Both used to be indexed by caRepository, inducing possible collision.
RRDP fallbacks are now indexed by rkiNotify+caRepository, ensuring
they're caged separately.
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;
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;
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;
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. */
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)
{
{
struct cache_node *node;
struct cache_cage *cage;
+ char const *rpkiNotify;
// XXX Make sure somewhere validates rpkiManifest matches caRepository.
// XXX review result signs
if (sias->rpkiNotify) {
switch (do_refresh(&cache.rrdp, sias->rpkiNotify, &node)) {
case 0:
+ rpkiNotify = sias->rpkiNotify;
goto refresh_success;
case EBUSY:
return EBUSY;
/* 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 */
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;
}
* 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;
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);
mutex_unlock(&commits_lock);
}
+char const *
+cage_rpkiNotify(struct cache_cage *cage)
+{
+ return cage->rpkiNotify;
+}
+
static void
cachent_print(struct cache_node *node)
{
{
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;
}
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);
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_ */
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;
*/
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;
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;
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 *);
{
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);
{
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 *
}
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 };
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. */
}
/* 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",
/* 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",
/* See comments at test_rsync_commit(). */
START_TEST(test_rrdp_commit)
{
+ char const *notif = "https://domain/rpki/notif.xml";
unsigned int i;
setup_test();
}
/* 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",
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",
}
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);
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;
}
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());
}
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;
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/<first-cage>/<c.cer>" */
+ ck_file("rrdp/0/0"); /* "rrdp/<first-cage>/<c.cer>" */
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
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);