#include "slurm/slurm_loader.h"
#include "thread/thread_pool.h"
-#define START_SERIAL 0
-
DEFINE_ARRAY_LIST_FUNCTIONS(deltas_db, struct delta_group, )
struct vrp_node {
/* Last valid SLURM applied to base */
struct db_slurm *slurm;
- serial_t next_serial;
+ /*
+ * This is the serial number of base.
+ *
+ * At least one RTR client implementation (Cloudflare's rpki-rtr-client)
+ * malfunctions if the validator uses zero as the first serial, so this
+ * value behaves as follows:
+ *
+ * serial = 0. After every successful validation cycle, serial++.
+ *
+ * Do not use this value to check whether we already finished our first
+ * validation. (Use base != NULL for that.) Zero is totally a valid
+ * serial, particularly when the integer wraps.
+ */
+ serial_t serial;
uint16_t v0_session_id;
uint16_t v1_session_id;
};
* "desynchronization" (more at RFC 6810 'Glossary' and
* 'Fields of a PDU')
*/
- state.next_serial = START_SERIAL;
+ state.serial = 0;
/* Get the bits that'll fit in session_id */
now = 0;
}
/*
- * Make an empty dummy delta array.
- * It's annoying, but temporary. (Until it expires.) Otherwise, it'd be a pain
- * to have to check NULL delta_group.deltas all the time.
+ * Remove unnecessary deltas from the database.
+ * Unnecessary deltas = those whose serial < min_serial.
*/
-static int
-create_empty_delta(struct deltas **deltas)
+static void
+cleanup_deltas(serial_t min_serial)
{
- struct delta_group deltas_node;
- int error;
+ struct delta_group *initial;
+ struct delta_group *rm;
+ array_index i;
- error = deltas_create(deltas);
- if (error)
- return error;
+ /*
+ * TODO the array is sorted by serial, but it's supposed to employ
+ * serial arithmetic. > is incorrect.
+ */
+ ARRAYLIST_FOREACH(&state.deltas, initial, i)
+ if (initial->serial > min_serial)
+ break;
- deltas_node.serial = state.next_serial;
- deltas_node.deltas = *deltas;
- error = deltas_db_add(&state.deltas, &deltas_node);
- if (error)
- deltas_refput(*deltas);
- return error;
+ for (rm = state.deltas.array; rm < initial; rm++)
+ deltas_refput(rm->deltas);
+
+ state.deltas.len -= (initial - state.deltas.array);
+ memmove(state.deltas.array, initial,
+ state.deltas.len * sizeof(struct delta_group));
}
-/**
- * Reallocate the array of @db starting at @start, the length and capacity are
- * calculated according to the new start.
- */
static int
-resize_deltas_db(struct deltas_db *db, struct delta_group *start)
+__compute_deltas(struct db_table *old_base, struct db_table *new_base,
+ bool *notify_clients)
{
- struct delta_group *tmp, *ptr;
+ struct deltas *deltas; /* Deltas in raw form */
+ struct delta_group deltas_node; /* Deltas in database node form */
+ serial_t min_serial;
+ int error;
- db->len -= (start - db->array);
- while (db->len < db->capacity / 2)
- db->capacity /= 2;
- tmp = malloc(sizeof(struct delta_group) * db->capacity);
- if (tmp == NULL)
- return pr_enomem();
+ error = 0;
- memcpy(tmp, start, db->len * sizeof(struct delta_group));
- /* Release memory allocated */
- for (ptr = db->array; ptr < start; ptr++)
- deltas_refput(ptr->deltas);
- free(db->array);
- db->array = tmp;
+ /* No clients listening = no need for deltas */
+ if (clients_get_min_serial(&min_serial) == -ENOENT)
+ goto purge_deltas;
- return 0;
-}
+ if (notify_clients != NULL)
+ *notify_clients = true;
-/*
- * Lock must be requested before calling this function
- */
-static int
-vrps_purge(struct deltas **deltas)
-{
- struct delta_group *group;
- array_index i;
- serial_t min_serial;
+ /* First version of the database = No deltas */
+ if (old_base == NULL)
+ goto purge_deltas;
+
+ /*
+ * Failure on computing deltas = latest database version lacks deltas,
+ * which renders all previous deltas useless. (Because clients always
+ * want the latest.)
+ */
+ error = compute_deltas(old_base, new_base, &deltas);
+ if (error)
+ goto purge_deltas;
- if (clients_get_min_serial(&min_serial) != 0) {
- /* Nobody will need deltas, just leave an empty one */
- deltas_refput(*deltas);
- deltas_db_cleanup(&state.deltas, deltagroup_cleanup);
- deltas_db_init(&state.deltas);
- return create_empty_delta(deltas);
+ if (deltas_is_empty(deltas)) {
+ if (notify_clients != NULL)
+ *notify_clients = false;
+ deltas_refput(deltas);
+ goto success; /* Happy path when the DB doesn't change. */
}
- /* Assume its ordered by serial, so get the new initial pointer */
- ARRAYLIST_FOREACH(&state.deltas, group, i)
- if (group->serial >= min_serial)
- break;
+ deltas_node.serial = state.serial;
+ deltas_node.deltas = deltas;
+ /* On success, ownership of deltas is transferred to state.deltas. */
+ error = deltas_db_add(&state.deltas, &deltas_node);
+ if (error) {
+ deltas_refput(deltas);
+ goto purge_deltas;
+ }
- /* Its the first element or reached end, nothing to purge */
- if (group == state.deltas.array ||
- (group - state.deltas.array) == state.deltas.len)
- return 0;
+ /* Happy path when the DB changes. (Fall through) */
+
+success:
+ cleanup_deltas(min_serial);
+ return 0;
- return resize_deltas_db(&state.deltas, group);
+purge_deltas:
+ deltas_db_cleanup(&state.deltas, deltagroup_cleanup);
+ return error;
}
static int
-__vrps_update(bool *changed)
+__vrps_update(bool *notify_clients)
{
struct db_table *old_base;
struct db_table *new_base;
- struct deltas *deltas; /* Deltas in raw form */
- struct delta_group deltas_node; /* Deltas in database node form */
- serial_t min_serial;
int error;
- if (changed)
- *changed = false;
+ if (notify_clients)
+ *notify_clients = false;
old_base = NULL;
new_base = NULL;
error = __perform_standalone_validation(&new_base);
if (error)
return error;
-
error = slurm_apply(&new_base, &state.slurm);
- if (error)
- goto revert_base;
-
- rwlock_write_lock(&state_lock);
+ if (error) {
+ db_table_destroy(new_base);
+ return error;
+ }
- if (state.base != NULL) {
- error = compute_deltas(state.base, new_base, &deltas);
- if (error) {
- rwlock_unlock(&state_lock);
- goto revert_base;
- }
+ /*
+ * At this point, new_base is completely valid. Even if we error out
+ * later, report the ROAs.
+ *
+ * This is done after the validation, not during it, to prevent
+ * duplicate ROAs.
+ */
+ output_print_data(new_base);
- if (deltas_is_empty(deltas)) {
- rwlock_unlock(&state_lock);
- goto revert_deltas; /* error == 0 is good */
- }
+ rwlock_write_lock(&state_lock);
- /* Just store deltas if someone will care about it */
- if (clients_get_min_serial(&min_serial) == 0) {
- deltas_node.serial = state.next_serial;
- deltas_node.deltas = deltas;
- error = deltas_db_add(&state.deltas, &deltas_node);
- if (error) {
- rwlock_unlock(&state_lock);
- goto revert_deltas;
- }
- }
+ old_base = state.base; /* Postpone destruction, to release lock ASAP. */
+ state.base = new_base;
+ state.serial++;
+ /*
+ * TODO after refactoring vrps_foreach_filtered_delta(), move this out
+ * of the mutex. You don't really need the mutex to compute the deltas;
+ * vrps_update() is supposed to be the only writer.
+ */
+ error = __compute_deltas(old_base, new_base, notify_clients);
+ if (error) {
/*
- * Postpone destruction of the old database,
- * to release the lock ASAP.
+ * Deltas are nice-to haves. As long as state.base is correct,
+ * the validator can continue serving the routers.
+ * (Albeit less efficiently.)
+ * So drop a warning and keep going.
*/
- old_base = state.base;
-
- /* Remove unnecessary deltas */
- error = vrps_purge(&deltas);
- if (error) {
- rwlock_unlock(&state_lock);
- goto revert_base;
- }
- } else {
- /* There's also an empty base, don't alter state */
- if (db_table_roa_count(new_base) +
- db_table_router_key_count(new_base) == 0) {
- rwlock_unlock(&state_lock);
- error = 0; /* OK (said explicitly) */
- goto revert_base;
- }
- error = create_empty_delta(&deltas);
- if (error) {
- rwlock_unlock(&state_lock);
- goto revert_base;
- }
+ pr_op_warn("Deltas could not be computed: %s", strerror(error));
}
- if (changed)
- *changed = true;
- state.base = new_base;
- state.next_serial++;
-
rwlock_unlock(&state_lock);
if (old_base != NULL)
db_table_destroy(old_base);
- /* Print after validation to avoid duplicated info */
- output_print_data(new_base);
-
return 0;
-
-revert_deltas:
- deltas_refput(deltas);
-revert_base:
- /* Print info that was already validated */
- output_print_data(new_base);
- db_table_destroy(new_base);
- return error;
}
int
int error;
/*
- * This wrapper is mainly for log informational data, so if there's no
- * need don't do unnecessary calls
+ * This wrapper is mainly intended to log informational data, so if
+ * there's no need, don't do unnecessary calls.
*/
if (!log_op_enabled(LOG_INFO))
return __vrps_update(changed);
pr_op_info("Starting validation.");
- serial = START_SERIAL;
if (config_get_mode() == SERVER) {
error = get_last_serial_number(&serial);
if (!error)
- pr_op_info("- Current serial number is %u.", serial);
+ pr_op_info("- Serial before validation: %u", serial);
}
time(&start);
pr_op_info("- Valid Prefixes: %u", db_table_roa_count(state.base));
pr_op_info("- Valid Router Keys: %u",
db_table_router_key_count(state.base));
- if (config_get_mode() == SERVER) {
- pr_op_info("- %s serial number is %u.",
- serial == state.next_serial - 1 ? "Current" : "New",
- state.next_serial - 1);
- }
+ if (config_get_mode() == SERVER)
+ pr_op_info("- Serial: %u", state.serial);
rwlock_unlock(&state_lock);
} while(0);
pr_op_info("- Real execution time: %ld secs.", exec_time);
vrps_get_deltas_from(serial_t from, serial_t *to, struct deltas_db *result)
{
struct delta_group *group;
+ serial_t first_serial;
+ serial_t last_serial;
array_index i;
- bool from_found;
int error;
- from_found = false;
-
error = rwlock_read_lock(&state_lock);
if (error)
return error;
- if (state.base == NULL) {
+ if (state.base == NULL)
+ goto try_again; /* Database still under construction. */
+ if (from == state.serial) {
+ /* Client already has the latest serial. */
rwlock_unlock(&state_lock);
- return -EAGAIN;
+ *to = from;
+ return 0;
}
+ if (state.deltas.len == 0)
+ goto reset_database; /* No deltas available. */
- ARRAYLIST_FOREACH(&state.deltas, group, i) {
- if (!from_found) {
- if (group->serial == from) {
- from_found = true;
- *to = group->serial;
- }
- continue;
- }
+ first_serial = state.deltas.array[0].serial - 1;
+ last_serial = state.deltas.array[state.deltas.len - 1].serial;
+
+ if (from < first_serial)
+ goto reset_database; /* Delta was already deleted. */
+ if (from > last_serial)
+ goto reset_database; /* Serial is invalid. */
+
+ for (i = from - first_serial; i < state.deltas.len; i++) {
+ group = &state.deltas.array[i];
error = deltas_db_add(result, group);
if (error) {
}
deltas_refget(group->deltas);
- *to = group->serial;
}
rwlock_unlock(&state_lock);
- return from_found ? 0 : -ESRCH;
+ *to = last_serial;
+ return 0;
+
+try_again:
+ rwlock_unlock(&state_lock);
+ return -EAGAIN;
+
+reset_database:
+ rwlock_unlock(&state_lock);
+ return -ESRCH;
}
int
return error;
if (state.base != NULL)
- *result = state.next_serial - 1;
+ *result = state.serial;
else
error = -EAGAIN;
* 4: Router key, ASN 0
* 5: Router key, ASN 1
*/
-static const bool iteration0_base[] = { 1, 0, 1, 0, 1, 0, };
-static const bool iteration1_base[] = { 1, 1, 1, 1, 1, 1, };
-static const bool iteration2_base[] = { 0, 1, 0, 1, 0, 1, };
-static const bool iteration3_base[] = { 1, 0, 1, 0, 1, 0, };
+static const bool iteration1_base[] = { 1, 0, 1, 0, 1, 0, };
+static const bool iteration2_base[] = { 1, 1, 1, 1, 1, 1, };
+static const bool iteration3_base[] = { 0, 1, 0, 1, 0, 1, };
+static const bool iteration4_base[] = { 1, 0, 1, 0, 1, 0, };
/*
* DELTA
* 5: Withdrawal, RK, ASN 1 11: Announcement, RK, ASN 1
*/
-static const bool deltas_0to0[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, };
-
-static const bool deltas_0to1[] = { 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, };
static const bool deltas_1to1[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, };
-static const bool deltas_0to2[] = { 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, };
-static const bool deltas_1to2[] = { 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, };
+static const bool deltas_1to2[] = { 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, };
static const bool deltas_2to2[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, };
+static const bool deltas_1to3[] = { 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, };
+static const bool deltas_2to3[] = { 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, };
+static const bool deltas_3to3[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, };
+
/* Deltas with rules that override each other */
-static const bool deltas_0to3_ovrd[] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, };
-static const bool deltas_1to3_ovrd[] = { 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, };
-static const bool deltas_2to3_ovrd[] = { 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, };
-static const bool deltas_3to3_ovrd[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, };
+static const bool deltas_1to4_ovrd[] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, };
+static const bool deltas_2to4_ovrd[] = { 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, };
+static const bool deltas_3to4_ovrd[] = { 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, };
+static const bool deltas_4to4_ovrd[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, };
/* Deltas cleaned up */
-static const bool deltas_0to3_clean[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, };
-static const bool deltas_1to3_clean[] = { 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, };
-static const bool deltas_2to3_clean[] = { 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, };
-static const bool deltas_3to3_clean[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, };
+static const bool deltas_1to4_clean[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, };
+static const bool deltas_2to4_clean[] = { 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, };
+static const bool deltas_3to4_clean[] = { 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, };
+static const bool deltas_4to4_clean[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, };
/* Impersonator functions */
}
static void
-check_no_deltas(serial_t from, serial_t to)
+check_no_deltas(serial_t from)
{
- serial_t actual_serial;
+ serial_t actual_to;
struct deltas_db deltas;
deltas_db_init(&deltas);
- ck_assert_int_eq(-ESRCH, vrps_get_deltas_from(from, &actual_serial,
+ ck_assert_int_eq(-ESRCH, vrps_get_deltas_from(from, &actual_to,
&deltas));
}
static void
-create_deltas_0to1(struct deltas_db *deltas, serial_t *serial, bool *changed,
+create_deltas_1to2(struct deltas_db *deltas, serial_t *serial, bool *changed,
bool *iterated_entries)
{
current_min_serial = 0;
/* First validation: One tree, no deltas */
ck_assert_int_eq(0, vrps_update(changed));
- check_serial(0);
- check_base(0, iteration0_base);
- check_deltas(0, 0, deltas_0to0, false);
-
- /* Second validation: One tree, added deltas */
- ck_assert_int_eq(0, vrps_update(changed));
check_serial(1);
check_base(1, iteration1_base);
- check_deltas(0, 1, deltas_0to1, false);
check_deltas(1, 1, deltas_1to1, false);
+
+ /* Second validation: One tree, added deltas */
+ ck_assert_int_eq(0, vrps_update(changed));
+ check_serial(2);
+ check_base(2, iteration2_base);
+ check_deltas(1, 2, deltas_1to2, false);
+ check_deltas(2, 2, deltas_2to2, false);
}
START_TEST(test_basic)
bool changed;
bool iterated_entries[12];
- create_deltas_0to1(&deltas, &serial, &changed, iterated_entries);
+ create_deltas_1to2(&deltas, &serial, &changed, iterated_entries);
/* Third validation: One tree, removed deltas */
ck_assert_int_eq(0, vrps_update(&changed));
- check_serial(2);
- check_base(2, iteration2_base);
- check_deltas(0, 2, deltas_0to2, false);
- check_deltas(1, 2, deltas_1to2, false);
- check_deltas(2, 2, deltas_2to2, false);
+ check_serial(3);
+ check_base(3, iteration3_base);
+ check_deltas(1, 3, deltas_1to3, false);
+ check_deltas(2, 3, deltas_2to3, false);
+ check_deltas(3, 3, deltas_3to3, false);
vrps_destroy();
}
bool changed;
bool iterated_entries[12];
- create_deltas_0to1(&deltas, &serial, &changed, iterated_entries);
+ create_deltas_1to2(&deltas, &serial, &changed, iterated_entries);
/*
- * Assume that the client(s) already have serial 1 (serial 2 will be
- * created) so serial 0 isn't needed anymore.
+ * Assume that the client(s) already have serial 2 (serial 3 will be
+ * created) so serial 1 isn't needed anymore.
*/
- current_min_serial = 1;
+ current_min_serial = 2;
- /* Third validation: One tree, removed deltas and delta 0 removed */
+ /* Third validation: One tree, removed deltas and delta 1 removed */
ck_assert_int_eq(0, vrps_update(&changed));
- check_serial(2);
- check_base(2, iteration2_base);
- check_no_deltas(0, 2);
- check_deltas(1, 2, deltas_1to2, false);
- check_deltas(2, 2, deltas_2to2, false);
+ check_serial(3);
+ check_base(3, iteration3_base);
+ check_no_deltas(1);
+ check_deltas(2, 3, deltas_2to3, false);
+ check_deltas(3, 3, deltas_3to3, false);
vrps_destroy();
bool changed;
bool iterated_entries[12];
- create_deltas_0to1(&deltas, &serial, &changed, iterated_entries);
+ create_deltas_1to2(&deltas, &serial, &changed, iterated_entries);
/* Third validation: One tree, removed deltas */
ck_assert_int_eq(0, vrps_update(&changed));
- check_serial(2);
- check_base(2, iteration2_base);
- check_deltas(0, 2, deltas_0to2, false);
- check_deltas(1, 2, deltas_1to2, false);
- check_deltas(2, 2, deltas_2to2, false);
+ check_serial(3);
+ check_base(3, iteration3_base);
+ check_deltas(1, 3, deltas_1to3, false);
+ check_deltas(2, 3, deltas_2to3, false);
+ check_deltas(3, 3, deltas_3to3, false);
/* Fourth validation with deltas that override each other */
ck_assert_int_eq(0, vrps_update(&changed));
- check_serial(3);
- check_base(3, iteration3_base);
- check_deltas(0, 3, deltas_0to3_ovrd, false);
- check_deltas(1, 3, deltas_1to3_ovrd, false);
- check_deltas(2, 3, deltas_2to3_ovrd, false);
- check_deltas(3, 3, deltas_3to3_ovrd, false);
+ check_serial(4);
+ check_base(4, iteration4_base);
+ check_deltas(1, 4, deltas_1to4_ovrd, false);
+ check_deltas(2, 4, deltas_2to4_ovrd, false);
+ check_deltas(3, 4, deltas_3to4_ovrd, false);
+ check_deltas(4, 4, deltas_4to4_ovrd, false);
/* Check "cleaned up" deltas */
- check_deltas(0, 3, deltas_0to3_clean, true);
- check_deltas(1, 3, deltas_1to3_clean, true);
- check_deltas(2, 3, deltas_2to3_clean, true);
- check_deltas(3, 3, deltas_3to3_clean, true);
+ check_deltas(1, 4, deltas_1to4_clean, true);
+ check_deltas(2, 4, deltas_2to4_clean, true);
+ check_deltas(3, 4, deltas_3to4_clean, true);
+ check_deltas(4, 4, deltas_4to4_clean, true);
vrps_destroy();