From: Alberto Leiva Popper Date: Wed, 13 Nov 2024 22:25:19 +0000 (-0600) Subject: Separate rsync fallbacks from RRDP fallbacks X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=c72177ada4b8ecdad26583fba0a04f3246c0d440;p=thirdparty%2FFORT-validator.git Separate rsync fallbacks from RRDP fallbacks Both used to be indexed by caRepository, inducing possible collision. RRDP fallbacks are now indexed by rkiNotify+caRepository, ensuring they're caged separately. --- diff --git a/src/cache.c b/src/cache.c index 509aff37..5ee086b3 100644 --- a/src/cache.c +++ b/src/cache.c @@ -79,12 +79,17 @@ struct cache_table { static struct rpki_cache { /* Latest view of the remote rsync modules */ + /* rsync modules (repositories); indexed by plain rsync URL */ struct cache_table rsync; /* Latest view of the remote HTTPS TAs */ + /* HTTPS files; indexed by plain HTTPS URL */ struct cache_table https; /* Latest view of the remote RRDP cages */ + /* RRDP modules (repositories); indexed by rpkiNotify */ struct cache_table rrdp; - /* Committed RPPs and TAs (offline fallback hard links) */ + + /* Committed (offline fallback hard links) RPPs and TAs */ + /* RPPs indexed by [rpkiNotif] + caRepo; TAs indexed by plain URL. */ struct cache_table fallback; } cache; @@ -94,9 +99,11 @@ static volatile sig_atomic_t lockfile_owned; struct cache_cage { struct cache_node const *refresh; struct cache_node const *fallback; + char const *rpkiNotify; }; struct cache_commit { + char *rpkiNotify; char *caRepository; struct cache_mapping *files; size_t nfiles; @@ -611,8 +618,7 @@ dl_rrdp(struct cache_node *notif) mtim = time_nonfatal(); - error = rrdp_update(¬if->map, notif->mtim, &changed, &cache.rrdp.seq, - ¬if->rrdp); + error = rrdp_update(¬if->map, notif->mtim, &changed, ¬if->rrdp); if (error) return error; @@ -744,16 +750,55 @@ do_refresh(struct cache_table *tbl, char const *uri, struct cache_node **result) return 0; } +static char * +get_rrdp_fallback_key(char const *context, char const *caRepository) +{ + char *key; + size_t keylen; + int written; + + keylen = strlen(context) + strlen(caRepository) + 2; + key = pmalloc(keylen); + + written = snprintf(key, keylen, "%s\t%s", context, caRepository); + if (written != keylen - 1) + pr_crit("find_rrdp_fallback_node: %zu %d %s %s", + keylen, written, context, caRepository); + + return key; +} + static struct cache_node * -get_fallback(char const *caRepository) +find_rrdp_fallback_node(struct sia_uris *sias) { - struct cache_node *node; + char *key; + struct cache_node *result; + + if (!sias->rpkiNotify || !sias->caRepository) + return NULL; - pr_val_debug("Retrieving %s fallback...", caRepository); - node = find_node(&cache.fallback, caRepository, strlen(caRepository)); - pr_val_debug(node ? "Fallback found." : "Fallback unavailable."); + key = get_rrdp_fallback_key(sias->rpkiNotify, sias->caRepository); + result = find_node(&cache.fallback, key, strlen(key)); + free(key); - return node; + return result; +} + +static struct cache_node * +get_fallback(struct sia_uris *sias) +{ + struct cache_node *rrdp; + struct cache_node *rsync; + + rrdp = find_rrdp_fallback_node(sias); + rsync = find_node(&cache.fallback, sias->caRepository, + strlen(sias->caRepository)); + + if (rrdp == NULL) + return rsync; + if (rsync == NULL) + return rrdp; + return (difftime(rsync->mtim, rrdp->mtim) > 0) ? rsync : rrdp; } /* Do not free nor modify the result. */ @@ -773,7 +818,10 @@ cache_refresh_by_url(char const *url) return node ? node->map.path : NULL; } -/* Do not free nor modify the result. */ +/* + * HTTPS (TAs) and rsync only; don't use this for RRDP. + * Do not free nor modify the result. + */ char * cache_get_fallback(char const *url) { @@ -807,6 +855,7 @@ cache_refresh_by_sias(struct sia_uris *sias, struct cache_cage **result) { struct cache_node *node; struct cache_cage *cage; + char const *rpkiNotify; // XXX Make sure somewhere validates rpkiManifest matches caRepository. // XXX review result signs @@ -816,6 +865,7 @@ cache_refresh_by_sias(struct sia_uris *sias, struct cache_cage **result) if (sias->rpkiNotify) { switch (do_refresh(&cache.rrdp, sias->rpkiNotify, &node)) { case 0: + rpkiNotify = sias->rpkiNotify; goto refresh_success; case EBUSY: return EBUSY; @@ -825,13 +875,14 @@ cache_refresh_by_sias(struct sia_uris *sias, struct cache_cage **result) /* Try rsync + optional fallback */ switch (do_refresh(&cache.rsync, sias->caRepository, &node)) { case 0: + rpkiNotify = NULL; goto refresh_success; case EBUSY: return EBUSY; } /* Try fallback only */ - node = get_fallback(sias->caRepository); /* XXX (test) does this catch notifies? */ + node = get_fallback(sias); if (!node) return EINVAL; /* Nothing to work with */ @@ -842,8 +893,9 @@ cache_refresh_by_sias(struct sia_uris *sias, struct cache_cage **result) refresh_success: *result = cage = pmalloc(sizeof(struct cache_cage)); + cage->rpkiNotify = rpkiNotify; cage->refresh = node; - cage->fallback = get_fallback(sias->caRepository); + cage->fallback = get_fallback(sias); return 0; } @@ -907,12 +959,13 @@ cage_disable_refresh(struct cache_cage *cage) * modified nor deleted until the cache cleanup. */ void -cache_commit_rpp(char const *caRepository, struct rpp *rpp) +cache_commit_rpp(char const *rpkiNotify, char const *caRepository, + struct rpp *rpp) { struct cache_commit *commit; commit = pmalloc(sizeof(struct cache_commit)); - // XXX missing context + commit->rpkiNotify = rpkiNotify ? pstrdup(rpkiNotify) : NULL; commit->caRepository = pstrdup(caRepository); commit->files = rpp->files; commit->nfiles = rpp->nfiles; @@ -931,7 +984,7 @@ cache_commit_file(struct cache_mapping *map) struct cache_commit *commit; commit = pmalloc(sizeof(struct cache_commit)); - // XXX missing context + commit->rpkiNotify = NULL; commit->caRepository = NULL; commit->files = pmalloc(sizeof(*map)); commit->files[0].url = pstrdup(map->url); @@ -943,6 +996,12 @@ cache_commit_file(struct cache_mapping *map) mutex_unlock(&commits_lock); } +char const * +cage_rpkiNotify(struct cache_cage *cage) +{ + return cage->rpkiNotify; +} + static void cachent_print(struct cache_node *node) { @@ -1170,15 +1229,27 @@ commit_fallbacks(void) { struct cache_commit *commit; struct cache_node *fb, *tmp; + time_t now; array_index i; int error; + now = time_fatal(); + while (!STAILQ_EMPTY(&commits)) { commit = STAILQ_FIRST(&commits); STAILQ_REMOVE_HEAD(&commits, lh); if (commit->caRepository) { - fb = provide_node(&cache.fallback, commit->caRepository); + if (commit->rpkiNotify) { + char *key; + key = get_rrdp_fallback_key(commit->rpkiNotify, + commit->caRepository); + fb = provide_node(&cache.fallback, key); + free(key); + } else { + fb = provide_node(&cache.fallback, + commit->caRepository); + } if (file_mkdir(fb->map.path, true) != 0) goto skip; @@ -1201,7 +1272,9 @@ commit_fallbacks(void) } freshen: fb->state = DLS_FRESH; -skip: free(commit->caRepository); + fb->mtim = now; +skip: free(commit->rpkiNotify); + free(commit->caRepository); for (i = 0; i < commit->nfiles; i++) { free(commit->files[i].url); free(commit->files[i].path); diff --git a/src/cache.h b/src/cache.h index c791346a..552b95b5 100644 --- a/src/cache.h +++ b/src/cache.h @@ -44,9 +44,11 @@ struct cache_cage; int cache_refresh_by_sias(struct sia_uris *, struct cache_cage **); char const *cage_map_file(struct cache_cage *, char const *); bool cage_disable_refresh(struct cache_cage *); -void cache_commit_rpp(char const *, struct rpp *); +void cache_commit_rpp(char const *, char const *, struct rpp *); void cache_commit_file(struct cache_mapping *); +char const *cage_rpkiNotify(struct cache_cage *); + void cache_print(void); /* Dump cache in stdout */ #endif /* SRC_CACHE_LOCAL_CACHE_H_ */ diff --git a/src/object/certificate.c b/src/object/certificate.c index 99fd1285..d1d28661 100644 --- a/src/object/certificate.c +++ b/src/object/certificate.c @@ -1950,7 +1950,7 @@ retry: mft = cage_map_file(cage, ca->sias.rpkiManifest); if (queued > 0) task_wakeup(); - cache_commit_rpp(ca->sias.caRepository, &ca->rpp); + cache_commit_rpp(cage_rpkiNotify(cage), ca->sias.caRepository, &ca->rpp); end: free(cage); return error; diff --git a/src/rrdp.c b/src/rrdp.c index fee1de1b..d3126256 100644 --- a/src/rrdp.c +++ b/src/rrdp.c @@ -1174,7 +1174,7 @@ dl_notif(struct cache_mapping const *map, time_t mtim, bool *changed, */ int rrdp_update(struct cache_mapping const *notif, time_t mtim, bool *changed, - struct cache_sequence *rrdp_seq, struct rrdp_state **state) + struct rrdp_state **state) { struct rrdp_state *old; struct update_notification new; @@ -1194,20 +1194,14 @@ rrdp_update(struct cache_mapping const *notif, time_t mtim, bool *changed, new.session.serial.str); if ((*state) == NULL) { - char *cage; - pr_val_debug("This is a new Notification."); - cage = cseq_next(rrdp_seq); - if (!cage) - goto clean_notif; - old = pzalloc(sizeof(struct rrdp_state)); /* session postponed! */ - cseq_init(&old->seq, cage, true); + cseq_init(&old->seq, pstrdup(notif->path), true); STAILQ_INIT(&old->delta_hashes); - error = file_mkdir(cage, false); + error = file_mkdir(notif->path, false); if (error) { rrdp_state_free(old); goto clean_notif; diff --git a/src/rrdp.h b/src/rrdp.h index 4d7126fc..311b336b 100644 --- a/src/rrdp.h +++ b/src/rrdp.h @@ -11,7 +11,7 @@ struct rrdp_state; int rrdp_update(struct cache_mapping const *, time_t, bool *, - struct cache_sequence *, struct rrdp_state **); + struct rrdp_state **); char const *rrdp_file(struct rrdp_state const *, char const *); char const *rrdp_create_fallback(char *, struct rrdp_state **, char const *); diff --git a/test/cache_test.c b/test/cache_test.c index 6eef59e4..2fc455d4 100644 --- a/test/cache_test.c +++ b/test/cache_test.c @@ -38,9 +38,12 @@ rsync_download(char const *url, char const *path) { rsync_counter++; - if (dl_error) + if (dl_error) { + printf("Simulating failed rsync.\n"); return dl_error; + } + printf("Simulating rsync: %s -> %s\n", url, path); ck_assert_int_eq(0, mkdir(path, CACHE_FILEMODE)); touch_file(path); @@ -57,8 +60,8 @@ setup_test(void) { dl_error = 0; init_tables(); - ck_assert_int_eq(0, system("rm -rf rsync/ https/ rrdp/ fallback/")); - ck_assert_int_eq(0, system("mkdir rsync/ https/ rrdp/ fallback/")); + ck_assert_int_eq(0, system("rm -rf rsync/ https/ rrdp/ fallback/ tmp/")); + ck_assert_int_eq(0, system("mkdir rsync/ https/ rrdp/ fallback/ tmp/")); } static struct cache_cage * @@ -134,7 +137,8 @@ print_tree(void) } static void -queue_commit(char const *caRepository, char const *path1, char const *path2) +queue_commit(char const *rpkiNotify, char const *caRepository, + char const *path1, char const *path2) { struct rpp rpp = { 0 }; @@ -145,7 +149,7 @@ queue_commit(char const *caRepository, char const *path1, char const *path2) rpp.files[1].url = path_join(caRepository, "cert.cer"); rpp.files[1].path = pstrdup(path2); - cache_commit_rpp(caRepository, &rpp); + cache_commit_rpp(rpkiNotify, caRepository, &rpp); } /* Only validates the first character of the file. */ @@ -464,9 +468,9 @@ START_TEST(test_rsync_commit) } /* Commit 3: Empty -> Populated */ - queue_commit("rsync://domain/mod/rpp0", "rsync/0/0", "rsync/0/1"); - queue_commit("rsync://domain/mod/rpp2", "rsync/2/0", "rsync/2/1"); - queue_commit("rsync://domain/mod/rpp3", "rsync/3/0", "rsync/3/2"); + queue_commit(NULL, "rsync://domain/mod/rpp0", "rsync/0/0", "rsync/0/1"); + queue_commit(NULL, "rsync://domain/mod/rpp2", "rsync/2/0", "rsync/2/1"); + queue_commit(NULL, "rsync://domain/mod/rpp3", "rsync/3/0", "rsync/3/2"); commit_fallbacks(); ck_filesystem("fallback", /* RPP0 */ "fallback/0/0", "A", "fallback/0/1", "B", @@ -478,9 +482,9 @@ START_TEST(test_rsync_commit) /* Commit 4: Populated -> Populated */ /* XXX check the refresh does, in fact, only return fallbacks when the RPP doesn't change */ - queue_commit("rsync://domain/mod/rpp0", "fallback/0/0", "fallback/0/1"); - queue_commit("rsync://domain/mod/rpp1", "rsync/1/0", "rsync/1/1"); - queue_commit("rsync://domain/mod/rpp3", "fallback/2/0", "rsync/3/1"); + queue_commit(NULL, "rsync://domain/mod/rpp0", "fallback/0/0", "fallback/0/1"); + queue_commit(NULL, "rsync://domain/mod/rpp1", "rsync/1/0", "rsync/1/1"); + queue_commit(NULL, "rsync://domain/mod/rpp3", "fallback/2/0", "rsync/3/1"); commit_fallbacks(); ck_filesystem("fallback", @@ -612,6 +616,7 @@ END_TEST /* See comments at test_rsync_commit(). */ START_TEST(test_rrdp_commit) { + char const *notif = "https://domain/rpki/notif.xml"; unsigned int i; setup_test(); @@ -637,9 +642,9 @@ START_TEST(test_rrdp_commit) } /* 3 */ - queue_commit("rsync://domain/mod/rpp0", "rrdp/0/0", "rrdp/0/1"); - queue_commit("rsync://domain/mod/rpp2", "rrdp/2/0", "rrdp/2/1"); - queue_commit("rsync://domain/mod/rpp3", "rrdp/3/0", "rrdp/3/2"); + queue_commit(notif, "rsync://domain/mod/rpp0", "rrdp/0/0", "rrdp/0/1"); + queue_commit(notif, "rsync://domain/mod/rpp2", "rrdp/2/0", "rrdp/2/1"); + queue_commit(notif, "rsync://domain/mod/rpp3", "rrdp/3/0", "rrdp/3/2"); commit_fallbacks(); ck_filesystem("fallback", "fallback/0/0", "A", "fallback/0/1", "B", @@ -650,9 +655,9 @@ START_TEST(test_rrdp_commit) new_iteration(false); /* 4 */ - queue_commit("rsync://domain/mod/rpp0", "fallback/0/0", "fallback/0/1"); - queue_commit("rsync://domain/mod/rpp1", "rrdp/1/0", "rrdp/1/1"); - queue_commit("rsync://domain/mod/rpp3", "fallback/2/0", "rrdp/3/1"); + queue_commit(notif, "rsync://domain/mod/rpp0", "fallback/0/0", "fallback/0/1"); + queue_commit(notif, "rsync://domain/mod/rpp1", "rrdp/1/0", "rrdp/1/1"); + queue_commit(notif, "rsync://domain/mod/rpp3", "fallback/2/0", "rrdp/3/1"); commit_fallbacks(); ck_filesystem("fallback", "fallback/0/0", "A", "fallback/0/1", "B", @@ -670,12 +675,90 @@ START_TEST(test_rrdp_commit) } END_TEST +START_TEST(test_context) +{ + char *RPKI_NOTIFY = "https://a.b.c/notif.xml"; + char *CA_REPOSITORY = "rsync://x.y.z/mod5/rpp3"; + char *FILE_URL = "rsync://x.y.z/mod5/rpp3/a.cer"; + char *FILE_RRDP_PATH = "rrdp/0/0"; + char *FILE_RSYNC_PATH = "rsync/0/rpp3/a.cer"; + + struct sia_uris sias = { 0 }; + struct cache_cage *cage; + struct rpp rpp = { 0 }; + + ck_assert_int_eq(0, hash_setup()); + ck_assert_int_eq(0, relax_ng_init()); + setup_test(); + + dls[0] = NHDR("3") + NSS("https://a.b.c/3/snapshot.xml", + "25b49ae65eeeda44222d599959086911c65ed4277021cdec456d80a6604b83c9") + NTAIL; + dls[1] = SHDR("3") PBLSH("rsync://x.y.z/mod5/rpp3/a.cer", "Rm9ydAo=") STAIL; + dls[2] = NULL; + + /* 1. 1st CA succeeds on RRDP */ + sias.rpkiNotify = RPKI_NOTIFY; + sias.caRepository = CA_REPOSITORY; + ck_assert_int_eq(0, cache_refresh_by_sias(&sias, &cage)); + ck_assert_str_eq(RPKI_NOTIFY, cage->rpkiNotify); + ck_assert_str_eq(FILE_RRDP_PATH, cage_map_file(cage, FILE_URL)); + ck_assert_int_eq(false, cage_disable_refresh(cage)); + ck_assert_ptr_eq(NULL, cage_map_file(cage, FILE_URL)); + + /* + * 2. 2nd CA points to the same caRepository, + * but does not provide RRDP as an option. + */ + sias.rpkiNotify = NULL; + ck_assert_int_eq(0, cache_refresh_by_sias(&sias, &cage)); + ck_assert_ptr_eq(NULL, cage->rpkiNotify); + ck_assert_str_eq(FILE_RSYNC_PATH, cage_map_file(cage, FILE_URL)); + ck_assert_int_eq(false, cage_disable_refresh(cage)); + ck_assert_ptr_eq(NULL, cage_map_file(cage, FILE_URL)); + + /* 3. Commit */ + rpp.nfiles = 1; + rpp.files = pzalloc(sizeof(struct cache_mapping)); + rpp.files->url = pstrdup(FILE_URL); + rpp.files->path = pstrdup(FILE_RRDP_PATH); + cache_commit_rpp(RPKI_NOTIFY, CA_REPOSITORY, &rpp); + + rpp.nfiles = 1; + rpp.files = pzalloc(sizeof(struct cache_mapping)); + rpp.files->url = pstrdup(FILE_URL); + rpp.files->path = pstrdup(FILE_RSYNC_PATH); + cache_commit_rpp(NULL, CA_REPOSITORY, &rpp); + + commit_fallbacks(); + + /* 4. Redo both CAs, check the fallbacks too */ + ck_assert_int_eq(0, cache_refresh_by_sias(&sias, &cage)); + ck_assert_ptr_eq(NULL, cage->rpkiNotify); + ck_assert_str_eq(FILE_RSYNC_PATH, cage_map_file(cage, FILE_URL)); + ck_assert_int_eq(true, cage_disable_refresh(cage)); + ck_assert_str_eq("fallback/1/0", cage_map_file(cage, FILE_URL)); + + sias.rpkiNotify = RPKI_NOTIFY; + ck_assert_int_eq(0, cache_refresh_by_sias(&sias, &cage)); + ck_assert_str_eq(RPKI_NOTIFY, cage->rpkiNotify); + ck_assert_str_eq(FILE_RRDP_PATH, cage_map_file(cage, FILE_URL)); + ck_assert_int_eq(true, cage_disable_refresh(cage)); + ck_assert_str_eq("fallback/0/0", cage_map_file(cage, FILE_URL)); + + cleanup_test(); + relax_ng_cleanup(); + hash_teardown(); +} +END_TEST + /* Boilerplate */ static Suite *create_suite(void) { Suite *suite; - TCase *rsync, *https, *rrdp; + TCase *rsync, *https, *rrdp, *multi; rsync = tcase_create("rsync"); tcase_add_test(rsync, test_cache_download_rsync); @@ -690,10 +773,14 @@ static Suite *create_suite(void) rrdp = tcase_create("rrdp"); tcase_add_test(rrdp, test_rrdp_commit); + multi = tcase_create("multi-protocol"); + tcase_add_test(multi, test_context); + suite = suite_create("local-cache"); suite_add_tcase(suite, rsync); suite_add_tcase(suite, https); suite_add_tcase(suite, rrdp); + suite_add_tcase(suite, multi); return suite; } diff --git a/test/rrdp_update_test.c b/test/rrdp_update_test.c index d4637712..5f346895 100644 --- a/test/rrdp_update_test.c +++ b/test/rrdp_update_test.c @@ -21,8 +21,8 @@ static void setup_test(void) { - ck_assert_int_eq(0, system("rm -rf tmp/")); - ck_assert_int_eq(0, system("mkdir -p tmp/https tmp/rrdp tmp/tmp")); + ck_assert_int_eq(0, system("rm -rf https/ rrdp/ tmp/")); + ck_assert_int_eq(0, system("mkdir https/ rrdp/ tmp/")); ck_assert_int_eq(0, hash_setup()); ck_assert_int_eq(0, relax_ng_init()); } @@ -84,10 +84,10 @@ START_TEST(startup) setup_test(); notif.url = "https://host/notification.xml"; - notif.path = "tmp/https/0"; + notif.path = "rrdp/0"; - seq.prefix = "tmp/rrdp"; - seq.next_id = 0; + seq.prefix = "rrdp"; + seq.next_id = 1; seq.pathlen = strlen(seq.prefix); seq.free_prefix = false; @@ -99,29 +99,30 @@ START_TEST(startup) dls[2] = NULL; https_counter = 0; - ck_assert_int_eq(0, rrdp_update(¬if, 0, &changed, &seq, &state)); + ck_assert_int_eq(0, rrdp_update(¬if, 0, &changed, &state)); ck_assert_uint_eq(2, https_counter); ck_assert_uint_eq(true, changed); - ck_file("tmp/rrdp/0/0"); /* "tmp/rrdp//" */ + ck_file("rrdp/0/0"); /* "rrdp//" */ maps[0].url = "rsync://a/b/c.cer"; - maps[0].path = "tmp/rrdp/0/0"; + maps[0].path = "rrdp/0/0"; maps[1].url = NULL; ck_state(TEST_SESSION, "3", 1, maps, state); /* Attempt to update, server hasn't changed anything. */ dls[1] = NULL; /* Snapshot should not redownload */ https_counter = 0; - ck_assert_int_eq(0, rrdp_update(¬if, 0, &changed, &seq, &state)); + ck_assert_int_eq(0, rrdp_update(¬if, 0, &changed, &state)); ck_assert_uint_eq(1, https_counter); ck_assert_uint_eq(false, changed); - ck_file("tmp/rrdp/0/0"); + ck_file("rrdp/0/0"); ck_state(TEST_SESSION, "3", 1, maps, state); rrdp_state_free(state); - cleanup_test(); // XXX Missing a looooooooooooooooooot of tests + + cleanup_test(); } END_TEST @@ -145,6 +146,15 @@ int main(void) SRunner *runner; int tests_failed; + if (mkdir("tmp", CACHE_FILEMODE) < 0 && errno != EEXIST) { + fprintf(stderr, "mkdir('tmp/'): %s\n", strerror(errno)); + return 1; + } + if (chdir("tmp") < 0) { + fprintf(stderr, "chdir('tmp/'): %s\n", strerror(errno)); + return 1; + } + suite = create_suite(); runner = srunner_create(suite);