From: Alberto Leiva Popper Date: Tue, 5 Nov 2024 23:16:49 +0000 (-0600) Subject: Rewrite the core loop and its threading X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=9775cea735cbe61a0e68c16c2dcdddd793a12099;p=thirdparty%2FFORT-validator.git Rewrite the core loop and its threading Decouples threads from TALs; threads claim RPPs now. Aside from scaling better, this unclogs the way to several future improvements. --- diff --git a/src/Makefile.am b/src/Makefile.am index 86cc34aa..5f41650b 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -72,7 +72,8 @@ fort_SOURCES += sig.h sig.c fort_SOURCES += slurm/db_slurm.c slurm/db_slurm.h fort_SOURCES += slurm/slurm_loader.c slurm/slurm_loader.h fort_SOURCES += slurm/slurm_parser.c slurm/slurm_parser.h -fort_SOURCES += state.h state.c +fort_SOURCES += stream.h stream.c +fort_SOURCES += task.h task.c fort_SOURCES += thread_pool.c thread_pool.h fort_SOURCES += thread_var.h thread_var.c fort_SOURCES += types/address.h types/address.c diff --git a/src/abbreviations.txt b/src/abbreviations.txt index cfdb82dc..c06e6f6c 100644 --- a/src/abbreviations.txt +++ b/src/abbreviations.txt @@ -27,6 +27,7 @@ notif: (RRDP) Notification pdu: Protocol Data Unit (RFC 6810) pp: Publication Point pr: print +pfx: prefix ptr: pointer refget: reference get (+1 to reference counter) refput: reference put (-1 to reference counter) diff --git a/src/asn1/asn1c/ROAIPAddressFamily.c b/src/asn1/asn1c/ROAIPAddressFamily.c index 29f1aa51..66101e79 100644 --- a/src/asn1/asn1c/ROAIPAddressFamily.c +++ b/src/asn1/asn1c/ROAIPAddressFamily.c @@ -47,14 +47,14 @@ static json_t * prefix6_to_json(struct ROAIPAddress *addr) { struct ipv6_prefix prefix6; - char buff[INET6_ADDRSTRLEN]; + char buf[INET6_ADDRSTRLEN]; if (prefix6_decode(&addr->address, &prefix6) != 0) return NULL; - if (inet_ntop(AF_INET6, &prefix6.addr, buff, INET6_ADDRSTRLEN) == NULL) + if (inet_ntop(AF_INET6, &prefix6.addr, buf, INET6_ADDRSTRLEN) == NULL) return NULL; - return prefix2json(buff, prefix6.len); + return prefix2json(buf, prefix6.len); } static json_t * diff --git a/src/cache.c b/src/cache.c index 704cf95a..a3e6359d 100644 --- a/src/cache.c +++ b/src/cache.c @@ -74,7 +74,7 @@ struct cache_commit { STAILQ_ENTRY(cache_commit) lh; }; -STAILQ_HEAD(cache_commits, cache_commit) commits = STAILQ_HEAD_INITIALIZER(commits); +static STAILQ_HEAD(cache_commits, cache_commit) commits = STAILQ_HEAD_INITIALIZER(commits); #define LOCKFILE ".lock" #define INDEX_FILE "index.json" diff --git a/src/config.c b/src/config.c index e49d4699..f6ada018 100644 --- a/src/config.c +++ b/src/config.c @@ -22,7 +22,6 @@ #include "init.h" #include "json_handler.h" #include "log.h" -#include "state.h" #include "thread_pool.h" #include "types/array.h" #include "types/path.h" diff --git a/src/main.c b/src/main.c index 994bf7d7..ccb789aa 100644 --- a/src/main.c +++ b/src/main.c @@ -10,9 +10,11 @@ #include "output_printer.h" #include "print_file.h" #include "relax_ng.h" +#include "rtr/db/vrps.h" #include "rtr/rtr.h" #include "rsync.h" #include "sig.h" +#include "task.h" #include "thread_var.h" static int @@ -159,6 +161,7 @@ main(int argc, char **argv) error = output_setup(); if (error) goto revert_vrps; + task_setup(); /* Meat */ @@ -175,6 +178,7 @@ main(int argc, char **argv) } /* End */ + task_teardown(); revert_vrps: vrps_destroy(); revert_relax_ng: diff --git a/src/object/bgpsec.c b/src/object/bgpsec.c index 7847405d..092095e1 100644 --- a/src/object/bgpsec.c +++ b/src/object/bgpsec.c @@ -20,7 +20,7 @@ asn_cb(struct asn_range const *range, void *arg) return pr_val_err("BGPsec certificate is not allowed to contain ASN range %u-%u.", range->min, range->max); - return vhandler_handle_router_key(params->ski, range, params->spk); + return vhandle_router_key(params->ski, range, params->spk); } int diff --git a/src/object/certificate.c b/src/object/certificate.c index 5ad8af7d..9de21803 100644 --- a/src/object/certificate.c +++ b/src/object/certificate.c @@ -24,6 +24,8 @@ #include "object/ghostbusters.h" #include "object/manifest.h" #include "object/roa.h" +#include "object/tal.h" +#include "task.h" #include "thread_var.h" #include "types/name.h" #include "types/path.h" @@ -215,15 +217,13 @@ fail: fnstack_pop(); } static int -validate_spki(X509_PUBKEY *cert_spki) +validate_spki(struct tal *tal, X509_PUBKEY *cert_spki) { - struct tal *tal; X509_PUBKEY *tal_spki; int error; - tal = validation_tal(state_retrieve()); if (tal == NULL) - pr_crit("Validation state has no TAL."); + pr_crit("TAL is NULL."); /* * We have a problem at this point: @@ -392,7 +392,7 @@ validate_subject_public_key(X509_PUBKEY *pubkey) } static int -validate_public_key(X509 *cert, enum cert_type type) +validate_public_key(struct rpki_certificate *cert) { X509_PUBKEY *pubkey; EVP_PKEY *evppkey; @@ -401,7 +401,7 @@ validate_public_key(X509 *cert, enum cert_type type) int error; /* Reminder: X509_PUBKEY is the same as SubjectPublicKeyInfo. */ - pubkey = X509_get_X509_PUBKEY(cert); + pubkey = X509_get_X509_PUBKEY(cert->x509); if (pubkey == NULL) return val_crypto_err("X509_get_X509_PUBKEY() returned NULL"); @@ -409,7 +409,7 @@ validate_public_key(X509 *cert, enum cert_type type) if (!ok) return val_crypto_err("X509_PUBKEY_get0_param() returned %d", ok); - if (type == CERTYPE_BGPSEC) + if (cert->type == CERTYPE_BGPSEC) return validate_certificate_public_key_algorithm_bgpsec(pa); error = validate_certificate_public_key_algorithm(pa); @@ -432,13 +432,13 @@ validate_public_key(X509 *cert, enum cert_type type) * getting the message. */ - if (type == CERTYPE_TA) { - error = validate_spki(pubkey); + if (cert->type == CERTYPE_TA) { + error = validate_spki(cert->tal, pubkey); if (error) return error; - if ((evppkey = X509_get0_pubkey(cert)) == NULL) + if ((evppkey = X509_get0_pubkey(cert->x509)) == NULL) return val_crypto_err("X509_get0_pubkey() returned NULL"); - if (X509_verify(cert, evppkey) != 1) + if (X509_verify(cert->x509, evppkey) != 1) return -EINVAL; } @@ -490,7 +490,7 @@ certificate_validate_rfc6487(struct rpki_certificate *cert) /* rfc6487#section-4.7 */ /* Fragment of rfc8630#section-2.3 */ - error = validate_public_key(cert->x509, cert->type); + error = validate_public_key(cert); if (error) return error; @@ -773,21 +773,6 @@ end: BIO_free(bio); return cert; } -static void -certificate_stack_push(struct cert_stack *stack, struct cache_mapping *map, - struct rpki_certificate *parent) -{ - struct rpki_certificate *cert; - - cert = pzalloc(sizeof(*cert)); - map_copy(&cert->map, map); - cert->parent = parent; - cert->refcount = 1; - SLIST_INSERT_HEAD(stack, cert, lh); - - parent->refcount++; -} - void rpki_certificate_init_ee(struct rpki_certificate *ee, struct rpki_certificate *parent, bool force_inherit) @@ -797,9 +782,9 @@ rpki_certificate_init_ee(struct rpki_certificate *ee, ee->policy = RPKI_POLICY_RFC6484; ee->resources = resources_create(RPKI_POLICY_RFC6484, force_inherit); ee->parent = parent; - ee->refcount = 1; + atomic_init(&ee->refcount, 1); - parent->refcount++; + atomic_fetch_add(&parent->refcount, 1); } void @@ -819,13 +804,47 @@ rpki_certificate_cleanup(struct rpki_certificate *cert) void rpki_certificate_free(struct rpki_certificate *cert) { - cert->refcount--; - if (cert->refcount == 0) { + if (atomic_fetch_sub(&cert->refcount, 1) == 1) { rpki_certificate_cleanup(cert); free(cert); } } +/* + * It appears that this function is called by LibreSSL whenever it finds an + * error while validating. + * It is expected to return "okay" status: Nonzero if the error should be + * ignored, zero if the error is grounds to abort the validation. + * + * Note to myself: During my tests, this function was called in + * X509_verify_cert(ctx) -> check_chain_extensions(0, ctx), + * and then twice again in + * X509_verify_cert(ctx) -> internal_verify(1, ctx). + * + * Regarding the ok argument: I'm not 100% sure that I get it; I don't + * understand why this function would be called with ok = 1. + * http://openssl.cs.utah.edu/docs/crypto/X509_STORE_CTX_set_verify_cb.html + * The logic I implemented is the same as the second example: Always ignore the + * error that's troubling the library, otherwise try to be as unintrusive as + * possible. + */ +static int +cb(int ok, X509_STORE_CTX *ctx) +{ + int error; + + /* + * We need to handle two new critical extensions (IP Resources and ASN + * Resources), so unknown critical extensions are fine as far as + * LibreSSL is concerned. + * Unfortunately, LibreSSL has no way of telling us *which* is the + * unknown critical extension, but since RPKI defines its own set of + * valid extensions, we'll have to figure it out later anyway. + */ + error = X509_STORE_CTX_get_error(ctx); + return (error == X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION) ? 1 : ok; +} + static STACK_OF(X509) * build_trusted_stack(struct rpki_certificate *cert) { @@ -906,6 +925,8 @@ certificate_validate_chain(struct rpki_certificate *cert) /* Reference: openbsd/src/usr.bin/openssl/verify.c */ X509_STORE_CTX *ctx; + X509_STORE *store; + X509_VERIFY_PARAM *params; STACK_OF(X509) *trusted; STACK_OF(X509_CRL) *crls; int ok; @@ -915,30 +936,45 @@ certificate_validate_chain(struct rpki_certificate *cert) return 0; /* No chain to validate. */ ctx = X509_STORE_CTX_new(); - if (ctx == NULL) { - val_crypto_err("X509_STORE_CTX_new() returned NULL"); - return EINVAL; + if (ctx == NULL) + return val_crypto_err("X509_STORE_CTX_new() returned NULL"); + + store = X509_STORE_new(); + if (!store) { + error = val_crypto_err("X509_STORE_new() returned NULL"); + goto end1; } + params = X509_VERIFY_PARAM_new(); + if (params == NULL) { + error = val_crypto_err("X509_VERIFY_PARAM_new() returned NULL"); + goto end2; + } + + X509_VERIFY_PARAM_set_flags(params, X509_V_FLAG_CRL_CHECK); + if (config_get_validation_time() != 0) + X509_VERIFY_PARAM_set_time(params, config_get_validation_time()); + X509_STORE_set1_param(store, params); + X509_STORE_set_verify_cb(store, cb); + /* Returns 0 or 1 , all callers test ! only. */ - ok = X509_STORE_CTX_init(ctx, validation_store(state_retrieve()), - cert->x509, NULL); + ok = X509_STORE_CTX_init(ctx, store, cert->x509, NULL); if (!ok) { error = val_crypto_err("X509_STORE_CTX_init() returned %d", ok); - goto end1; + goto end3; } trusted = build_trusted_stack(cert); if (!trusted) { error = EINVAL; - goto end1; + goto end3; } X509_STORE_CTX_trusted_stack(ctx, trusted); crls = build_crl_stack(cert); if (!crls) { error = EINVAL; - goto end2; + goto end4; } X509_STORE_CTX_set0_crls(ctx, crls); @@ -961,9 +997,10 @@ certificate_validate_chain(struct rpki_certificate *cert) * error code is stored in the context. */ error = X509_STORE_CTX_get_error(ctx); - if (error == X509_V_ERR_CRL_HAS_EXPIRED) + if (error == X509_V_ERR_CRL_HAS_EXPIRED) { complain_crl_stale(cert->parent->rpp.crl.obj); - else if (error) + error = EINVAL; + } else if (error) pr_val_err("Certificate validation failed: %s", X509_verify_cert_error_string(error)); else { @@ -975,13 +1012,15 @@ certificate_validate_chain(struct rpki_certificate *cert) val_crypto_err("Certificate validation failed: %d", ok); error = EINVAL; } - goto end3; + goto end5; } error = 0; -end3: sk_X509_CRL_free(crls); -end2: sk_X509_free(trusted); +end5: sk_X509_CRL_free(crls); +end4: sk_X509_free(trusted); +end3: X509_VERIFY_PARAM_free(params); +end2: X509_STORE_free(store); end1: X509_STORE_CTX_free(ctx); return error; } @@ -1850,14 +1889,15 @@ end: fnstack_pop(); return error; } -static int -certificate_traverse(struct rpki_certificate *ca, struct cert_stack *stack) +int +certificate_traverse(struct rpki_certificate *ca) { struct cache_cage *cage; char const *mft; array_index i; struct cache_mapping *map; char const *ext; + unsigned int queued; int error; error = certificate_validate(ca); @@ -1889,58 +1929,22 @@ retry: mft = cage_map_file(cage, ca->sias.rpkiManifest); goto end; } + queued = 0; for (i = 0; i < ca->rpp.nfiles; i++) { map = ca->rpp.files + i; ext = map->url + strlen(map->url) - 4; if (strcmp(ext, ".cer") == 0) - certificate_stack_push(stack, map, ca); + queued += task_enqueue(map, ca); else if (strcmp(ext, ".roa") == 0) roa_traverse(map, ca); else if (strcmp(ext, ".gbr") == 0) ghostbusters_traverse(map, ca); } + if (queued > 0) + task_wakeup(); cache_commit_rpp(ca->sias.caRepository, &ca->rpp); end: free(cage); return error; } - -int -traverse_tree(struct cache_mapping const *ta_map, struct validation *state) -{ - struct cert_stack stack; - struct rpki_certificate *ta; - struct rpki_certificate *ca; - int error; - - SLIST_INIT(&stack); - - /* == Root certificate == */ - ta = pzalloc(sizeof(struct rpki_certificate)); - map_copy(&ta->map, ta_map); - ta->refcount = 1; - - error = certificate_traverse(ta, &stack); - if (error) - goto end; - - /* - * From now on, the tree should be considered valid, even if subsequent - * certificates fail. - * (the root validated successfully; subtrees are isolated problems.) - */ - - /* == Every other certificate == */ - while (!SLIST_EMPTY(&stack)) { - ca = SLIST_FIRST(&stack); - SLIST_REMOVE_HEAD(&stack, lh); - - certificate_traverse(ca, &stack); - - rpki_certificate_free(ca); - } - -end: rpki_certificate_free(ta); - return error; -} diff --git a/src/object/certificate.h b/src/object/certificate.h index e4cc9994..212f537b 100644 --- a/src/object/certificate.h +++ b/src/object/certificate.h @@ -2,6 +2,7 @@ #define SRC_OBJECT_CERTIFICATE_H_ #include +#include #include #include "asn1/asn1c/ANY.h" @@ -9,7 +10,6 @@ #include "cache.h" #include "certificate_refs.h" #include "resource.h" -#include "state.h" #include "types/rpp.h" /* Certificate types in the RPKI */ @@ -30,11 +30,12 @@ struct rpki_certificate { struct resources *resources; struct sia_uris sias; + struct tal *tal; /* Only needed by TAs for now */ struct rpki_certificate *parent; struct rpp rpp; /* Nonexistent on EEs */ SLIST_ENTRY(rpki_certificate) lh; /* List Hook */ - unsigned int refcount; + atomic_uint refcount; }; void rpki_certificate_init_ee(struct rpki_certificate *, @@ -82,6 +83,6 @@ int certificate_validate_extensions_bgpsec(void); */ int certificate_validate_aia(struct rpki_certificate *); -int traverse_tree(struct cache_mapping const *, struct validation *); +int certificate_traverse(struct rpki_certificate *); #endif /* SRC_OBJECT_CERTIFICATE_H_ */ diff --git a/src/object/roa.c b/src/object/roa.c index 6319fe57..5b2c487a 100644 --- a/src/object/roa.c +++ b/src/object/roa.c @@ -5,6 +5,7 @@ #include "log.h" #include "object/signed_object.h" #include "thread_var.h" +#include "validation_handler.h" static int decode_roa(struct signed_object *sobj, struct RouteOriginAttestation **result) @@ -21,18 +22,19 @@ static int ____handle_roa_v4(struct resources *parent, unsigned long asn, struct ROAIPAddress *roa_addr) { - struct ipv4_prefix prefix; - unsigned long max_length; + struct ipv4_prefix pfx; + unsigned long maxlen; + char buf[INET_ADDRSTRLEN]; int error; - error = prefix4_decode(&roa_addr->address, &prefix); + error = prefix4_decode(&roa_addr->address, &pfx); if (error) return error; - pr_val_debug("address: %s/%u", v4addr2str(&prefix.addr), prefix.len); + pr_val_debug("address: %s/%u", addr2str4(&pfx.addr, buf), pfx.len); if (roa_addr->maxLength != NULL) { - error = asn_INTEGER2ulong(roa_addr->maxLength, &max_length); + error = asn_INTEGER2ulong(roa_addr->maxLength, &maxlen); if (error) { if (errno) { pr_val_err("Error casting ROA's IPv4 maxLength: %s", @@ -40,46 +42,47 @@ ____handle_roa_v4(struct resources *parent, unsigned long asn, } return pr_val_err("The ROA's IPv4 maxLength isn't a valid unsigned long"); } - pr_val_debug("maxLength: %lu", max_length); + pr_val_debug("maxLength: %lu", maxlen); - if (max_length > 32) { + if (maxlen > 32) { return pr_val_err("maxLength (%lu) is out of bounds (0-32).", - max_length); + maxlen); } - if (prefix.len > max_length) { + if (pfx.len > maxlen) { return pr_val_err("Prefix length (%u) > maxLength (%lu)", - prefix.len, max_length); + pfx.len, maxlen); } } else { - max_length = prefix.len; + maxlen = pfx.len; } - if (!resources_contains_ipv4(parent, &prefix)) { + if (!resources_contains_ipv4(parent, &pfx)) { return pr_val_err("ROA is not allowed to advertise %s/%u.", - v4addr2str(&prefix.addr), prefix.len); + addr2str4(&pfx.addr, buf), pfx.len); } - return vhandler_handle_roa_v4(asn, &prefix, max_length); + return vhandle_roa_v4(asn, &pfx, maxlen); } static int ____handle_roa_v6(struct resources *parent, unsigned long asn, struct ROAIPAddress *roa_addr) { - struct ipv6_prefix prefix; - unsigned long max_length; + struct ipv6_prefix pfx; + unsigned long maxlen; + char buf[INET6_ADDRSTRLEN]; int error; - error = prefix6_decode(&roa_addr->address, &prefix); + error = prefix6_decode(&roa_addr->address, &pfx); if (error) return error; - pr_val_debug("address: %s/%u", v6addr2str(&prefix.addr), prefix.len); + pr_val_debug("address: %s/%u", addr2str6(&pfx.addr, buf), pfx.len); if (roa_addr->maxLength != NULL) { - error = asn_INTEGER2ulong(roa_addr->maxLength, &max_length); + error = asn_INTEGER2ulong(roa_addr->maxLength, &maxlen); if (error) { if (errno) { pr_val_err("Error casting ROA's IPv6 maxLength: %s", @@ -87,28 +90,28 @@ ____handle_roa_v6(struct resources *parent, unsigned long asn, } return pr_val_err("The ROA's IPv6 maxLength isn't a valid unsigned long"); } - pr_val_debug("maxLength: %lu", max_length); + pr_val_debug("maxLength: %lu", maxlen); - if (max_length > 128) { + if (maxlen > 128) { return pr_val_err("maxLength (%lu) is out of bounds (0-128).", - max_length); + maxlen); } - if (prefix.len > max_length) { + if (pfx.len > maxlen) { return pr_val_err("Prefix length (%u) > maxLength (%lu)", - prefix.len, max_length); + pfx.len, maxlen); } } else { - max_length = prefix.len; + maxlen = pfx.len; } - if (!resources_contains_ipv6(parent, &prefix)) { + if (!resources_contains_ipv6(parent, &pfx)) { return pr_val_err("ROA is not allowed to advertise %s/%u.", - v6addr2str(&prefix.addr), prefix.len); + addr2str6(&pfx.addr, buf), pfx.len); } - return vhandler_handle_roa_v6(asn, &prefix, max_length); + return vhandle_roa_v6(asn, &pfx, maxlen); } static int diff --git a/src/object/tal.c b/src/object/tal.c index ffa68f8c..084412ea 100644 --- a/src/object/tal.c +++ b/src/object/tal.c @@ -11,6 +11,7 @@ #include "file.h" #include "log.h" #include "object/certificate.h" +#include "task.h" #include "thread_var.h" #include "types/path.h" #include "types/str.h" @@ -23,18 +24,6 @@ struct tal { size_t spki_len; }; -struct validation_thread { - pthread_t pid; - char *tal_file; /* TAL file name */ - struct db_table *db; - int error; - /* This should also only be manipulated by the parent thread. */ - SLIST_ENTRY(validation_thread) next; -}; - -/* List of threads, one per TAL file */ -SLIST_HEAD(threads_list, validation_thread); - static char * find_newline(char *str) { @@ -146,180 +135,121 @@ tal_get_spki(struct tal *tal, unsigned char const **buffer, size_t *len) *len = tal->spki_len; } -static void -__do_file_validation(struct validation_thread *thread) +static int +validate_ta(struct tal *tal, struct cache_mapping const *ta_map) +{ + struct rpki_certificate *ta; + int error; + + ta = pzalloc(sizeof(struct rpki_certificate)); + map_copy(&ta->map, ta_map); + ta->tal = tal; + atomic_init(&ta->refcount, 1); + + error = certificate_traverse(ta); + + rpki_certificate_free(ta); + return error; +} + +static int +traverse_tal(char const *tal_path, void *arg) { struct tal tal; - struct validation_handler collector; - struct db_table *db; - struct validation *state; char **url; struct cache_mapping map; + int error; - thread->error = tal_init(&tal, thread->tal_file); - if (thread->error) - return; - - collector.handle_roa_v4 = handle_roa_v4; - collector.handle_roa_v6 = handle_roa_v6; - collector.handle_router_key = handle_router_key; - collector.arg = db = db_table_create(); + fnstack_push(tal_path); - thread->error = validation_prepare(&state, &tal, &collector); - if (thread->error) { - db_table_destroy(db); + error = tal_init(&tal, tal_path); + if (error) goto end1; - } + /* Online attempts */ ARRAYLIST_FOREACH(&tal.urls, url) { map.url = *url; map.path = cache_refresh_url(*url); if (!map.path) continue; - if (traverse_tree(&map, state) != 0) + if (validate_ta(&tal, &map) != 0) continue; goto end2; /* Happy path */ } + /* Offline fallback attempts */ ARRAYLIST_FOREACH(&tal.urls, url) { map.url = *url; map.path = cache_fallback_url(*url); if (!map.path) continue; - if (traverse_tree(&map, state) != 0) + if (validate_ta(&tal, &map) != 0) continue; goto end2; /* Happy path */ } pr_op_err("None of the TAL URIs yielded a successful traversal."); - thread->error = EINVAL; - db_table_destroy(db); - db = NULL; + error = EINVAL; -end2: thread->db = db; - validation_destroy(state); -end1: tal_cleanup(&tal); +end2: tal_cleanup(&tal); +end1: fnstack_pop(); + return error; } static void * -do_file_validation(void *arg) +pick_up_work(void *arg) { - struct validation_thread *thread = arg; - time_t start, finish; - - start = time(NULL); - - fnstack_init(); - fnstack_push(thread->tal_file); - - __do_file_validation(thread); + struct validation_task *task = NULL; - fnstack_cleanup(); + while ((task = task_dequeue(task)) != NULL) + certificate_traverse(task->ca); - finish = time(NULL); - if (start != ((time_t) -1) && finish != ((time_t) -1)) - pr_op_debug("The %s tree took %.0lf seconds.", - path_filename(thread->tal_file), - difftime(finish, start)); return NULL; } -static void -thread_destroy(struct validation_thread *thread) -{ - free(thread->tal_file); - db_table_destroy(thread->db); - free(thread); -} - -/* Creates a thread for the @tal_file TAL */ -static int -spawn_tal_thread(char const *tal_file, void *arg) -{ - struct threads_list *threads = arg; - struct validation_thread *thread; - int error; - - thread = pmalloc(sizeof(struct validation_thread)); - - thread->tal_file = pstrdup(tal_file); - thread->db = NULL; - thread->error = -EINTR; - SLIST_INSERT_HEAD(threads, thread, next); - - error = pthread_create(&thread->pid, NULL, do_file_validation, thread); - if (error) { - pr_op_err("Could not spawn validation thread for %s: %s", - tal_file, strerror(error)); - free(thread->tal_file); - free(thread); - } - - return error; -} - -struct db_table * +int perform_standalone_validation(void) { - struct threads_list threads = SLIST_HEAD_INITIALIZER(threads); - struct validation_thread *thread; - struct db_table *db = NULL; + pthread_t threads[5]; // XXX variabilize + unsigned int ids[5]; + array_index t, t2; int error; - int tmperr; error = cache_prepare(); if (error) - return NULL; - - /* TODO (fine) Maybe don't spawn threads if there's only one TAL */ - if (foreach_file(config_get_tal(), ".tal", true, spawn_tal_thread, - &threads) != 0) { - while (!SLIST_EMPTY(&threads)) { - thread = SLIST_FIRST(&threads); - SLIST_REMOVE_HEAD(&threads, next); - thread_destroy(thread); - } + return error; + fnstack_init(); + task_start(); - /* - * Commit even on failure, as there's no reason to throw away - * something we recently downloaded if it's marked as valid. - */ + if (foreach_file(config_get_tal(), ".tal", true, traverse_tal, NULL)!=0) goto end; - } - /* Wait for all */ - while (!SLIST_EMPTY(&threads)) { - thread = SLIST_FIRST(&threads); - tmperr = pthread_join(thread->pid, NULL); - if (tmperr) - pr_crit("pthread_join() threw '%s' on the '%s' thread.", - strerror(tmperr), thread->tal_file); - SLIST_REMOVE_HEAD(&threads, next); - if (thread->error) { - error = thread->error; - pr_op_warn("Validation from TAL '%s' yielded '%s'; " - "discarding all validation results.", - thread->tal_file, strerror(abs(error))); - } - - if (!error) { - if (db == NULL) { - db = thread->db; - thread->db = NULL; - } else { - error = db_table_join(db, thread->db); - } + for (t = 0; t < 5; t++) { + ids[t] = t; + error = pthread_create(&threads[t], NULL, pick_up_work, &ids[t]); + if (error) { + pr_op_err("Could not spawn validation thread %zu: %s", + t, strerror(error)); + break; } - - thread_destroy(thread); } - /* If at least one thread had a fatal error, the table is unusable. */ - if (error) { - db_table_destroy(db); - db = NULL; + if (t == 0) { + pick_up_work(NULL); + error = 0; + } else for (t2 = 0; t2 < t; t2++) { + error = pthread_join(threads[t2], NULL); + if (error) + pr_crit("pthread_join(%zu) failed: %s", + t2, strerror(error)); } -end: cache_commit(); - return db; +end: task_stop(); + fnstack_cleanup(); + /* + * Commit even on failure, as there's no reason to throw away something + * we might have recently downloaded if it managed to be marked valid. + */ + cache_commit(); + return error; } diff --git a/src/object/tal.h b/src/object/tal.h index 0bf235cd..70f73303 100644 --- a/src/object/tal.h +++ b/src/object/tal.h @@ -1,15 +1,15 @@ #ifndef SRC_OBJECT_TAL_H_ #define SRC_OBJECT_TAL_H_ -/* This is RFC 8630. */ +#include "stddef.h" -#include "rtr/db/db_table.h" +/* This is RFC 8630. */ struct tal; char const *tal_get_file_name(struct tal *); void tal_get_spki(struct tal *, unsigned char const **, size_t *); -struct db_table *perform_standalone_validation(void); +int perform_standalone_validation(void); #endif /* SRC_OBJECT_TAL_H_ */ diff --git a/src/resource.c b/src/resource.c index 67d76f61..4468bcce 100644 --- a/src/resource.c +++ b/src/resource.c @@ -113,6 +113,7 @@ add_prefix4(struct resources *resources, struct resources *parent, IPAddress_t *addr) { struct ipv4_prefix prefix; + char buf[INET_ADDRSTRLEN]; int error; if (parent && (resources->ip4s == parent->ip4s)) @@ -126,10 +127,10 @@ add_prefix4(struct resources *resources, struct resources *parent, switch (resources->policy) { case RPKI_POLICY_RFC6484: return pr_val_err("Parent certificate doesn't own IPv4 prefix '%s/%u'.", - v4addr2str(&prefix.addr), prefix.len); + addr2str4(&prefix.addr, buf), prefix.len); case RPKI_POLICY_RFC8360: return pr_val_warn("Certificate is overclaiming the IPv4 prefix '%s/%u'.", - v4addr2str(&prefix.addr), prefix.len); + addr2str4(&prefix.addr, buf), prefix.len); } } @@ -139,12 +140,12 @@ add_prefix4(struct resources *resources, struct resources *parent, error = res4_add_prefix(resources->ip4s, &prefix); if (error) { pr_val_err("Error adding IPv4 prefix '%s/%u' to certificate resources: %s", - v4addr2str(&prefix.addr), prefix.len, + addr2str4(&prefix.addr, buf), prefix.len, sarray_err2str(error)); return error; } - pr_val_debug("Prefix: %s/%u", v4addr2str(&prefix.addr), prefix.len); + pr_val_debug("Prefix: %s/%u", addr2str4(&prefix.addr, buf), prefix.len); return 0; } @@ -153,6 +154,7 @@ add_prefix6(struct resources *resources, struct resources *parent, IPAddress_t *addr) { struct ipv6_prefix prefix; + char buf[INET6_ADDRSTRLEN]; int error; if (parent && (resources->ip6s == parent->ip6s)) @@ -166,10 +168,10 @@ add_prefix6(struct resources *resources, struct resources *parent, switch (resources->policy) { case RPKI_POLICY_RFC6484: return pr_val_err("Parent certificate doesn't own IPv6 prefix '%s/%u'.", - v6addr2str(&prefix.addr), prefix.len); + addr2str6(&prefix.addr, buf), prefix.len); case RPKI_POLICY_RFC8360: return pr_val_warn("Certificate is overclaiming the IPv6 prefix '%s/%u'.", - v6addr2str(&prefix.addr), prefix.len); + addr2str6(&prefix.addr, buf), prefix.len); } } @@ -179,12 +181,12 @@ add_prefix6(struct resources *resources, struct resources *parent, error = res6_add_prefix(resources->ip6s, &prefix); if (error) { pr_val_err("Error adding IPv6 prefix '%s/%u' to certificate resources: %s", - v6addr2str(&prefix.addr), prefix.len, + addr2str6(&prefix.addr, buf), prefix.len, sarray_err2str(error)); return error; } - pr_val_debug("Prefix: %s/%u", v6addr2str(&prefix.addr), prefix.len); + pr_val_debug("Prefix: %s/%u", addr2str6(&prefix.addr, buf), prefix.len); return 0; } @@ -208,6 +210,8 @@ add_range4(struct resources *resources, struct resources *parent, IPAddressRange_t *input) { struct ipv4_range range; + char buf1[INET_ADDRSTRLEN]; + char buf2[INET_ADDRSTRLEN]; int error; if (parent && (resources->ip4s == parent->ip4s)) @@ -221,10 +225,12 @@ add_range4(struct resources *resources, struct resources *parent, switch (resources->policy) { case RPKI_POLICY_RFC6484: return pr_val_err("Parent certificate doesn't own IPv4 range '%s-%s'.", - v4addr2str(&range.min), v4addr2str2(&range.max)); + addr2str4(&range.min, buf1), + addr2str4(&range.max, buf2)); case RPKI_POLICY_RFC8360: return pr_val_warn("Certificate is overclaiming the IPv4 range '%s-%s'.", - v4addr2str(&range.min), v4addr2str2(&range.max)); + addr2str4(&range.min, buf1), + addr2str4(&range.max, buf2)); } } @@ -234,13 +240,15 @@ add_range4(struct resources *resources, struct resources *parent, error = res4_add_range(resources->ip4s, &range); if (error) { pr_val_err("Error adding IPv4 range '%s-%s' to certificate resources: %s", - v4addr2str(&range.min), v4addr2str2(&range.max), + addr2str4(&range.min, buf1), + addr2str4(&range.max, buf2), sarray_err2str(error)); return error; } - pr_val_debug("Range: %s-%s", v4addr2str(&range.min), - v4addr2str2(&range.max)); + pr_val_debug("Range: %s-%s", + addr2str4(&range.min, buf1), + addr2str4(&range.max, buf2)); return 0; } @@ -249,6 +257,8 @@ add_range6(struct resources *resources, struct resources *parent, IPAddressRange_t *input) { struct ipv6_range range; + char buf1[INET6_ADDRSTRLEN]; + char buf2[INET6_ADDRSTRLEN]; int error; if (parent && (resources->ip6s == parent->ip6s)) @@ -262,10 +272,12 @@ add_range6(struct resources *resources, struct resources *parent, switch (resources->policy) { case RPKI_POLICY_RFC6484: return pr_val_err("Parent certificate doesn't own IPv6 range '%s-%s'.", - v6addr2str(&range.min), v6addr2str2(&range.max)); + addr2str6(&range.min, buf1), + addr2str6(&range.max, buf2)); case RPKI_POLICY_RFC8360: return pr_val_warn("Certificate is overclaiming the IPv6 range '%s-%s'.", - v6addr2str(&range.min), v6addr2str2(&range.max)); + addr2str6(&range.min, buf1), + addr2str6(&range.max, buf2)); } } @@ -275,13 +287,15 @@ add_range6(struct resources *resources, struct resources *parent, error = res6_add_range(resources->ip6s, &range); if (error) { pr_val_err("Error adding IPv6 range '%s-%s' to certificate resources: %s", - v6addr2str(&range.min), v6addr2str2(&range.max), + addr2str6(&range.min, buf1), + addr2str6(&range.max, buf2), sarray_err2str(error)); return error; } - pr_val_debug("Range: %s-%s", v6addr2str(&range.min), - v6addr2str2(&range.max)); + pr_val_debug("Range: %s-%s", + addr2str6(&range.min, buf1), + addr2str6(&range.max, buf2)); return 0; } diff --git a/src/rtr/db/db_table.c b/src/rtr/db/db_table.c index abb1ef49..8818606a 100644 --- a/src/rtr/db/db_table.c +++ b/src/rtr/db/db_table.c @@ -1,8 +1,10 @@ #include "rtr/db/db_table.h" #include +#include #include "alloc.h" +#include "common.h" #include "log.h" #include "types/uthash.h" @@ -19,6 +21,7 @@ struct hashable_key { struct db_table { struct hashable_roa *roas; struct hashable_key *router_keys; + pthread_mutex_t lock; }; struct db_table * @@ -29,6 +32,8 @@ db_table_create(void) table = pmalloc(sizeof(struct db_table)); table->roas = NULL; table->router_keys = NULL; + panic_on_fail(pthread_mutex_init(&table->lock, NULL), + "pthread_mutex_init"); return table; } @@ -52,6 +57,8 @@ db_table_destroy(struct db_table *table) free(rk); } + pthread_mutex_destroy(&table->lock); + free(table); } @@ -61,9 +68,14 @@ add_roa(struct db_table *table, struct hashable_roa *new) struct hashable_roa *old; int error; + mutex_lock(&table->lock); + errno = 0; HASH_REPLACE(hh, table->roas, data, sizeof(new->data), new, old); error = errno; + + mutex_unlock(&table->lock); + if (error) { pr_val_err("ROA couldn't be added to hash table: %s", strerror(error)); @@ -81,9 +93,14 @@ add_router_key(struct db_table *table, struct hashable_key *new) struct hashable_key *old; int error; + mutex_lock(&table->lock); + errno = 0; HASH_REPLACE(hh, table->router_keys, data, sizeof(new->data), new, old); error = errno; + + mutex_unlock(&table->lock); + if (error) { pr_val_err("Router Key couldn't be added to hash table: %s", strerror(error)); diff --git a/src/rtr/db/vrps.c b/src/rtr/db/vrps.c index d43751c6..b94e8339 100644 --- a/src/rtr/db/vrps.c +++ b/src/rtr/db/vrps.c @@ -12,6 +12,7 @@ #include "rtr/db/deltas_array.h" #include "rtr/pdu.h" #include "slurm/slurm_loader.h" +#include "validation_handler.h" struct vrp_node { struct delta_vrp delta; @@ -126,41 +127,6 @@ vrps_destroy(void) db_table_destroy(state.base); } -int -handle_roa_v4(uint32_t as, struct ipv4_prefix const *prefix, - uint8_t max_length, void *arg) -{ - return rtrhandler_handle_roa_v4(arg, as, prefix, max_length); -} - -int -handle_roa_v6(uint32_t as, struct ipv6_prefix const * prefix, - uint8_t max_length, void *arg) -{ - return rtrhandler_handle_roa_v6(arg, as, prefix, max_length); -} - -int -handle_router_key(unsigned char const *ski, struct asn_range const *asns, - unsigned char const *spk, void *arg) -{ - uint64_t asn; - int error; - - /* - * TODO (warning) Umm... this is begging for a limit. - * If the issuer gets it wrong, we can iterate up to 2^32 times. - * The RFCs don't seem to care about this. - */ - for (asn = asns->min; asn <= asns->max; asn++) { - error = rtrhandler_handle_router_key(arg, ski, asn, spk); - if (error) - return error; - } - - return 0; -} - /* * High level validator function. * @@ -184,15 +150,20 @@ __vrps_update(bool *changed) struct deltas *new_deltas; int error; + vhandle_init(); + if (changed != NULL) *changed = false; old_base = state.base; - new_base = NULL; new_deltas = NULL; - new_base = perform_standalone_validation(); - if (new_base == NULL) - return EINVAL; + error = perform_standalone_validation(); + if (error) { + db_table_destroy(vhandle_claim()); + return error; + } + + new_base = vhandle_claim(); error = slurm_apply(new_base, &state.slurm); if (error) { diff --git a/src/rtr/db/vrps.h b/src/rtr/db/vrps.h index b10e20a3..aab5b390 100644 --- a/src/rtr/db/vrps.h +++ b/src/rtr/db/vrps.h @@ -28,11 +28,6 @@ int vrps_foreach_delta_since(serial_t, serial_t *, delta_vrp_foreach_cb, delta_router_key_foreach_cb, void *); int get_last_serial_number(serial_t *); -int handle_roa_v4(uint32_t, struct ipv4_prefix const *, uint8_t, void *); -int handle_roa_v6(uint32_t, struct ipv6_prefix const *, uint8_t, void *); -int handle_router_key(unsigned char const *, struct asn_range const *, - unsigned char const *, void *); - uint16_t get_current_session_id(uint8_t); void vrps_print_base(void); diff --git a/src/slurm/db_slurm.c b/src/slurm/db_slurm.c index 6816c993..432d2be5 100644 --- a/src/slurm/db_slurm.c +++ b/src/slurm/db_slurm.c @@ -37,8 +37,6 @@ struct db_slurm { struct slurm_csum_list csum_list; }; -static char addr_buf[INET6_ADDRSTRLEN]; - static void slurm_bgpsec_wrap_refget(struct slurm_bgpsec_wrap *elem) { @@ -437,20 +435,21 @@ db_slurm_foreach_assertion_bgpsec(struct db_slurm *db, bgpsec_foreach_cb cb, static int print_prefix_data(struct slurm_prefix *prefix, void *arg) { - char *pad = " "; + char addr_buf[INET6_ADDRSTRLEN]; pr_op_info(" {"); if (prefix->data_flag & SLURM_COM_FLAG_ASN) - pr_op_info("%s ASN: %u", pad, prefix->vrp.asn); + pr_op_info(" ASN: %u", prefix->vrp.asn); if (prefix->data_flag & SLURM_PFX_FLAG_PREFIX) { - pr_op_info("%s Prefix: %s/%u", pad, + pr_op_info(" Prefix: %s/%u", inet_ntop(prefix->vrp.addr_fam, &prefix->vrp.prefix, - addr_buf, INET6_ADDRSTRLEN), prefix->vrp.prefix_length); + addr_buf, INET6_ADDRSTRLEN), + prefix->vrp.prefix_length); } if (prefix->data_flag & SLURM_PFX_FLAG_MAX_LENGTH) - pr_op_info("%s Max prefix length: %u", pad, + pr_op_info(" Max prefix length: %u", prefix->vrp.max_prefix_length); pr_op_info(" }"); diff --git a/src/state.c b/src/state.c deleted file mode 100644 index f32901be..00000000 --- a/src/state.c +++ /dev/null @@ -1,154 +0,0 @@ -#include "state.h" - -#include "alloc.h" -#include "config.h" -#include "log.h" -#include "thread_var.h" - -/** - * Just a bunch of thread-specific variables that are too much of a pain - * to keep passing around. - * - * Should be refactored away, honestly. - */ -struct validation { - struct tal *tal; - - struct x509_data { - /** https://www.openssl.org/docs/man1.1.1/man3/X509_STORE_load_locations.html */ - X509_STORE *store; - X509_VERIFY_PARAM *params; - } x509_data; - - /** - * Two buffers calling code will store stringified IP addresses in, - * to prevent proliferation of similar buffers on the stack. - * - * They are meant to be large enough to contain both IPv4 and IPv6 - * addresses. - */ - char addr_buffer1[INET6_ADDRSTRLEN]; - char addr_buffer2[INET6_ADDRSTRLEN]; - - struct validation_handler validation_handler; -}; - -/* - * It appears that this function is called by LibreSSL whenever it finds an - * error while validating. - * It is expected to return "okay" status: Nonzero if the error should be - * ignored, zero if the error is grounds to abort the validation. - * - * Note to myself: During my tests, this function was called in - * X509_verify_cert(ctx) -> check_chain_extensions(0, ctx), - * and then twice again in - * X509_verify_cert(ctx) -> internal_verify(1, ctx). - * - * Regarding the ok argument: I'm not 100% sure that I get it; I don't - * understand why this function would be called with ok = 1. - * http://openssl.cs.utah.edu/docs/crypto/X509_STORE_CTX_set_verify_cb.html - * The logic I implemented is the same as the second example: Always ignore the - * error that's troubling the library, otherwise try to be as unintrusive as - * possible. - */ -static int -cb(int ok, X509_STORE_CTX *ctx) -{ - int error; - - /* - * We need to handle two new critical extensions (IP Resources and ASN - * Resources), so unknown critical extensions are fine as far as - * LibreSSL is concerned. - * Unfortunately, LibreSSL has no way of telling us *which* is the - * unknown critical extension, but since RPKI defines its own set of - * valid extensions, we'll have to figure it out later anyway. - */ - error = X509_STORE_CTX_get_error(ctx); - return (error == X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION) ? 1 : ok; -} - -/** - * Creates a struct validation, puts it in thread local, and (incidentally) - * returns it. - */ -int -validation_prepare(struct validation **out, struct tal *tal, - struct validation_handler *validation_handler) -{ - struct validation *result; - X509_VERIFY_PARAM *params; - int error; - - result = pmalloc(sizeof(struct validation)); - - error = state_store(result); - if (error) - goto undo_result; - - result->tal = tal; - - result->x509_data.store = X509_STORE_new(); - if (!result->x509_data.store) { - error = val_crypto_err("X509_STORE_new() returned NULL"); - goto undo_result; - } - - params = X509_VERIFY_PARAM_new(); - if (params == NULL) - enomem_panic(); - - X509_VERIFY_PARAM_set_flags(params, X509_V_FLAG_CRL_CHECK); - if (config_get_validation_time() != 0) - X509_VERIFY_PARAM_set_time(params, config_get_validation_time()); - X509_STORE_set1_param(result->x509_data.store, params); - X509_STORE_set_verify_cb(result->x509_data.store, cb); - - result->validation_handler = *validation_handler; - result->x509_data.params = params; /* Ownership transfered */ - - *out = result; - return 0; - -undo_result: - free(result); - return error; -} - -void -validation_destroy(struct validation *state) -{ - X509_VERIFY_PARAM_free(state->x509_data.params); - X509_STORE_free(state->x509_data.store); - free(state); -} - -struct tal * -validation_tal(struct validation *state) -{ - return (state != NULL) ? state->tal : NULL; -} - -X509_STORE * -validation_store(struct validation *state) -{ - return state->x509_data.store; -} - -char * -validation_get_ip_buffer1(struct validation *state) -{ - return state->addr_buffer1; -} - -char * -validation_get_ip_buffer2(struct validation *state) -{ - return state->addr_buffer2; -} - -struct validation_handler const * -validation_get_validation_handler(struct validation *state) -{ - return &state->validation_handler; -} diff --git a/src/state.h b/src/state.h deleted file mode 100644 index d168a715..00000000 --- a/src/state.h +++ /dev/null @@ -1,27 +0,0 @@ -#ifndef SRC_STATE_H_ -#define SRC_STATE_H_ - -#include - -#include "object/tal.h" -#include "validation_handler.h" - -struct validation; - -int validation_prepare(struct validation **, struct tal *, - struct validation_handler *); -void validation_destroy(struct validation *); - -struct tal *validation_tal(struct validation *); -X509_STORE *validation_store(struct validation *); - -void validation_pubkey_valid(struct validation *); -void validation_pubkey_invalid(struct validation *); - -char *validation_get_ip_buffer1(struct validation *); -char *validation_get_ip_buffer2(struct validation *); - -struct validation_handler const * -validation_get_validation_handler(struct validation *); - -#endif /* SRC_STATE_H_ */ diff --git a/src/stream.c b/src/stream.c index 0a4055e1..8ffd905c 100644 --- a/src/stream.c +++ b/src/stream.c @@ -4,6 +4,9 @@ #include #include +#include "alloc.h" +#include "log.h" + void read_stream_init(struct read_stream *stream, int fd) { diff --git a/src/stream.h b/src/stream.h index f0f34398..4c9ed95e 100644 --- a/src/stream.h +++ b/src/stream.h @@ -1,6 +1,8 @@ #ifndef SRC_STREAM_H_ #define SRC_STREAM_H_ +#include + struct read_stream { int fd; unsigned char *buffer; diff --git a/src/task.c b/src/task.c new file mode 100644 index 00000000..555f7516 --- /dev/null +++ b/src/task.c @@ -0,0 +1,182 @@ +#include "task.h" + +#include + +#include "alloc.h" +#include "common.h" +#include "log.h" + +/* Queued, not yet claimed tasks */ +static STAILQ_HEAD(validation_tasks, validation_task) tasks; +/* Active tasks (length of @tasks plus number of running tasks) */ +static int active; + +static bool enabled = true; + +static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t awakener = PTHREAD_COND_INITIALIZER; + +static void +task_free(struct validation_task *task) +{ + rpki_certificate_free(task->ca); + free(task); +} + +void +task_setup(void) +{ + STAILQ_INIT(&tasks); + active = 0; + enabled = true; + panic_on_fail(pthread_mutex_init(&lock, NULL), "pthread_mutex_init"); + panic_on_fail(pthread_cond_init(&awakener, NULL), "pthread_cond_init"); +} + +static void +cleanup(void) +{ + struct validation_task *task; + + enabled = false; + active = 0; + while (!STAILQ_EMPTY(&tasks)) { + task = STAILQ_FIRST(&tasks); + STAILQ_REMOVE_HEAD(&tasks, lh); + task_free(task); + } +} + +void +task_start(void) +{ + cleanup(); + enabled = true; +} + +void +task_stop(void) +{ + mutex_lock(&lock); + cleanup(); + mutex_unlock(&lock); +} + +void +task_teardown(void) +{ + pthread_mutex_destroy(&lock); + pthread_cond_destroy(&awakener); +} + +/* + * Defers a task for later. + * Call task_wakeup() once you've queued all your tasks. + */ +unsigned int +task_enqueue(struct cache_mapping *map, struct rpki_certificate *parent) +{ + struct validation_task *task; + struct rpki_certificate *ca; + + atomic_fetch_add(&parent->refcount, 1); + + ca = pzalloc(sizeof(struct rpki_certificate)); + ca->map.url = pstrdup(map->url); + ca->map.path = pstrdup(map->path); + ca->parent = parent; + atomic_init(&ca->refcount, 1); + + task = pmalloc(sizeof(struct validation_task)); + task->ca = ca; + + mutex_lock(&lock); + + if (enabled) { + STAILQ_INSERT_TAIL(&tasks, task, lh); + active++; + } else { + task_free(task); + } + + mutex_unlock(&lock); + + return 1; +} + +/* Wakes up threads currently waiting for tasks. */ +void +task_wakeup(void) +{ + mutex_lock(&lock); + panic_on_fail(pthread_cond_broadcast(&awakener), + "pthread_cond_broadcast"); + mutex_unlock(&lock); +} + +/* + * Frees the @prev previous task, and returns the next one. + * + * If no task is available yet, will sleep until someone calls task_wakeup(). + * If all the tasks are done, returns NULL. + * + * Assumes at least one task has been queued before the first dequeue. + */ +struct validation_task * +task_dequeue(struct validation_task *prev) +{ + struct validation_task *task; + struct timespec timeout; + int error; + + if (prev) + task_free(prev); + timeout.tv_nsec = 0; + + mutex_lock(&lock); + + if (!enabled) + goto end; + + if (prev) { + active--; + if (active < 0) + pr_crit("active < 0: %d", active); + } + + while (active > 0) { + pr_op_debug("task_dequeue(): %u tasks active.", active); + + task = STAILQ_FIRST(&tasks); + if (task != NULL) { + STAILQ_REMOVE_HEAD(&tasks, lh); + mutex_unlock(&lock); + pr_op_debug("task_dequeue(): Claimed task '%s'.", + task->ca->map.url); + return task; + } + + pr_op_debug("task_dequeue(): Sleeping..."); + timeout.tv_sec = time_fatal() + 10; + error = pthread_cond_timedwait(&awakener, &lock, &timeout); + switch (error) { + case 0: + pr_op_debug("task_dequeue(): Woke up by cond."); + break; + case ETIMEDOUT: + pr_op_debug("task_dequeue(): Woke up by timeout."); + break; + case EINTR: + pr_op_debug("task_dequeue(): Interrupted by signal."); + goto end; + default: + panic_on_fail(error, "pthread_cond_wait"); + } + } + + pr_op_debug("task_dequeue(): No more tasks; done."); + panic_on_fail(pthread_cond_broadcast(&awakener), + "pthread_cond_broadcast"); +end: mutex_unlock(&lock); + return NULL; +} diff --git a/src/task.h b/src/task.h new file mode 100644 index 00000000..155e8e41 --- /dev/null +++ b/src/task.h @@ -0,0 +1,23 @@ +#ifndef SRC_TASK_H_ +#define SRC_TASK_H_ + +#include + +#include "types/map.h" +#include "object/certificate.h" + +struct validation_task { + struct rpki_certificate *ca; + STAILQ_ENTRY(validation_task) lh; +}; + +void task_setup(void); +void task_start(void); +void task_stop(void); +void task_teardown(void); + +unsigned int task_enqueue(struct cache_mapping *, struct rpki_certificate *); +void task_wakeup(void); +struct validation_task *task_dequeue(struct validation_task *); + +#endif /* SRC_TASK_H_ */ diff --git a/src/thread_var.c b/src/thread_var.c index ab09259c..c5a817ee 100644 --- a/src/thread_var.c +++ b/src/thread_var.c @@ -5,7 +5,6 @@ #include "alloc.h" #include "log.h" -static pthread_key_t state_key; static pthread_key_t filenames_key; struct filename_stack { @@ -29,14 +28,6 @@ thvar_init(void) { int error; - error = pthread_key_create(&state_key, NULL); - if (error) { - pr_op_err( - "Fatal: Errcode %d while initializing the validation state thread variable.", - error); - return error; - } - /* * Hm. It's a little odd. * fnstack_discard() is not being called on program termination. @@ -54,29 +45,6 @@ thvar_init(void) return 0; } -/* Puts @state in the current thread's variable pool. Call once per thread. */ -int -state_store(struct validation *state) -{ - int error; - - error = pthread_setspecific(state_key, state); - if (error) - pr_op_err("pthread_setspecific() returned %d.", error); - - return error; -} - -/* - * Returns the current thread's validation state. Should not be used outside of - * validation threads. - */ -struct validation * -state_retrieve(void) -{ - return pthread_getspecific(state_key); -} - /** Initializes the current thread's fnstack. Call once per thread. */ void fnstack_init(void) @@ -188,47 +156,3 @@ fnstack_pop(void) files->len--; } - -static char const * -addr2str(int af, void const *addr, char *(*buffer_cb)(struct validation *)) -{ - struct validation *state = state_retrieve(); - return inet_ntop(af, addr, buffer_cb(state), INET6_ADDRSTRLEN); -} - -/* - * Returns @addr, converted to a printable string. Intended for minimal clutter - * address printing. - * - * The buffer the string is stored in was allocated in a thread variable, so it - * will be overridden the next time you call this function. Also, you should not - * free it. - * - * The buffer is the same as v6addr2str()'s, so don't mix them either. - */ -char const * -v4addr2str(struct in_addr const *addr) -{ - return addr2str(AF_INET, addr, validation_get_ip_buffer1); -} - -/* Same as v4addr2str(), except a different buffer is used. */ -char const * -v4addr2str2(struct in_addr const *addr) -{ - return addr2str(AF_INET, addr, validation_get_ip_buffer2); -} - -/* See v4addr2str(). */ -char const * -v6addr2str(struct in6_addr const *addr) -{ - return addr2str(AF_INET6, addr, validation_get_ip_buffer1); -} - -/* See v4addr2str2(). */ -char const * -v6addr2str2(struct in6_addr const *addr) -{ - return addr2str(AF_INET6, addr, validation_get_ip_buffer2); -} diff --git a/src/thread_var.h b/src/thread_var.h index 23bca5ae..42fb318c 100644 --- a/src/thread_var.h +++ b/src/thread_var.h @@ -1,14 +1,10 @@ #ifndef SRC_THREAD_VAR_H_ #define SRC_THREAD_VAR_H_ -#include "state.h" #include "types/map.h" int thvar_init(void); /* This function does not need cleanup. */ -int state_store(struct validation *); -struct validation *state_retrieve(void); - void fnstack_init(void); void fnstack_cleanup(void); @@ -17,10 +13,4 @@ void fnstack_push_map(struct cache_mapping const *); char const *fnstack_peek(void); void fnstack_pop(void); -/* Please remember that these functions can only be used during validations. */ -char const *v4addr2str(struct in_addr const *); -char const *v4addr2str2(struct in_addr const *); -char const *v6addr2str(struct in6_addr const *); -char const *v6addr2str2(struct in6_addr const *); - #endif /* SRC_THREAD_VAR_H_ */ diff --git a/src/types/address.c b/src/types/address.c index 911015fb..6843c653 100644 --- a/src/types/address.c +++ b/src/types/address.c @@ -159,6 +159,7 @@ prefix6_equals(struct ipv6_prefix const *a, struct ipv6_prefix const *b) int prefix4_decode(IPAddress_t const *str, struct ipv4_prefix *result) { + char buf[INET_ADDRSTRLEN]; int len; if (str->size > 4) { @@ -183,7 +184,7 @@ prefix4_decode(IPAddress_t const *str, struct ipv4_prefix *result) if ((result->addr.s_addr & be32_suffix_mask(result->len)) != 0) { return pr_val_err("IPv4 prefix '%s/%u' has enabled suffix bits.", - v4addr2str(&result->addr), result->len); + addr2str4(&result->addr, buf), result->len); } return 0; @@ -196,6 +197,7 @@ int prefix6_decode(IPAddress_t const *str, struct ipv6_prefix *result) { struct in6_addr suffix; + char buf[INET6_ADDRSTRLEN]; int len; if (str->size > 16) { @@ -222,7 +224,7 @@ prefix6_decode(IPAddress_t const *str, struct ipv6_prefix *result) ipv6_suffix_mask(result->len, &suffix); if (addr6_bitwise_and(&result->addr, &suffix)) { return pr_val_err("IPv6 prefix '%s/%u' has enabled suffix bits.", - v6addr2str(&result->addr), result->len); + addr2str6(&result->addr, buf), result->len); } return 0; @@ -231,9 +233,13 @@ prefix6_decode(IPAddress_t const *str, struct ipv6_prefix *result) static int check_order4(struct ipv4_range *result) { + char buf1[INET_ADDRSTRLEN]; + char buf2[INET_ADDRSTRLEN]; + if (ntohl(result->min.s_addr) > ntohl(result->max.s_addr)) { return pr_val_err("The IPv4 range '%s-%s' is inverted.", - v4addr2str(&result->min), v4addr2str2(&result->max)); + addr2str4(&result->min, buf1), + addr2str4(&result->max, buf2)); } return 0; @@ -247,6 +253,8 @@ check_order4(struct ipv4_range *result) static int check_encoding4(struct ipv4_range *range) { + char buf1[INET_ADDRSTRLEN]; + char buf2[INET_ADDRSTRLEN]; const uint32_t MIN = ntohl(range->min.s_addr); const uint32_t MAX = ntohl(range->max.s_addr); uint32_t mask; @@ -260,7 +268,7 @@ check_encoding4(struct ipv4_range *range) return 0; return pr_val_err("IPAddressRange '%s-%s' is a range, but should have been encoded as a prefix.", - v4addr2str(&range->min), v4addr2str2(&range->max)); + addr2str4(&range->min, buf1), addr2str4(&range->max, buf2)); } /** @@ -295,14 +303,16 @@ check_order6(struct ipv6_range *result) uint32_t min; uint32_t max; unsigned int quadrant; + char buf1[INET6_ADDRSTRLEN]; + char buf2[INET6_ADDRSTRLEN]; for (quadrant = 0; quadrant < 4; quadrant++) { min = addr6_get_quadrant(&result->min, quadrant); max = addr6_get_quadrant(&result->max, quadrant); if (min > max) { return pr_val_err("The IPv6 range '%s-%s' is inverted.", - v6addr2str(&result->min), - v6addr2str2(&result->max)); + addr2str6(&result->min, buf1), + addr2str6(&result->max, buf2)); } else if (min < max) { return 0; /* result->min < result->max */ } @@ -314,8 +324,11 @@ check_order6(struct ipv6_range *result) static int pr_bad_encoding(struct ipv6_range *range) { + char buf1[INET6_ADDRSTRLEN]; + char buf2[INET6_ADDRSTRLEN]; return pr_val_err("IPAddressRange %s-%s is a range, but should have been encoded as a prefix.", - v6addr2str(&range->min), v6addr2str2(&range->max)); + addr2str6(&range->min, buf1), + addr2str6(&range->max, buf2)); } static int diff --git a/src/validation_handler.c b/src/validation_handler.c index 9b02ac1e..34298ebb 100644 --- a/src/validation_handler.c +++ b/src/validation_handler.c @@ -1,58 +1,52 @@ #include "validation_handler.h" -#include "log.h" -#include "thread_var.h" - -/* - * Never returns NULL by contract. - */ -static struct validation_handler const * -get_current_threads_handler(void) -{ - struct validation_handler const *handler; +#include "rtr/db/db_table.h" - handler = validation_get_validation_handler(state_retrieve()); - if (handler == NULL) - pr_crit("This thread lacks a validation handler."); +static struct db_table *table; - return handler; +void +vhandle_init(void) +{ + table = db_table_create(); } -int -vhandler_handle_roa_v4(uint32_t as, struct ipv4_prefix const *prefix, - uint8_t max_length) +struct db_table * +vhandle_claim(void) { - struct validation_handler const *handler; - - handler = get_current_threads_handler(); - - return (handler->handle_roa_v4 != NULL) - ? handler->handle_roa_v4(as, prefix, max_length, handler->arg) - : 0; + struct db_table *result = table; + table = NULL; + return result; } int -vhandler_handle_roa_v6(uint32_t as, struct ipv6_prefix const *prefix, - uint8_t max_length) +vhandle_roa_v4(uint32_t as, struct ipv4_prefix const *pfx, uint8_t maxlen) { - struct validation_handler const *handler; - - handler = get_current_threads_handler(); - - return (handler->handle_roa_v6 != NULL) - ? handler->handle_roa_v6(as, prefix, max_length, handler->arg) - : 0; + return rtrhandler_handle_roa_v4(table, as, pfx, maxlen); } int -vhandler_handle_router_key(unsigned char const *ski, - struct asn_range const *asns, unsigned char const *spk) +vhandle_roa_v6(uint32_t as, struct ipv6_prefix const *pfx, uint8_t maxlen) { - struct validation_handler const *handler; - - handler = get_current_threads_handler(); + return rtrhandler_handle_roa_v6(table, as, pfx, maxlen); +} - return (handler->handle_router_key != NULL) - ? handler->handle_router_key(ski, asns, spk, handler->arg) - : 0; +int +vhandle_router_key(unsigned char const *ski, struct asn_range const *asns, + unsigned char const *spk) +{ + uint64_t asn; + int error; + + /* + * TODO (warning) Umm... this is begging for a limit. + * If the issuer gets it wrong, we can iterate up to 2^32 times. + * The RFCs don't seem to care about this. + */ + for (asn = asns->min; asn <= asns->max; asn++) { + error = rtrhandler_handle_router_key(table, ski, asn, spk); + if (error) + return error; + } + + return 0; } diff --git a/src/validation_handler.h b/src/validation_handler.h index 2e284c1f..d06ae147 100644 --- a/src/validation_handler.h +++ b/src/validation_handler.h @@ -3,40 +3,15 @@ #include "rtr/db/vrps.h" -/** - * Functions that handle validation results. - * - * At some point, I believe we will end up separating the validator code into a - * library, so it can be used by other applications aside from Fort's RTR - * server. - * - * This structure is designed with that in mind; it's the callback collection - * that the library's user application will fill up, so it can do whatever it - * wants with the validated ROAs. - * - * Because it's intended to be used by arbitrary applications, it needs to be - * generic. Please refrain from adding callbacks that are specifically meant for - * a particular use case. - * - * All of these functions can be NULL. - */ -struct validation_handler { - /** Called every time Fort has successfully validated an IPv4 ROA. */ - int (*handle_roa_v4)(uint32_t, struct ipv4_prefix const *, uint8_t, - void *); - /** Called every time Fort has successfully validated an IPv6 ROA. */ - int (*handle_roa_v6)(uint32_t, struct ipv6_prefix const *, uint8_t, - void *); - /** Called every time Fort has successfully validated a BGPsec cert */ - int (*handle_router_key)(unsigned char const *, - struct asn_range const *, unsigned char const *, void *); - /** Generic user-defined argument for the functions above. */ - void *arg; -}; +void vhandle_init(void); +struct db_table *vhandle_claim(void); -int vhandler_handle_roa_v4(uint32_t, struct ipv4_prefix const *, uint8_t); -int vhandler_handle_roa_v6(uint32_t, struct ipv6_prefix const *, uint8_t); -int vhandler_handle_router_key(unsigned char const *, struct asn_range const *, +/* Called every time Fort has successfully validated an IPv4 ROA. */ +int vhandle_roa_v4(uint32_t, struct ipv4_prefix const *, uint8_t); +/* Called every time Fort has successfully validated an IPv6 ROA. */ +int vhandle_roa_v6(uint32_t, struct ipv6_prefix const *, uint8_t); +/* Called every time Fort has successfully validated a BGPsec cert. */ +int handle_router_key(unsigned char const *, struct asn_range const *, unsigned char const *); #endif /* SRC_VALIDATION_HANDLER_H_ */ diff --git a/test/Makefile.am b/test/Makefile.am index 11a8e446..85f9867c 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -36,6 +36,7 @@ check_PROGRAMS += rrdp_update.test check_PROGRAMS += rsync.test check_PROGRAMS += serial.test check_PROGRAMS += tal.test +check_PROGRAMS += task.test check_PROGRAMS += thread_pool.test check_PROGRAMS += url.test check_PROGRAMS += uthash.test @@ -91,6 +92,9 @@ serial_test_LDADD = ${MY_LDADD} tal_test_SOURCES = object/tal_test.c tal_test_LDADD = ${MY_LDADD} +task_test_SOURCES = task_test.c +task_test_LDADD = ${MY_LDADD} + thread_pool_test_SOURCES = thread_pool_test.c thread_pool_test_LDADD = ${MY_LDADD} diff --git a/test/mock.c b/test/mock.c index 89a76fbd..25b5d97c 100644 --- a/test/mock.c +++ b/test/mock.c @@ -5,7 +5,6 @@ #include "config.h" #include "incidence.h" #include "log.h" -#include "state.h" #include "thread_var.h" /* Some core functions, as linked from unit tests. */ diff --git a/test/object/tal_test.c b/test/object/tal_test.c index 1f0c4feb..a8388915 100644 --- a/test/object/tal_test.c +++ b/test/object/tal_test.c @@ -25,11 +25,6 @@ MOCK_ABORT_INT(handle_roa_v6, uint32_t as, struct ipv6_prefix const *prefix, uint8_t max_length, void *arg) MOCK_ABORT_INT(handle_router_key, unsigned char const *ski, struct asn_range const *asns, unsigned char const *spk, void *arg) -MOCK(state_retrieve, struct validation *, NULL, void) -MOCK_ABORT_VOID(validation_destroy, struct validation *state) -MOCK_ABORT_INT(validation_prepare, struct validation **out, struct tal *tal, - struct validation_handler *validation_handler) -MOCK(validation_tal, struct tal *, NULL, struct validation *state) /* Tests */ diff --git a/test/rtr/db/rtr_db_mock.c b/test/rtr/db/rtr_db_mock.c index 491f5956..86e15db6 100644 --- a/test/rtr/db/rtr_db_mock.c +++ b/test/rtr/db/rtr_db_mock.c @@ -1,7 +1,8 @@ #include -#include "object/tal.h" +#include "object/tal.h" #include "types/address.c" +#include "types/asn.h" static unsigned char db_imp_ski[] = { 0x0e, 0xe9, 0x6a, 0x8e, 0x2f, 0xac, 0x50, 0xce, 0x6c, 0x5f, @@ -21,57 +22,35 @@ static unsigned char db_imp_spk[] = { static int serial = 1; +static struct db_table *test_table; + static void -add_v4(struct validation_handler *handler, uint32_t as) +add_v4(struct db_table *table, uint32_t as) { struct ipv4_prefix prefix; prefix.addr.s_addr = htonl(0xC0000200); prefix.len = 24; - ck_assert_int_eq(0, handler->handle_roa_v4(as, &prefix, 32, - handler->arg)); + ck_assert_int_eq(0, rtrhandler_handle_roa_v4(table, as, &prefix, 32)); } static void -add_v6(struct validation_handler *handler, uint32_t as) +add_v6(struct db_table *table, uint32_t as) { struct ipv6_prefix prefix; in6_addr_init(&prefix.addr, 0x20010DB8u, 0, 0, 0); prefix.len = 96; - ck_assert_int_eq(0, handler->handle_roa_v6(as, &prefix, 120, - handler->arg)); -} - -static void -add_rk(struct validation_handler *handler, uint32_t as) -{ - struct asn_range range = { .min = as, .max = as }; - ck_assert_int_eq(0, handler->handle_router_key(db_imp_ski, &range, - db_imp_spk, handler->arg)); -} - -static int -__handle_roa_v4(uint32_t as, struct ipv4_prefix const *prefix, - uint8_t max_length, void *arg) -{ - return rtrhandler_handle_roa_v4(arg, as, prefix, max_length); -} - -static int -__handle_roa_v6(uint32_t as, struct ipv6_prefix const *prefix, - uint8_t max_length, void *arg) -{ - return rtrhandler_handle_roa_v6(arg, as, prefix, max_length); + ck_assert_int_eq(0, rtrhandler_handle_roa_v6(table, as, &prefix, 120)); } static int -__handle_router_key(unsigned char const *ski, struct asn_range const *range, - unsigned char const *spk, void *arg) +__handle_router_key(struct db_table *table, struct asn_range const *range) { uint64_t as; int error; for (as = range->min; as <= range->max; as++) { - error = rtrhandler_handle_router_key(arg, ski, as, spk); + error = rtrhandler_handle_router_key(table, db_imp_ski, as, + db_imp_spk); if (error) return error; } @@ -79,39 +58,45 @@ __handle_router_key(unsigned char const *ski, struct asn_range const *range, return 0; } -struct db_table * -perform_standalone_validation(void) +static void +add_rk(struct db_table *table, uint32_t as) { - struct validation_handler handler; + struct asn_range range = { .min = as, .max = as }; + ck_assert_int_eq(0, __handle_router_key(table, &range)); +} - handler.handle_roa_v4 = __handle_roa_v4; - handler.handle_roa_v6 = __handle_roa_v6; - handler.handle_router_key = __handle_router_key; - handler.arg = db_table_create(); +void +vhandle_init(void) +{ + test_table = db_table_create(); +} +int +perform_standalone_validation(void) +{ switch (serial) { case 1: - add_v4(&handler, 0); - add_v6(&handler, 0); - add_rk(&handler, 0); + add_v4(test_table, 0); + add_v6(test_table, 0); + add_rk(test_table, 0); break; case 2: - add_v4(&handler, 0); - add_v6(&handler, 0); - add_rk(&handler, 0); - add_v4(&handler, 1); - add_v6(&handler, 1); - add_rk(&handler, 1); + add_v4(test_table, 0); + add_v6(test_table, 0); + add_rk(test_table, 0); + add_v4(test_table, 1); + add_v6(test_table, 1); + add_rk(test_table, 1); break; case 3: - add_v4(&handler, 1); - add_v6(&handler, 1); - add_rk(&handler, 1); + add_v4(test_table, 1); + add_v6(test_table, 1); + add_rk(test_table, 1); break; case 4: - add_v4(&handler, 0); - add_v6(&handler, 0); - add_rk(&handler, 0); + add_v4(test_table, 0); + add_v6(test_table, 0); + add_rk(test_table, 0); break; default: ck_abort_msg("perform_standalone_validation() was called too many times (%d).", @@ -119,5 +104,13 @@ perform_standalone_validation(void) } serial++; - return handler.arg; + return 0; +} + +struct db_table * +vhandle_claim(void) +{ + struct db_table *tmp = test_table; + test_table = NULL; + return tmp; } diff --git a/test/task_test.c b/test/task_test.c new file mode 100644 index 00000000..fe322e93 --- /dev/null +++ b/test/task_test.c @@ -0,0 +1,164 @@ +#include "task.c" + +#include + +#include "alloc.c" +#include "common.c" +#include "mock.c" +#include "types/map.c" +#include "types/path.c" + +void +rpki_certificate_free(struct rpki_certificate *cert) +{ + if (atomic_fetch_sub(&cert->refcount, 1) == 1) { + map_cleanup(&cert->map); + free(cert); + } +} + +static void +queue_1(char *mapstr) +{ + struct cache_mapping map; + struct rpki_certificate parent = { 0 }; + + map.url = map.path = mapstr; + ck_assert_int_eq(1, task_enqueue(&map, &parent)); +} + +static struct validation_task * +dequeue_1(char *mapstr, struct validation_task *prev) +{ + struct validation_task *task; + task = task_dequeue(prev); + ck_assert_str_eq(mapstr, task->ca->map.url); + return task; +} + +static void +check_empty(struct validation_task *prev) +{ + ck_assert_ptr_eq(NULL, task_dequeue(prev)); +} + +static void +test_1(char *mapstr) +{ + queue_1(mapstr); + check_empty(dequeue_1(mapstr, NULL)); +} + +START_TEST(test_queue_empty) +{ + task_setup(); + + task_start(); + ck_assert_ptr_eq(NULL, task_dequeue(NULL)); + ck_assert_ptr_eq(NULL, task_dequeue(NULL)); + task_stop(); + + task_teardown(); +} +END_TEST + +START_TEST(test_queue_1) +{ + task_setup(); + task_start(); test_1("a"); task_stop(); + task_teardown(); +} +END_TEST + +START_TEST(test_queue_3) +{ + struct validation_task *prev; + + task_setup(); + task_start(); + + queue_1("b"); + queue_1("c"); + queue_1("d"); + + prev = dequeue_1("b", NULL); + prev = dequeue_1("c", prev); + prev = dequeue_1("d", prev); + check_empty(prev); + + task_stop(); + task_teardown(); +} +END_TEST + +START_TEST(test_queue_multiple) +{ + task_setup(); + + task_start(); test_1("a"); task_stop(); + task_start(); test_1("b"); task_stop(); + task_start(); test_1("c"); task_stop(); + + task_teardown(); +} +END_TEST + +START_TEST(test_queue_interrupted) +{ + struct validation_task *prev; + + task_setup(); + + task_start(); + queue_1("1"); + queue_1("2"); + task_stop(); + + check_empty(NULL); + check_empty(NULL); + + task_start(); + check_empty(NULL); + queue_1("3"); + queue_1("4"); + prev = dequeue_1("3", NULL); + task_stop(); + + check_empty(prev); + + task_teardown(); +} +END_TEST + +static Suite *create_suite(void) +{ + Suite *suite; + TCase *queue; + + queue = tcase_create("queue"); + tcase_add_test(queue, test_queue_empty); + tcase_add_test(queue, test_queue_1); + tcase_add_test(queue, test_queue_3); + tcase_add_test(queue, test_queue_multiple); + tcase_add_test(queue, test_queue_interrupted); + + suite = suite_create("task"); + suite_add_tcase(suite, queue); + return suite; +} + +int main(void) +{ + Suite *suite; + SRunner *runner; + int tests_failed; + + suite = create_suite(); + + runner = srunner_create(suite); + srunner_run_all(runner, CK_NORMAL); + tests_failed = srunner_ntests_failed(runner); + srunner_free(runner); + + return (tests_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; +}