From: Alberto Leiva Popper Date: Thu, 9 May 2019 19:55:48 +0000 (-0500) Subject: Patch the PDU handler's locking X-Git-Tag: v0.0.2~38 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=58f75f6e87b1fda64df30ef3d780689f47edc987;p=thirdparty%2FFORT-validator.git Patch the PDU handler's locking Side effects: - New typedef: `serial_t`. Because I keep forgetting the serial data type we're using. - Remove `enum delta_op` in favor of `FLAG_*`. The latter still should probably be converted into an enum and renamed though, especially since its instances are never actually used as bitwise flags. - Moved `struct vrp`'s `flag` field into `struct delta`, because it's not always used in `vrp`. Also, to improve performance a little, we're no longer canceling deltas on cache responses when there's only one serial, because the ROA hash table already guarantees their uniqueness. --- diff --git a/src/Makefile.am b/src/Makefile.am index c7ce4cdf..afa7c6ec 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -10,28 +10,33 @@ fort_SOURCES = main.c fort_SOURCES += address.h address.c fort_SOURCES += algorithm.h algorithm.c fort_SOURCES += certificate_refs.h certificate_refs.c +fort_SOURCES += clients.c clients.h +fort_SOURCES += common.c common.h fort_SOURCES += console_handler.h console_handler.c -fort_SOURCES += common.h fort_SOURCES += config.h config.c fort_SOURCES += debug.h debug.c fort_SOURCES += extension.h extension.c fort_SOURCES += file.h file.c +fort_SOURCES += json_parser.c json_parser.h fort_SOURCES += line_file.h line_file.c fort_SOURCES += log.h log.c fort_SOURCES += nid.h nid.c +fort_SOURCES += notify.c notify.h fort_SOURCES += random.h random.c fort_SOURCES += resource.h resource.c fort_SOURCES += rpp.h rpp.c +fort_SOURCES += slurm_db.c slurm_db.h +fort_SOURCES += slurm_loader.c slurm_loader.h +fort_SOURCES += slurm_parser.c slurm_parser.h fort_SOURCES += sorted_array.h sorted_array.c fort_SOURCES += state.h state.c fort_SOURCES += str.h str.c fort_SOURCES += thread_var.h thread_var.c +fort_SOURCES += updates_daemon.c updates_daemon.h fort_SOURCES += uri.h uri.c fort_SOURCES += json_handler.h json_handler.c fort_SOURCES += validation_handler.h validation_handler.c -fort_SOURCES += rsync/rsync.h rsync/rsync.c - fort_SOURCES += asn1/content_info.h asn1/content_info.c fort_SOURCES += asn1/decode.h asn1/decode.c fort_SOURCES += asn1/oid.h asn1/oid.c @@ -46,8 +51,6 @@ fort_SOURCES += config/types.h fort_SOURCES += config/uint.c config/uint.h fort_SOURCES += config/uint32.c config/uint32.h -fort_SOURCES += data_structure/array_list.h - fort_SOURCES += crypto/base64.h crypto/base64.c fort_SOURCES += crypto/hash.h crypto/hash.c @@ -69,15 +72,7 @@ fort_SOURCES += resource/ip4.h resource/ip4.c fort_SOURCES += resource/ip6.h resource/ip6.c fort_SOURCES += resource/asn.h resource/asn.c -fort_SOURCES += array_list.h -fort_SOURCES += clients.c clients.h -fort_SOURCES += common.c common.h -fort_SOURCES += json_parser.c json_parser.h -fort_SOURCES += notify.c notify.h -fort_SOURCES += slurm_db.c slurm_db.h -fort_SOURCES += slurm_loader.c slurm_loader.h -fort_SOURCES += slurm_parser.c slurm_parser.h -fort_SOURCES += updates_daemon.c updates_daemon.h +fort_SOURCES += rsync/rsync.h rsync/rsync.c fort_SOURCES += rtr/err_pdu.c rtr/err_pdu.h fort_SOURCES += rtr/pdu_handler.c rtr/pdu_handler.h diff --git a/src/address.c b/src/address.c index 67663021..562026bf 100644 --- a/src/address.c +++ b/src/address.c @@ -377,7 +377,7 @@ addr2str6(struct in6_addr *addr, char *buffer) return inet_ntop(AF_INET6, addr, buffer, INET6_ADDRSTRLEN); } -static int const +static int str2addr4(const char *addr, struct in_addr *dst) { if (!inet_pton(AF_INET, addr, dst)) @@ -385,7 +385,7 @@ str2addr4(const char *addr, struct in_addr *dst) return 0; } -static int const +static int str2addr6(const char *addr, struct in6_addr *dst) { if (!inet_pton(AF_INET6, addr, dst)) @@ -438,8 +438,8 @@ str_to_prefix_length(const char *text, uint8_t *dst, uint8_t max_value) return -EINVAL; } /* An underflow or overflow will be considered here */ - if (len < 0 || max_value < len) - return pr_err("Prefix length (%ld) is out of range (0-%d).", + if (max_value < len) + return pr_err("Prefix length (%lu) is out of range (0-%u).", len, max_value); *dst = (uint8_t) len; diff --git a/src/clients.c b/src/clients.c index fd5e031e..c212d726 100644 --- a/src/clients.c +++ b/src/clients.c @@ -23,7 +23,7 @@ static struct hashable_client *table; /** Read/write lock, which protects @table and its inhabitants. */ static pthread_rwlock_t lock; /** Serial number from which deltas must be stored */ -static uint32_t min_serial; +static serial_t min_serial; int clients_db_init(void) @@ -111,7 +111,7 @@ clients_add(int fd, struct sockaddr_storage *addr, uint8_t rtr_version) } void -clients_update_serial(int fd, uint32_t serial) +clients_update_serial(int fd, serial_t serial) { struct hashable_client *cur_client; @@ -126,11 +126,11 @@ unlock: rwlock_unlock(&lock); } -uint32_t +serial_t clients_get_min_serial(void) { struct hashable_client *current, *ptr; - uint32_t result; + serial_t result; rwlock_write_lock(&lock); if (HASH_COUNT(table) == 0) diff --git a/src/clients.h b/src/clients.h index 7be04f82..5055e144 100644 --- a/src/clients.h +++ b/src/clients.h @@ -2,6 +2,7 @@ #define SRC_CLIENTS_H_ #include +#include "rtr/db/vrp.h" #define ERTR_VERSION_MISMATCH 8754983 @@ -14,17 +15,17 @@ struct client { }; in_port_t sin_port; uint8_t rtr_version; - uint32_t serial_number; + serial_t serial_number; }; int clients_db_init(void); int clients_add(int, struct sockaddr_storage *, uint8_t); -void clients_update_serial(int, uint32_t); +void clients_update_serial(int, serial_t); void clients_forget(int); typedef int (*clients_foreach_cb)(struct client const *, void *); int clients_foreach(clients_foreach_cb, void *); -uint32_t clients_get_min_serial(void); +serial_t clients_get_min_serial(void); void clients_db_destroy(void); diff --git a/src/data_structure/array_list.h b/src/data_structure/array_list.h index a4f5e089..e87c6be5 100644 --- a/src/data_structure/array_list.h +++ b/src/data_structure/array_list.h @@ -16,8 +16,13 @@ size_t capacity; \ } -#define DEFINE_ARRAY_LIST_FUNCTIONS(name, elem_type) \ - static void \ +#define DECLARE_ARRAY_LIST_FUNCTIONS(name, elem_type) \ + void name##_init(struct name *); \ + void name##_cleanup(struct name *, void (*cb)(elem_type *)); \ + int name##_add(struct name *list, elem_type *elem); + +#define DEFINE_ARRAY_LIST_FUNCTIONS(name, elem_type, modifiers) \ + modifiers void \ name##_init(struct name *list) \ { \ list->array = NULL; \ @@ -25,7 +30,7 @@ list->capacity = 0; \ } \ \ - static void \ + modifiers void \ name##_cleanup(struct name *list, void (*cb)(elem_type *)) \ { \ array_index i; \ @@ -36,7 +41,7 @@ } \ \ /* Will store a shallow copy, not @elem */ \ - static int \ + modifiers int \ name##_add(struct name *list, elem_type *elem) \ { \ elem_type *tmp; \ @@ -66,7 +71,7 @@ #define ARRAY_LIST(name, elem_type) \ DEFINE_ARRAY_LIST_STRUCT(name, elem_type); \ - DEFINE_ARRAY_LIST_FUNCTIONS(name, elem_type) + DEFINE_ARRAY_LIST_FUNCTIONS(name, elem_type, ) /* c = cursor */ #define ARRAYLIST_FOREACH(list, c) for ( \ diff --git a/src/notify.c b/src/notify.c index 40e7485a..949ade2f 100644 --- a/src/notify.c +++ b/src/notify.c @@ -11,27 +11,24 @@ static int send_notify(struct client const *client, void *arg) { struct sender_common common; - uint32_t *serial = arg; - uint16_t session_id; + serial_t *serial = arg; int error; /* Send Serial Notify PDU */ - session_id = get_current_session_id(client->rtr_version); - init_sender_common(&common, client->fd, client->rtr_version, - &session_id, serial, NULL); - error = send_serial_notify_pdu(&common); + init_sender_common(&common, client->fd, client->rtr_version); + error = send_serial_notify_pdu(&common, *serial); - /* Error? Log it */ + /* Error? Log it... */ if (error) - pr_warn("Error sending notify PDU to client"); + pr_warn("Error code %d sending notify PDU to client.", error); - return 0; /* Do not interrupt notify to other clients */ + return 0; /* ...but do not interrupt notify to other clients */ } int notify_clients(void) { - uint32_t serial; + serial_t serial; int error; error = get_last_serial_number(&serial); diff --git a/src/rtr/db/delta.c b/src/rtr/db/delta.c index d53085f0..b8ffc998 100644 --- a/src/rtr/db/delta.c +++ b/src/rtr/db/delta.c @@ -26,6 +26,9 @@ struct deltas { struct deltas_v6 adds; struct deltas_v6 removes; } v6; + + /* TODO (now) atomic */ + unsigned int references; }; int @@ -41,24 +44,34 @@ deltas_create(struct deltas **_result) deltas_v4_init(&result->v4.removes); deltas_v6_init(&result->v6.adds); deltas_v6_init(&result->v6.removes); + result->references = 1; *_result = result; return 0; } void -deltas_destroy(struct deltas *deltas) +deltas_get(struct deltas *deltas) +{ + deltas->references++; +} + +void +deltas_put(struct deltas *deltas) { - deltas_v4_cleanup(&deltas->v4.adds, NULL); - deltas_v4_cleanup(&deltas->v4.removes, NULL); - deltas_v6_cleanup(&deltas->v6.adds, NULL); - deltas_v6_cleanup(&deltas->v6.removes, NULL); - free(deltas); + deltas->references--; + if (deltas->references == 0) { + deltas_v4_cleanup(&deltas->v4.adds, NULL); + deltas_v4_cleanup(&deltas->v4.removes, NULL); + deltas_v6_cleanup(&deltas->v6.adds, NULL); + deltas_v6_cleanup(&deltas->v6.removes, NULL); + free(deltas); + } } int deltas_add_roa_v4(struct deltas *deltas, uint32_t as, struct v4_address *addr, - enum delta_op op) + int op) { struct delta_v4 delta = { .as = as, @@ -67,18 +80,18 @@ deltas_add_roa_v4(struct deltas *deltas, uint32_t as, struct v4_address *addr, }; switch (op) { - case DELTA_ADD: + case FLAG_ANNOUNCEMENT: return deltas_v4_add(&deltas->v4.adds, &delta); - case DELTA_RM: + case FLAG_WITHDRAWAL: return deltas_v4_add(&deltas->v4.removes, &delta); } - return pr_crit("Unknown delta operation: %u", op); + return pr_crit("Unknown delta operation: %d", op); } int deltas_add_roa_v6(struct deltas *deltas, uint32_t as, struct v6_address *addr, - enum delta_op op) + int op) { struct delta_v6 delta = { .as = as, @@ -87,13 +100,13 @@ deltas_add_roa_v6(struct deltas *deltas, uint32_t as, struct v6_address *addr, }; switch (op) { - case DELTA_ADD: + case FLAG_ANNOUNCEMENT: return deltas_v6_add(&deltas->v6.adds, &delta); - case DELTA_RM: + case FLAG_WITHDRAWAL: return deltas_v6_add(&deltas->v6.removes, &delta); } - return pr_crit("Unknown delta operation: %u", op); + return pr_crit("Unknown delta operation: %d", op); } bool @@ -106,22 +119,23 @@ deltas_is_empty(struct deltas *deltas) } static int -__foreach_v4(struct deltas_v4 *array, vrp_foreach_cb cb, void *arg, - uint8_t flags) +__foreach_v4(struct deltas_v4 *array, delta_foreach_cb cb, void *arg, + serial_t serial, uint8_t flags) { + struct delta delta; struct delta_v4 *d; - struct vrp vrp; int error; - vrp.addr_fam = AF_INET; - vrp.flags = flags; + delta.serial = serial; + delta.vrp.addr_fam = AF_INET; + delta.flags = flags; ARRAYLIST_FOREACH(array, d) { - vrp.asn = d->as; - vrp.prefix.v4 = d->prefix.addr; - vrp.prefix_length = d->prefix.len; - vrp.max_prefix_length = d->max_length; - error = cb(&vrp, arg); + delta.vrp.asn = d->as; + delta.vrp.prefix.v4 = d->prefix.addr; + delta.vrp.prefix_length = d->prefix.len; + delta.vrp.max_prefix_length = d->max_length; + error = cb(&delta, arg); if (error) return error; } @@ -130,22 +144,23 @@ __foreach_v4(struct deltas_v4 *array, vrp_foreach_cb cb, void *arg, } static int -__foreach_v6(struct deltas_v6 *array, vrp_foreach_cb cb, void *arg, - uint8_t flags) +__foreach_v6(struct deltas_v6 *array, delta_foreach_cb cb, void *arg, + serial_t serial, uint8_t flags) { + struct delta delta; struct delta_v6 *d; - struct vrp vrp; int error; - vrp.addr_fam = AF_INET6; - vrp.flags = flags; + delta.serial = serial; + delta.vrp.addr_fam = AF_INET6; + delta.flags = flags; ARRAYLIST_FOREACH(array, d) { - vrp.asn = d->as; - vrp.prefix.v6 = d->prefix.addr; - vrp.prefix_length = d->prefix.len; - vrp.max_prefix_length = d->max_length; - error = cb(&vrp, arg); + delta.vrp.asn = d->as; + delta.vrp.prefix.v6 = d->prefix.addr; + delta.vrp.prefix_length = d->prefix.len; + delta.vrp.max_prefix_length = d->max_length; + error = cb(&delta, arg); if (error) return error; } @@ -154,19 +169,26 @@ __foreach_v6(struct deltas_v6 *array, vrp_foreach_cb cb, void *arg, } int -deltas_foreach(struct deltas *deltas, vrp_foreach_cb cb, void *arg) +deltas_foreach(serial_t serial, struct deltas *deltas, delta_foreach_cb cb, + void *arg) { int error; - error = __foreach_v4(&deltas->v4.adds, cb, arg, FLAG_ANNOUNCEMENT); + error = __foreach_v4(&deltas->v4.adds, cb, arg, serial, + FLAG_ANNOUNCEMENT); if (error) return error; - error = __foreach_v4(&deltas->v4.removes, cb, arg, FLAG_WITHDRAWAL); + + error = __foreach_v4(&deltas->v4.removes, cb, arg, serial, + FLAG_WITHDRAWAL); if (error) return error; - error = __foreach_v6(&deltas->v6.adds, cb, arg, FLAG_ANNOUNCEMENT); + error = __foreach_v6(&deltas->v6.adds, cb, arg, serial, + FLAG_ANNOUNCEMENT); if (error) return error; - return __foreach_v6(&deltas->v6.removes, cb, arg, FLAG_WITHDRAWAL); + + return __foreach_v6(&deltas->v6.removes, cb, arg, serial, + FLAG_WITHDRAWAL); } diff --git a/src/rtr/db/delta.h b/src/rtr/db/delta.h index 076b0dba..1d2a0701 100644 --- a/src/rtr/db/delta.h +++ b/src/rtr/db/delta.h @@ -4,22 +4,16 @@ #include "rtr/db/roa.h" #include "rtr/db/vrp.h" -enum delta_op { - DELTA_ADD, - DELTA_RM, -}; - struct deltas; int deltas_create(struct deltas **); -void deltas_destroy(struct deltas *); +void deltas_get(struct deltas *); +void deltas_put(struct deltas *); -int deltas_add_roa_v4(struct deltas *, uint32_t, struct v4_address *, - enum delta_op); -int deltas_add_roa_v6(struct deltas *, uint32_t, struct v6_address *, - enum delta_op); +int deltas_add_roa_v4(struct deltas *, uint32_t, struct v4_address *, int); +int deltas_add_roa_v6(struct deltas *, uint32_t, struct v6_address *, int); bool deltas_is_empty(struct deltas *); -int deltas_foreach(struct deltas *, vrp_foreach_cb , void *); +int deltas_foreach(serial_t, struct deltas *, delta_foreach_cb , void *); #endif /* SRC_DELTA_H_ */ diff --git a/src/rtr/db/roa.c b/src/rtr/db/roa.c index 15aefcc0..1ea7d757 100644 --- a/src/rtr/db/roa.c +++ b/src/rtr/db/roa.c @@ -1,7 +1,7 @@ #include "rtr/db/roa.h" -DEFINE_ARRAY_LIST_FUNCTIONS(v4_addresses, struct v4_address) -DEFINE_ARRAY_LIST_FUNCTIONS(v6_addresses, struct v6_address) +DEFINE_ARRAY_LIST_FUNCTIONS(v4_addresses, struct v4_address, static) +DEFINE_ARRAY_LIST_FUNCTIONS(v6_addresses, struct v6_address, static) int roa_create(uint32_t as, struct roa **_result) diff --git a/src/rtr/db/roa_table.c b/src/rtr/db/roa_table.c index 52a0b42d..ab528e11 100644 --- a/src/rtr/db/roa_table.c +++ b/src/rtr/db/roa_table.c @@ -83,7 +83,6 @@ create_roa(uint32_t asn, uint8_t max_length) roa->data.asn = asn; roa->data.max_prefix_length = max_length; - roa->data.flags = FLAG_ANNOUNCEMENT; return roa; } @@ -133,7 +132,7 @@ rtrhandler_handle_roa_v6(struct roa_table *table, uint32_t asn, } static int -add_delta(struct deltas *deltas, struct hashable_roa *roa, enum delta_op op) +add_delta(struct deltas *deltas, struct hashable_roa *roa, int op) { union { struct v4_address v4; @@ -163,7 +162,7 @@ add_delta(struct deltas *deltas, struct hashable_roa *roa, enum delta_op op) */ static int add_deltas(struct hashable_roa *roas1, struct hashable_roa *roas2, - struct deltas *deltas, enum delta_op op) + struct deltas *deltas, int op) { struct hashable_roa *n1; /* A node from @roas1 */ struct hashable_roa *n2; /* A node from @roas2 */ @@ -192,10 +191,10 @@ compute_deltas(struct roa_table *old, struct roa_table *new, if (error) return error; - error = add_deltas(new->roas, old->roas, deltas, DELTA_ADD); + error = add_deltas(new->roas, old->roas, deltas, FLAG_ANNOUNCEMENT); if (error) goto fail; - error = add_deltas(old->roas, new->roas, deltas, DELTA_RM); + error = add_deltas(old->roas, new->roas, deltas, FLAG_WITHDRAWAL); if (error) goto fail; @@ -203,6 +202,6 @@ compute_deltas(struct roa_table *old, struct roa_table *new, return 0; fail: - deltas_destroy(deltas); + deltas_put(deltas); return error; } diff --git a/src/rtr/db/vrp.h b/src/rtr/db/vrp.h index 3ffc0403..c002d928 100644 --- a/src/rtr/db/vrp.h +++ b/src/rtr/db/vrp.h @@ -7,6 +7,8 @@ #define FLAG_WITHDRAWAL 0 #define FLAG_ANNOUNCEMENT 1 +typedef uint32_t serial_t; + struct vrp { uint32_t asn; /* @@ -21,9 +23,16 @@ struct vrp { uint8_t prefix_length; uint8_t max_prefix_length; uint8_t addr_fam; - uint8_t flags; + /* uint8_t flags; */ /* TODO (now) commit remove */ +}; + +struct delta { + serial_t serial; + struct vrp vrp; + uint8_t flags; }; -typedef int (*vrp_foreach_cb)(struct vrp *, void *); +typedef int (*vrp_foreach_cb)(struct vrp const *, void *); +typedef int (*delta_foreach_cb)(struct delta const *, void *); #endif /* SRC_RTR_DB_VRP_H_ */ diff --git a/src/rtr/db/vrps.c b/src/rtr/db/vrps.c index 9e3e0882..c9a8aef4 100644 --- a/src/rtr/db/vrps.c +++ b/src/rtr/db/vrps.c @@ -1,12 +1,9 @@ #include "vrps.h" #include -#include -#include +#include #include "clients.h" #include "common.h" -#include "validation_handler.h" -#include "data_structure/array_list.h" #include "object/tal.h" #include "rtr/db/roa_table.h" @@ -17,12 +14,7 @@ #define START_SERIAL 0 -struct delta { - uint32_t serial; - struct deltas *deltas; -}; - -ARRAY_LIST(deltas_db, struct delta) +DEFINE_ARRAY_LIST_FUNCTIONS(deltas_db, struct delta_group, ) struct state { /** @@ -36,7 +28,11 @@ struct state { /** ROA changes to @base over time. */ struct deltas_db deltas; - uint32_t current_serial; + /* + * TODO (whatever) Maybe rename this. It's not really the current + * serial; it's the next one. + */ + serial_t current_serial; uint16_t v0_session_id; uint16_t v1_session_id; time_t last_modified_date; @@ -45,10 +41,10 @@ struct state { /** Read/write lock, which protects @state and its inhabitants. */ static pthread_rwlock_t lock; -static void -delta_destroy(struct delta *delta) +void +deltagroup_cleanup(struct delta_group *group) { - deltas_destroy(delta->deltas); + deltas_put(group->deltas); } int @@ -57,7 +53,6 @@ vrps_init(void) int error; state.base = NULL; - deltas_db_init(&state.deltas); /* @@ -66,7 +61,6 @@ vrps_init(void) * 'Fields of a PDU') */ state.current_serial = START_SERIAL; - /* Get the bits that'll fit in session_id */ state.v0_session_id = time(NULL) & 0xFFFF; /* Minus 1 to prevent same ID */ @@ -76,7 +70,7 @@ vrps_init(void) error = pthread_rwlock_init(&lock, NULL); if (error) { - deltas_db_cleanup(&state.deltas, delta_destroy); + deltas_db_cleanup(&state.deltas, deltagroup_cleanup); return pr_errno(error, "pthread_rwlock_init() errored"); } @@ -88,7 +82,7 @@ vrps_destroy(void) { if (state.base != NULL) roa_table_destroy(state.base); - deltas_db_cleanup(&state.deltas, delta_destroy); + deltas_db_cleanup(&state.deltas, deltagroup_cleanup); pthread_rwlock_destroy(&lock); /* Nothing to do with error code */ } @@ -140,26 +134,24 @@ __perform_standalone_validation(struct roa_table **result) return 0; } - - /** * Reallocate the array of @db starting at @start, the length and capacity are * calculated according to the new start. */ static void -resize_deltas_db(struct deltas_db *db, struct delta *start) +resize_deltas_db(struct deltas_db *db, struct delta_group *start) { - struct delta *tmp; + struct delta_group *tmp; db->len -= (start - db->array); while (db->len < db->capacity / 2) db->capacity /= 2; - tmp = malloc(sizeof(struct delta) * db->capacity); + tmp = malloc(sizeof(struct delta_group) * db->capacity); if (tmp == NULL) { pr_enomem(); return; } - memcpy(tmp, start, db->len * sizeof(struct delta)); + memcpy(tmp, start, db->len * sizeof(struct delta_group)); free(db->array); db->array = tmp; } @@ -167,22 +159,22 @@ resize_deltas_db(struct deltas_db *db, struct delta *start) static void vrps_purge(void) { - struct delta *d; + struct delta_group *group; uint32_t min_serial; min_serial = clients_get_min_serial(); /** Assume is ordered by serial, so get the new initial pointer */ - ARRAYLIST_FOREACH(&state.deltas, d) - if (d->serial >= min_serial) + ARRAYLIST_FOREACH(&state.deltas, group) + if (group->serial >= min_serial) break; /** Is the first element or reached end, nothing to purge */ - if (d == state.deltas.array || - (d - state.deltas.array) == state.deltas.len) + if (group == state.deltas.array || + (group - state.deltas.array) == state.deltas.len) return; - resize_deltas_db(&state.deltas, d); + resize_deltas_db(&state.deltas, group); } int @@ -191,7 +183,7 @@ vrps_update(bool *changed) struct roa_table *old_base; struct roa_table *new_base; struct deltas *deltas; /* Deltas in raw form */ - struct delta deltas_node; /* Deltas in database node form */ + struct delta_group deltas_node; /* Deltas in database node form */ int error; *changed = false; @@ -232,6 +224,27 @@ vrps_update(bool *changed) /** Remove unnecessary deltas */ vrps_purge(); + + } else { + /* + * 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. + */ + error = deltas_create(&deltas); + if (error) { + rwlock_unlock(&lock); + goto revert_base; + } + + deltas_node.serial = state.current_serial; + deltas_node.deltas = deltas; + error = deltas_db_add(&state.deltas, &deltas_node); + if (error) { + rwlock_unlock(&lock); + goto revert_deltas; + } } *changed = true; @@ -245,75 +258,15 @@ vrps_update(bool *changed) return 0; revert_deltas: - deltas_destroy(deltas); + deltas_put(deltas); revert_base: roa_table_destroy(new_base); return error; } -/* - * Get a status to know the difference between the delta with serial SERIAL and - * the last delta at DB. - * - * If SERIAL is received as NULL, and there's data at DB then the status will - * be DIFF_AVAILABLE. - * - * This function can only fail due to critical r/w lock bugs. - */ -int -deltas_db_status(uint32_t *serial, enum delta_status *result) -{ - struct delta *delta; - int error; - - error = rwlock_read_lock(&lock); - if (error) - return error; - - if (state.base == NULL) { - *result = DS_NO_DATA_AVAILABLE; - goto rlock_succeed; - } - - /* No serial to match, and there's data at DB */ - if (serial == NULL) { - *result = DS_DIFF_AVAILABLE; - goto rlock_succeed; - } - - /* Is the last version? */ - if (*serial == (state.current_serial - 1)) { - *result = DS_NO_DIFF; - goto rlock_succeed; - } - - /* The first serial isn't at deltas */ - if (*serial == START_SERIAL) { - *result = DS_DIFF_AVAILABLE; - goto rlock_succeed; - } - - /* Get the delta corresponding to the serial */ - ARRAYLIST_FOREACH(&state.deltas, delta) - if (delta->serial == *serial) { - *result = DS_DIFF_AVAILABLE; - goto rlock_succeed; - } - - /* No match yet, release lock */ - rwlock_unlock(&lock); - - /* Reached end, diff can't be determined */ - *result = DS_DIFF_UNDETERMINED; - return 0; - -rlock_succeed: - rwlock_unlock(&lock); - return 0; -} - +/* TODO (whatever) @serial is a dumb hack. */ int -vrps_foreach_base_roa(vrp_foreach_cb cb, void *arg) +vrps_foreach_base_roa(vrp_foreach_cb cb, void *arg, serial_t *serial) { int error; @@ -321,18 +274,36 @@ vrps_foreach_base_roa(vrp_foreach_cb cb, void *arg) if (error) return error; - if (state.base != NULL) + if (state.base != NULL) { error = roa_table_foreach_roa(state.base, cb, arg); + *serial = state.current_serial - 1; + } else { + error = -EAGAIN; + } rwlock_unlock(&lock); return error; } +/** + * Adds to @result the deltas whose serial > @from. + * + * The result code is one of the following: + * + * 0: No errors. + * -EAGAIN: No data available; database still under construction. + * -ESRCH: @from was not found. + * Anything else: Generic fail. + * + * As usual, only 0 guarantees valid out parameters. (@to and @result.) + * (But note that @result is supposed to be already initialized, so caller will + * have to clean it up regardless of error.) + */ int -vrps_foreach_delta_roa(uint32_t from, uint32_t to, vrp_foreach_cb cb, void *arg) +vrps_get_deltas_from(serial_t from, serial_t *to, struct deltas_db *result) { - struct delta *d; + struct delta_group *group; bool from_found; int error; @@ -342,27 +313,36 @@ vrps_foreach_delta_roa(uint32_t from, uint32_t to, vrp_foreach_cb cb, void *arg) if (error) return error; - ARRAYLIST_FOREACH(&state.deltas, d) { + if (state.base == NULL) { + rwlock_unlock(&lock); + return -EAGAIN; + } + + ARRAYLIST_FOREACH(&state.deltas, group) { if (!from_found) { - if (d->serial > from) + if (group->serial == from) { from_found = true; - else - continue; + *to = group->serial; + } + continue; } - if (d->serial > to) - break; - error = deltas_foreach(d->deltas, cb, arg); - if (error) - break; + + error = deltas_db_add(result, group); + if (error) { + rwlock_unlock(&lock); + return error; + } + + deltas_get(group->deltas); + *to = group->serial; } rwlock_unlock(&lock); - - return error; + return from_found ? 0 : -ESRCH; } int -get_last_serial_number(uint32_t *result) +get_last_serial_number(serial_t *result) { int error; @@ -370,11 +350,14 @@ get_last_serial_number(uint32_t *result) if (error) return error; - *result = state.current_serial - 1; + if (state.base != NULL) + *result = state.current_serial - 1; + else + error = -EAGAIN; rwlock_unlock(&lock); - return 0; + return error; } uint16_t diff --git a/src/rtr/db/vrps.h b/src/rtr/db/vrps.h index 173cd37f..631f0c52 100644 --- a/src/rtr/db/vrps.h +++ b/src/rtr/db/vrps.h @@ -2,29 +2,36 @@ #define SRC_VRPS_H_ #include -#include "rtr/db/vrp.h" - -enum delta_status { - /** There's no data at the DB */ - DS_NO_DATA_AVAILABLE, - /** The diff can't be determined */ - DS_DIFF_UNDETERMINED, - /** There's no difference */ - DS_NO_DIFF, - /** There are diffs between SERIAL and the last DB serial */ - DS_DIFF_AVAILABLE, +#include "data_structure/array_list.h" +#include "rtr/db/delta.h" + +/* + * Deltas that share a serial. + */ +struct delta_group { + serial_t serial; + struct deltas *deltas; }; +void deltagroup_cleanup(struct delta_group *); + +DEFINE_ARRAY_LIST_STRUCT(deltas_db, struct delta_group); +DECLARE_ARRAY_LIST_FUNCTIONS(deltas_db, struct delta_group) + int vrps_init(void); void vrps_destroy(void); int vrps_update(bool *); -int deltas_db_status(uint32_t *, enum delta_status *); -int vrps_foreach_base_roa(vrp_foreach_cb, void *); -int vrps_foreach_delta_roa(uint32_t, uint32_t, vrp_foreach_cb, void *); +/* + * The following three functions return -EAGAIN when vrps_update() has never + * been called, or while it's still building the database. + * Handle gracefully. + */ +int vrps_foreach_base_roa(vrp_foreach_cb, void *, serial_t *); +int vrps_get_deltas_from(serial_t, serial_t *, struct deltas_db *); +int get_last_serial_number(serial_t *); -int get_last_serial_number(uint32_t *); uint16_t get_current_session_id(uint8_t); #endif /* SRC_VRPS_H_ */ diff --git a/src/rtr/err_pdu.c b/src/rtr/err_pdu.c index c18b2302..06db7b2d 100644 --- a/src/rtr/err_pdu.c +++ b/src/rtr/err_pdu.c @@ -4,6 +4,11 @@ #include "pdu_sender.h" #include "log.h" +/* + * TODO (urgent) According to the function below, NO_DATA_AVAILABLE is not + * fatal. However, some callers of this function are terminating the connection + * regardless of that. + */ int err_pdu_send(int fd, uint8_t version, uint16_t code, void *err_pdu_header, char const *message) diff --git a/src/rtr/pdu.h b/src/rtr/pdu.h index 54424bed..103a21f0 100644 --- a/src/rtr/pdu.h +++ b/src/rtr/pdu.h @@ -9,14 +9,18 @@ #define RTR_V0 0 #define RTR_V1 1 -#define PDU_TYPE_SERIAL_NOTIFY 0 -#define PDU_TYPE_CACHE_RESPONSE 3 -#define PDU_TYPE_IPV4_PREFIX 4 -#define PDU_TYPE_IPV6_PREFIX 6 -#define PDU_TYPE_END_OF_DATA 7 -#define PDU_TYPE_CACHE_RESET 8 -#define PDU_TYPE_ROUTER_KEY 9 -#define PDU_TYPE_ERROR_REPORT 10 +enum pdu_type { + PDU_TYPE_SERIAL_NOTIFY = 0, + PDU_TYPE_SERIAL_QUERY = 1, + PDU_TYPE_RESET_QUERY = 2, + PDU_TYPE_CACHE_RESPONSE = 3, + PDU_TYPE_IPV4_PREFIX = 4, + PDU_TYPE_IPV6_PREFIX = 6, + PDU_TYPE_END_OF_DATA = 7, + PDU_TYPE_CACHE_RESET = 8, + PDU_TYPE_ROUTER_KEY = 9, + PDU_TYPE_ERROR_REPORT = 10, +}; struct pdu_header { uint8_t protocol_version; diff --git a/src/rtr/pdu_handler.c b/src/rtr/pdu_handler.c index c7fd219e..462c6c81 100644 --- a/src/rtr/pdu_handler.c +++ b/src/rtr/pdu_handler.c @@ -16,7 +16,7 @@ warn_unexpected_pdu(int fd, void *pdu, char const *pdu_name) struct pdu_header *pdu_header = pdu; pr_warn("Unexpected %s PDU received", pdu_name); err_pdu_send(fd, pdu_header->protocol_version, ERR_PDU_UNSUP_PDU_TYPE, - pdu_header, "Unexpected PDU received"); + pdu_header, "PDU is unexpected or out of order."); return -EINVAL; } @@ -26,136 +26,124 @@ handle_serial_notify_pdu(int fd, void *pdu) return warn_unexpected_pdu(fd, pdu, "Serial Notify"); } -static int -send_commmon_exchange(struct sender_common *common, - int (*pdu_sender)(struct sender_common *)) +int +handle_serial_query_pdu(int fd, void *pdu) { + struct serial_query_pdu *received = pdu; + struct sender_common common; + struct deltas_db deltas; + serial_t final_serial; int error; + init_sender_common(&common, fd, received->header.protocol_version); + /* + * RFC 6810 and 8210: + * "If [...] either the router or the cache finds that the value of the + * Session ID is not the same as the other's, the party which detects + * the mismatch MUST immediately terminate the session with an Error + * Report PDU with code 0 ("Corrupt Data")" + */ + if (received->header.m.session_id != common.session_id) + return err_pdu_send(fd, common.version, ERR_PDU_CORRUPT_DATA, + &received->header, "Session ID doesn't match."); + /* - * TODO (urgent) On certain errors, shouldn't we send error PDUs or + * TODO (now) On certain errors, shouldn't we send error PDUs or * something? */ - /* Send Cache response PDU */ - error = send_cache_response_pdu(common); + /* + * For the record, there are two reasons why we want to work on a + * (shallow) copy of the deltas (as opposed to eg. a foreach): + * 1. We need to remove deltas that cancel each other. + * (Which can't be done directly on the DB.) + * 2. It's probably best not to hold the VRPS read lock while writing + * PDUs, to minimize writer stagnation. + */ + + deltas_db_init(&deltas); + error = vrps_get_deltas_from(received->serial_number, &final_serial, + &deltas); + if (error == -EAGAIN) { + error = err_pdu_send(fd, common.version, + ERR_PDU_NO_DATA_AVAILABLE, NULL, NULL); + goto end; + } + if (error == -ESRCH) { + /* https://tools.ietf.org/html/rfc6810#section-6.3 */ + error = send_cache_reset_pdu(&common); + goto end; + } if (error) - return error; + goto end; - /* Send Payload PDUs */ - error = pdu_sender(common); + /* + * https://tools.ietf.org/html/rfc6810#section-6.2 + * (Except the end of data PDU.) + */ + + error = send_cache_response_pdu(&common); if (error) - return error; + goto end; + error = send_pdus_delta(&deltas, &common); + if (error) + goto end; /* TODO (now) maybe send something? */ + error = send_end_of_data_pdu(&common, final_serial); - /* Send End of data PDU */ - return send_end_of_data_pdu(common); +end: + deltas_db_cleanup(&deltas, deltagroup_cleanup); + return error; } -/* - * TODO (urgent) The semaphoring is bonkers. The code keeps locking, storing a - * value, unlocking, locking again, and using the old value. - * It doesn't look like it's a problem for now, but eventually will be, when old - * delta forgetting is implemented. - * I'm going to defer this because it shouldn't be done during the merge. - */ -int -handle_serial_query_pdu(int fd, void *pdu) -{ - struct serial_query_pdu *received = pdu; +struct base_roa_args { + bool started; struct sender_common common; + serial_t last_serial; +}; + +static int +send_base_roa(struct vrp const *vrp, void *arg) +{ + struct base_roa_args *args = arg; int error; - enum delta_status updates; - uint32_t current_serial; - uint16_t session_id; - uint8_t version; - /* - * RFC 6810 and 8210: - * "If [...] either the router or the cache finds that the value of the - * Session ID is not the same as the other's, the party which detects - * the mismatch MUST immediately terminate the session with an Error - * Report PDU with code 0 ("Corrupt Data")" - */ - version = received->header.protocol_version; - session_id = get_current_session_id(version); - if (received->header.m.session_id != session_id) - return err_pdu_send(fd, version, ERR_PDU_CORRUPT_DATA, - &received->header, NULL); - if (get_last_serial_number(¤t_serial) != 0) - goto critical; - - init_sender_common(&common, fd, version, &session_id, - &received->serial_number, ¤t_serial); - - if (deltas_db_status(common.start_serial, &updates) != 0) - goto critical; - - switch (updates) { - case DS_NO_DATA_AVAILABLE: - /* https://tools.ietf.org/html/rfc8210#section-8.4 */ - return err_pdu_send(fd, version, ERR_PDU_NO_DATA_AVAILABLE, - NULL, NULL); - case DS_DIFF_UNDETERMINED: - /* https://tools.ietf.org/html/rfc8210#section-8.3 */ - return send_cache_reset_pdu(&common); - case DS_DIFF_AVAILABLE: - /* https://tools.ietf.org/html/rfc8210#section-8.2 */ - return send_commmon_exchange(&common, send_pdus_delta); - case DS_NO_DIFF: - /* Typical exchange with no Payloads */ - error = send_cache_response_pdu(&common); + if (!args->started) { + error = send_cache_response_pdu(&args->common); if (error) return error; - return send_end_of_data_pdu(&common); + args->started = true; } - pr_warn("Reached 'unreachable' code"); - return -EINVAL; - -critical: - return err_pdu_send(fd, version, ERR_PDU_INTERNAL_ERROR, - &received->header, NULL); + /* TODO (now) maybe send something on error? */ + return send_prefix_pdu(&args->common, vrp, FLAG_ANNOUNCEMENT); } int handle_reset_query_pdu(int fd, void *pdu) { struct reset_query_pdu *received = pdu; - struct sender_common common; - uint32_t current_serial; - uint16_t session_id; - uint8_t version; - enum delta_status updates; - - version = received->header.protocol_version; - session_id = get_current_session_id(version); - if (get_last_serial_number(¤t_serial) != 0) - goto critical; - - init_sender_common(&common, fd, version, &session_id, NULL, - ¤t_serial); - - if (deltas_db_status(NULL, &updates) != 0) - goto critical; - switch (updates) { - case DS_NO_DATA_AVAILABLE: - /* https://tools.ietf.org/html/rfc8210#section-8.4 */ - return err_pdu_send(fd, version, ERR_PDU_NO_DATA_AVAILABLE, - NULL, NULL); - case DS_DIFF_AVAILABLE: - /* https://tools.ietf.org/html/rfc8210#section-8.1 */ - return send_commmon_exchange(&common, send_pdus_base); - case DS_DIFF_UNDETERMINED: - case DS_NO_DIFF: - break; - } + struct base_roa_args args; + serial_t current_serial; + int error; - pr_warn("Reached 'unreachable' code"); - return -EINVAL; + args.started = false; + init_sender_common(&args.common, fd, received->header.protocol_version); + + /* + * It's probably best not to work on a copy, because the tree is large. + * Unfortunately, this means we'll have to encourage writer stagnation, + * but most clients are supposed to request far more serial queries than + * reset queries. + */ + + error = vrps_foreach_base_roa(send_base_roa, &args, ¤t_serial); + if (error == -EAGAIN) + return err_pdu_send(fd, args.common.version, + ERR_PDU_NO_DATA_AVAILABLE, NULL, NULL); + if (error) + return error; -critical: - return err_pdu_send(fd, version, ERR_PDU_INTERNAL_ERROR, - &received->header, NULL); + return send_end_of_data_pdu(&args.common, current_serial); } int diff --git a/src/rtr/pdu_sender.c b/src/rtr/pdu_sender.c index 3dd12cbf..827a7bc0 100644 --- a/src/rtr/pdu_sender.c +++ b/src/rtr/pdu_sender.c @@ -21,7 +21,7 @@ struct vrp_node { - struct vrp vrp; + struct delta delta; SLIST_ENTRY(vrp_node) next; }; @@ -29,14 +29,11 @@ struct vrp_node { SLIST_HEAD(vrp_slist, vrp_node); void -init_sender_common(struct sender_common *common, int fd, uint8_t version, - uint16_t *session_id, uint32_t *start_serial, uint32_t *end_serial) +init_sender_common(struct sender_common *common, int fd, uint8_t version) { common->fd = fd; common->version = version; - common->session_id = session_id == NULL ? 0 : session_id; - common->start_serial = start_serial; - common->end_serial = end_serial; + common->session_id = get_current_session_id(version); } /* * Set all the header values, EXCEPT length field. @@ -160,16 +157,16 @@ send_response(int fd, unsigned char *data, size_t data_len) } int -send_serial_notify_pdu(struct sender_common *common) +send_serial_notify_pdu(struct sender_common *common, serial_t start_serial) { struct serial_notify_pdu pdu; unsigned char data[BUFFER_SIZE]; size_t len; set_header_values(&pdu.header, common->version, PDU_TYPE_SERIAL_NOTIFY, - *common->session_id); + common->session_id); - pdu.serial_number = *common->start_serial; + pdu.serial_number = start_serial; pdu.header.length = length_serial_notify_pdu(&pdu); len = serialize_serial_notify_pdu(&pdu, data); @@ -201,8 +198,8 @@ send_cache_response_pdu(struct sender_common *common) size_t len; /* This PDU has only the header */ - set_header_values(&pdu.header, common->version, - PDU_TYPE_CACHE_RESPONSE, *common->session_id); + set_header_values(&pdu.header, common->version, PDU_TYPE_CACHE_RESPONSE, + common->session_id); pdu.header.length = HEADER_LENGTH; len = serialize_cache_response_pdu(&pdu, data); @@ -211,7 +208,8 @@ send_cache_response_pdu(struct sender_common *common) } static int -send_ipv4_prefix_pdu(struct sender_common *common, struct vrp *vrp) +send_ipv4_prefix_pdu(struct sender_common *common, struct vrp const *vrp, + uint8_t flags) { struct ipv4_prefix_pdu pdu; unsigned char data[BUFFER_SIZE]; @@ -220,7 +218,7 @@ send_ipv4_prefix_pdu(struct sender_common *common, struct vrp *vrp) set_header_values(&pdu.header, common->version, PDU_TYPE_IPV4_PREFIX, 0); - pdu.flags = vrp->flags; + pdu.flags = flags; pdu.prefix_length = vrp->prefix_length; pdu.max_length = vrp->max_prefix_length; pdu.zero = 0; @@ -234,7 +232,8 @@ send_ipv4_prefix_pdu(struct sender_common *common, struct vrp *vrp) } static int -send_ipv6_prefix_pdu(struct sender_common *common, struct vrp *vrp) +send_ipv6_prefix_pdu(struct sender_common *common, struct vrp const *vrp, + uint8_t flags) { struct ipv6_prefix_pdu pdu; unsigned char data[BUFFER_SIZE]; @@ -243,7 +242,7 @@ send_ipv6_prefix_pdu(struct sender_common *common, struct vrp *vrp) set_header_values(&pdu.header, common->version, PDU_TYPE_IPV6_PREFIX, 0); - pdu.flags = vrp->flags; + pdu.flags = flags; pdu.prefix_length = vrp->prefix_length; pdu.max_length = vrp->max_prefix_length; pdu.zero = 0; @@ -257,26 +256,21 @@ send_ipv6_prefix_pdu(struct sender_common *common, struct vrp *vrp) } int -send_prefix_pdu(struct vrp *vrp, void *arg) +send_prefix_pdu(struct sender_common *common, struct vrp const *vrp, + uint8_t flags) { switch (vrp->addr_fam) { case AF_INET: - return send_ipv4_prefix_pdu(arg, vrp); + return send_ipv4_prefix_pdu(common, vrp, flags); case AF_INET6: - return send_ipv6_prefix_pdu(arg, vrp); + return send_ipv6_prefix_pdu(common, vrp, flags); } return -EINVAL; } -int -send_pdus_base(struct sender_common *common) -{ - return vrps_foreach_base_roa(send_prefix_pdu, common); -} - static bool -vrp_equals(struct vrp *left, struct vrp *right) +vrp_equals(struct vrp const *left, struct vrp const *right) { return left->asn == right->asn && left->addr_fam == right->addr_fam @@ -289,18 +283,27 @@ vrp_equals(struct vrp *left, struct vrp *right) right->prefix.v6.s6_addr32))); } +static int +vrp_simply_send(struct delta const *delta, void *arg) +{ + return send_prefix_pdu(arg, &delta->vrp, delta->flags); +} + /** - * Remove the announcements/withdrawals that override each other + * Remove the announcements/withdrawals that override each other. + * + * (Note: We're assuming the array is already duplicateless enough thanks to the + * hash table.) */ static int -vrp_ovrd_remove(struct vrp *vrp, void *arg) +vrp_ovrd_remove(struct delta const *delta, void *arg) { struct vrp_node *ptr; struct vrp_slist *filtered_vrps = arg; SLIST_FOREACH(ptr, filtered_vrps, next) - if (vrp_equals(vrp, &ptr->vrp) && - vrp->flags != ptr->vrp.flags) { + if (vrp_equals(&delta->vrp, &ptr->delta.vrp) && + delta->flags != ptr->delta.flags) { SLIST_REMOVE(filtered_vrps, ptr, vrp_node, next); free(ptr); return 0; @@ -310,27 +313,46 @@ vrp_ovrd_remove(struct vrp *vrp, void *arg) if (ptr == NULL) return pr_enomem(); - ptr->vrp = *vrp; + ptr->delta = *delta; SLIST_INSERT_HEAD(filtered_vrps, ptr, next); return 0; } int -send_pdus_delta(struct sender_common *common) +send_pdus_delta(struct deltas_db *deltas, struct sender_common *common) { struct vrp_slist filtered_vrps; + struct delta_group *group; struct vrp_node *ptr; int error; + /* + * Short circuit: Entries that share serial are already guaranteed to + * not contradict each other, so no filtering required. + */ + if (deltas->len == 1) { + group = &deltas->array[0]; + return deltas_foreach(group->serial, group->deltas, + vrp_simply_send, common); + } + + /* + * Filter: Remove entries that cancel each other. + * (We'll have to build a separate list because the database nodes + * are immutable.) + */ SLIST_INIT(&filtered_vrps); - error = vrps_foreach_delta_roa(*common->start_serial, - *common->end_serial, vrp_ovrd_remove, &filtered_vrps); - if (error) - goto release_list; + ARRAYLIST_FOREACH(deltas, group) { + error = deltas_foreach(group->serial, group->deltas, + vrp_ovrd_remove, &filtered_vrps); + if (error) + goto release_list; + } - /** Now send the filtered deltas */ + /* Now send the filtered deltas */ SLIST_FOREACH(ptr, &filtered_vrps, next) { - error = send_prefix_pdu(&ptr->vrp, common); + error = send_prefix_pdu(common, &ptr->delta.vrp, + ptr->delta.flags); if (error) break; } @@ -346,7 +368,7 @@ release_list: } int -send_end_of_data_pdu(struct sender_common *common) +send_end_of_data_pdu(struct sender_common *common, serial_t end_serial) { struct end_of_data_pdu pdu; unsigned char data[BUFFER_SIZE]; @@ -354,8 +376,8 @@ send_end_of_data_pdu(struct sender_common *common) int error; set_header_values(&pdu.header, common->version, PDU_TYPE_END_OF_DATA, - *common->session_id); - pdu.serial_number = *common->end_serial; + common->session_id); + pdu.serial_number = end_serial; if (common->version == RTR_V1) { pdu.refresh_interval = config_get_refresh_interval(); pdu.retry_interval = config_get_retry_interval(); @@ -381,8 +403,7 @@ struct pdu_header *err_pdu_header, char const *message) unsigned char data[BUFFER_SIZE]; size_t len; - set_header_values(&pdu.header, version, PDU_TYPE_ERROR_REPORT, - code); + set_header_values(&pdu.header, version, PDU_TYPE_ERROR_REPORT, code); pdu.error_pdu_length = 0; pdu.erroneous_pdu = (void *)err_pdu_header; diff --git a/src/rtr/pdu_sender.h b/src/rtr/pdu_sender.h index 94b6f196..4464a7a2 100644 --- a/src/rtr/pdu_sender.h +++ b/src/rtr/pdu_sender.h @@ -2,24 +2,22 @@ #define SRC_RTR_PDU_SENDER_H_ #include "pdu.h" +#include "rtr/db/vrps.h" struct sender_common { int fd; uint8_t version; - uint16_t *session_id; - uint32_t *start_serial; - uint32_t *end_serial; + uint16_t session_id; }; -void init_sender_common(struct sender_common *, int, uint8_t, uint16_t *, - uint32_t *, uint32_t *); +void init_sender_common(struct sender_common *, int, uint8_t); -int send_serial_notify_pdu(struct sender_common *); +int send_serial_notify_pdu(struct sender_common *, serial_t); int send_cache_reset_pdu(struct sender_common *); int send_cache_response_pdu(struct sender_common *); -int send_pdus_base(struct sender_common *); -int send_pdus_delta(struct sender_common *); -int send_end_of_data_pdu(struct sender_common *); +int send_prefix_pdu(struct sender_common *, struct vrp const *, uint8_t); +int send_pdus_delta(struct deltas_db *, struct sender_common *); +int send_end_of_data_pdu(struct sender_common *, serial_t); int send_error_report_pdu(int, uint8_t, uint16_t, struct pdu_header *, char const *); diff --git a/src/rtr/rtr.c b/src/rtr/rtr.c index b2cf7fdc..8bc26276 100644 --- a/src/rtr/rtr.c +++ b/src/rtr/rtr.c @@ -189,8 +189,7 @@ client_thread_cb(void *param_void) /* Protocol Version Negotiation */ if (rtr_version != RTR_VERSION_SUPPORTED) { err_pdu_send(param->client_fd, RTR_VERSION_SUPPORTED, - ERR_PDU_UNSUP_PROTO_VERSION, - (struct pdu_header *) pdu, NULL); + ERR_PDU_UNSUP_PROTO_VERSION, pdu, NULL); return end_client(param->client_fd, meta, pdu); } /* RTR Version ready, now update client */ @@ -199,10 +198,10 @@ client_thread_cb(void *param_void) if (err) { if (err == -ERTR_VERSION_MISMATCH) { err_pdu_send(param->client_fd, rtr_version, - (rtr_version == RTR_V0 - ? ERR_PDU_UNSUP_PROTO_VERSION - : ERR_PDU_UNEXPECTED_PROTO_VERSION), - (struct pdu_header *) pdu, NULL); + (rtr_version == RTR_V0) + ? ERR_PDU_UNSUP_PROTO_VERSION + : ERR_PDU_UNEXPECTED_PROTO_VERSION, + pdu, NULL); } return end_client(param->client_fd, meta, pdu); } diff --git a/test/Makefile.am b/test/Makefile.am index 6cb82fb6..2873d3ee 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -23,10 +23,12 @@ BASIC_MODULES += impersonator.c check_PROGRAMS = address.test check_PROGRAMS += clients.test check_PROGRAMS += line_file.test +check_PROGRAMS += pdu_handler.test check_PROGRAMS += rsync.test check_PROGRAMS += roa_table.test #check_PROGRAMS += tal.test check_PROGRAMS += vcard.test +check_PROGRAMS += vrps.test #check_PROGRAMS += rtr/pdu.test #check_PROGRAMS += rtr/primitive_reader.test TESTS = ${check_PROGRAMS} @@ -48,6 +50,17 @@ line_file_test_SOURCES += ../src/line_file.c ../src/line_file.h line_file_test_SOURCES += line_file_test.c line_file_test_LDADD = ${MY_LDADD} +pdu_handler_test_SOURCES = ${BASIC_MODULES} +pdu_handler_test_SOURCES += ../src/common.c +pdu_handler_test_SOURCES += ../src/rtr/pdu_handler.c +pdu_handler_test_SOURCES += ../src/rtr/err_pdu.c +pdu_handler_test_SOURCES += ../src/rtr/db/delta.c +pdu_handler_test_SOURCES += ../src/rtr/db/roa_table.c +pdu_handler_test_SOURCES += ../src/rtr/db/vrps.c +pdu_handler_test_SOURCES += rtr/db/impersonator.c +pdu_handler_test_SOURCES += rtr/pdu_handler_test.c +pdu_handler_test_LDADD = ${MY_LDADD} + roa_table_test_SOURCES = ${BASIC_MODULES} roa_table_test_SOURCES += ../src/rtr/db/delta.c roa_table_test_SOURCES += ../src/rtr/db/roa_table.c @@ -74,6 +87,15 @@ vcard_test_SOURCES = ${BASIC_MODULES} vcard_test_SOURCES += vcard_test.c vcard_test_LDADD = ${MY_LDADD} +vrps_test_SOURCES = ${BASIC_MODULES} +vrps_test_SOURCES += ../src/common.c +vrps_test_SOURCES += ../src/rtr/db/delta.c +vrps_test_SOURCES += ../src/rtr/db/roa_table.c +vrps_test_SOURCES += ../src/rtr/db/vrps.c +vrps_test_SOURCES += rtr/db/impersonator.c +vrps_test_SOURCES += rtr/db/vrps_test.c +vrps_test_LDADD = ${MY_LDADD} + #rtr_pdu_test_SOURCES = ${BASIC_MODULES} #rtr_pdu_test_SOURCES += rtr/pdu_test.c #rtr_pdu_test_SOURCES += rtr/stream.c diff --git a/test/rtr/db/impersonator.c b/test/rtr/db/impersonator.c new file mode 100644 index 00000000..3e2f3a95 --- /dev/null +++ b/test/rtr/db/impersonator.c @@ -0,0 +1,58 @@ +#include "object/tal.h" + +#include + +static int iteration = 0; + +static void +add_v4(struct validation_handler *handler, 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)); +} + +static void +add_v6(struct validation_handler *handler, uint32_t as) +{ + struct ipv6_prefix prefix; + prefix.addr.s6_addr32[0] = htonl(0x20010DB8); + prefix.addr.s6_addr32[1] = 0; + prefix.addr.s6_addr32[2] = 0; + prefix.addr.s6_addr32[3] = 0; + prefix.len = 96; + ck_assert_int_eq(0, handler->handle_roa_v6(as, &prefix, 120, + handler->arg)); +} + +int +perform_standalone_validation(struct validation_handler *handler) +{ + ck_assert_int_eq(0, handler->reset(handler->arg)); + + switch (iteration) { + case 0: + add_v4(handler, 0); + add_v6(handler, 0); + break; + case 1: + add_v4(handler, 0); + add_v6(handler, 0); + add_v4(handler, 1); + add_v6(handler, 1); + break; + case 2: + case 3: + add_v4(handler, 1); + add_v6(handler, 1); + break; + default: + ck_abort_msg("perform_standalone_validation() was called too many times (%d).", + iteration); + } + + iteration++; + return 0; +} diff --git a/test/rtr/db/roa_table_test.c b/test/rtr/db/roa_table_test.c index 6b3850f8..7b11577f 100644 --- a/test/rtr/db/roa_table_test.c +++ b/test/rtr/db/roa_table_test.c @@ -11,8 +11,8 @@ static bool roas_found[TOTAL_ROAS]; static unsigned int total_found; static bool -vrp_equals_v4(struct vrp *vrp, uint8_t as, uint32_t addr, uint8_t prefix_len, - uint8_t max_prefix_len) +vrp_equals_v4(struct vrp const *vrp, uint8_t as, uint32_t addr, + uint8_t prefix_len, uint8_t max_prefix_len) { return (AF_INET == vrp->addr_fam) && (as == vrp->asn) @@ -22,8 +22,8 @@ vrp_equals_v4(struct vrp *vrp, uint8_t as, uint32_t addr, uint8_t prefix_len, } static bool -vrp_equals_v6(struct vrp *vrp, uint8_t as, uint32_t addr, uint8_t prefix_len, - uint8_t max_prefix_len) +vrp_equals_v6(struct vrp const *vrp, uint8_t as, uint32_t addr, + uint8_t prefix_len, uint8_t max_prefix_len) { return (AF_INET6 == vrp->addr_fam) && (as == vrp->asn) @@ -45,7 +45,7 @@ update_found(array_index index) } static int -foreach_cb(struct vrp *vrp, void *arg) +foreach_cb(struct vrp const *vrp, void *arg) { char const *str; @@ -156,7 +156,7 @@ START_TEST(test_basic) for (i = 0; i < TOTAL_ROAS; i++) ck_assert_int_eq(true, roas_found[i]); - roa_table_put(table); + roa_table_destroy(table); } END_TEST diff --git a/test/rtr/db/vrps_test.c b/test/rtr/db/vrps_test.c new file mode 100644 index 00000000..c6dfccb0 --- /dev/null +++ b/test/rtr/db/vrps_test.c @@ -0,0 +1,254 @@ +#include +#include +#include + +#include "common.h" +#include "thread_var.h" +#include "validation_handler.h" +#include "rtr/db/vrps.h" + +/* -- Expected database descriptors -- */ + +/* + * BASE + * 0: IPv4, ASN 0 + * 1: IPv4, ASN 1 + * 2: IPv6, ASN 0 + * 3: IPv6, ASN 1 + */ +static const bool iteration0_base[] = { 1, 0, 1, 0, }; +static const bool iteration1_base[] = { 1, 1, 1, 1, }; +static const bool iteration2_base[] = { 0, 1, 0, 1, }; + +/* + * DELTA + * 0: Withdrawal, IPv4, ASN 0 4: Announcement, IPv4, ASN 0 + * 1: Withdrawal, IPv4, ASN 1 5: Announcement, IPv4, ASN 1 + * 2: Withdrawal, IPv6, ASN 0 6: Announcement, IPv6, ASN 0 + * 3: Withdrawal, IPv6, ASN 1 7: Announcement, IPv6, ASN 1 + */ + +static const bool deltas_0to0[] = { 0, 0, 0, 0, 0, 0, 0, 0, }; + +static const bool deltas_0to1[] = { 0, 0, 0, 0, 0, 1, 0, 1, }; +static const bool deltas_1to1[] = { 0, 0, 0, 0, 0, 0, 0, 0, }; + +static const bool deltas_0to2[] = { 1, 0, 1, 0, 0, 1, 0, 1, }; +static const bool deltas_1to2[] = { 1, 0, 1, 0, 0, 0, 0, 0, }; +static const bool deltas_2to2[] = { 0, 0, 0, 0, 0, 0, 0, 0, }; + +/* Impersonator functions */ + +serial_t +clients_get_min_serial(void) +{ + return 0; +} + +/* Test functions */ + +static int +vrp_fail(struct vrp const *vrp, void *arg) +{ + char const *addr; + + switch (vrp->addr_fam) { + case AF_INET: + addr = v4addr2str(&vrp->prefix.v4); + break; + case AF_INET6: + addr = v6addr2str(&vrp->prefix.v6); + break; + default: + addr = "unknown"; + } + + ck_abort_msg("Expected no callbacks, got VRP %u/%s/%u/%u.", + vrp->asn, addr, vrp->prefix_length, vrp->max_prefix_length); +} + +static array_index +get_vrp_index(struct vrp const *vrp) +{ + array_index family_bit; + + switch (vrp->addr_fam) { + case AF_INET: + ck_assert_uint_eq(htonl(0xC0000200), vrp->prefix.v4.s_addr); + ck_assert_uint_eq(24, vrp->prefix_length); + ck_assert_uint_eq(32, vrp->max_prefix_length); + family_bit = 0; + break; + + case AF_INET6: + ck_assert_uint_eq(htonl(0x20010DB8), vrp->prefix.v6.s6_addr32[0]); + ck_assert_uint_eq(0, vrp->prefix.v6.s6_addr32[1]); + ck_assert_uint_eq(0, vrp->prefix.v6.s6_addr32[2]); + ck_assert_uint_eq(0, vrp->prefix.v6.s6_addr32[3]); + ck_assert_uint_eq(96, vrp->prefix_length); + ck_assert_uint_eq(120, vrp->max_prefix_length); + family_bit = 1; + break; + + default: + ck_abort_msg("VRP has unknown protocol: %u", vrp->addr_fam); + } + + ck_assert_msg(vrp->asn <= 1, "Unexpected AS number: %u", vrp->asn); + + return (family_bit << 1) | (vrp->asn << 0); +} + +static array_index +get_delta_index(struct delta const *delta) +{ + array_index result; + + result = get_vrp_index(&delta->vrp); + ck_assert_msg(delta->flags <= 1, "Unexpected flags: %u", delta->flags); + + return (delta->flags << 2) | result; +} + +static int +vrp_check(struct vrp const *vrp, void *arg) +{ + bool *array = arg; + array_index index; + + index = get_vrp_index(vrp); + ck_assert_uint_eq(false, array[index]); + array[index] = true; + + return 0; +} + +static int +delta_check(struct delta const *delta, void *arg) +{ + bool *array = arg; + array_index index; + + index = get_delta_index(delta); + ck_assert_uint_eq(false, array[index]); + array[index] = true; + + return 0; + +} + +static void +check_serial(serial_t expected_serial) +{ + serial_t actual_serial; + ck_assert_int_eq(0, get_last_serial_number(&actual_serial)); + ck_assert_uint_eq(expected_serial, actual_serial); +} + +static void +check_base(serial_t expected_serial, bool const *expected_base) +{ + serial_t actual_serial; + bool actual_base[4]; + array_index i; + + memset(actual_base, 0, sizeof(actual_base)); + ck_assert_int_eq(0, vrps_foreach_base_roa(vrp_check, actual_base, + &actual_serial)); + ck_assert_uint_eq(expected_serial, actual_serial); + for (i = 0; i < ARRAY_LEN(actual_base); i++) + ck_assert_uint_eq(expected_base[i], actual_base[i]); +} + +static void +check_deltas(serial_t from, serial_t to, bool const *expected_deltas) +{ + serial_t actual_serial; + bool actual_deltas[8]; + struct deltas_db deltas; + struct delta_group *group; + array_index i; + + deltas_db_init(&deltas); + ck_assert_int_eq(0, vrps_get_deltas_from(from, &actual_serial, + &deltas)); + ck_assert_uint_eq(to, actual_serial); + + memset(actual_deltas, 0, sizeof(actual_deltas)); + ARRAYLIST_FOREACH(&deltas, group) + ck_assert_int_eq(0, deltas_foreach(group->serial, group->deltas, + delta_check, actual_deltas)); + for (i = 0; i < ARRAY_LEN(actual_deltas); i++) + ck_assert_uint_eq(expected_deltas[i], actual_deltas[i]); +} + +START_TEST(test_basic) +{ + struct deltas_db deltas; + serial_t serial; + bool changed; + bool iterated_entries[8]; + + deltas_db_init(&deltas); + + ck_assert_int_eq(0, vrps_init()); + + /* First validation not yet performed: Tell routers to wait */ + ck_assert_int_eq(-EAGAIN, get_last_serial_number(&serial)); + ck_assert_int_eq(-EAGAIN, vrps_foreach_base_roa(vrp_fail, + iterated_entries, &serial)); + ck_assert_int_eq(-EAGAIN, vrps_get_deltas_from(0, &serial, &deltas)); + + /* 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); + + /* 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); + check_deltas(1, 1, deltas_1to1); + + /* 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); + check_deltas(1, 2, deltas_1to2); + check_deltas(2, 2, deltas_2to2); + + vrps_destroy(); +} +END_TEST + +Suite *pdu_suite(void) +{ + Suite *suite; + TCase *core; + + core = tcase_create("Core"); + tcase_add_test(core, test_basic); + + suite = suite_create("VRP Database"); + suite_add_tcase(suite, core); + return suite; +} + +int main(void) +{ + Suite *suite; + SRunner *runner; + int tests_failed; + + suite = pdu_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; +} diff --git a/test/rtr/pdu_handler_test.c b/test/rtr/pdu_handler_test.c new file mode 100644 index 00000000..b4629dcc --- /dev/null +++ b/test/rtr/pdu_handler_test.c @@ -0,0 +1,352 @@ +#include +#include +#include + +#include "validation_handler.h" +#include "rtr/pdu.h" +#include "rtr/pdu_handler.h" +#include "rtr/pdu_sender.h" +#include "rtr/db/vrps.h" + +/* TODO (now) test the removal of deltas that cancel each other */ +/* TODO (now) test delta forgetting */ + +/* Helper functions */ + +struct expected_pdu { + uint8_t pdu_type; + STAILQ_ENTRY(expected_pdu) list_hook; +}; + +static STAILQ_HEAD(, expected_pdu) expected_pdus = STAILQ_HEAD_INITIALIZER(expected_pdus); + +static void +expected_pdu_add(uint8_t pdu_type) +{ + struct expected_pdu *pdu; + + pdu = malloc(sizeof(struct expected_pdu)); + ck_assert_ptr_ne(NULL, pdu); + + pdu->pdu_type = pdu_type; + STAILQ_INSERT_TAIL(&expected_pdus, pdu, list_hook); +} + +static uint8_t +pop_expected_pdu(void) +{ + struct expected_pdu *pdu; + uint8_t result; + + pdu = STAILQ_FIRST(&expected_pdus); + ck_assert_ptr_ne(NULL, pdu); + result = pdu->pdu_type; + STAILQ_REMOVE(&expected_pdus, pdu, expected_pdu, list_hook); + free(pdu); + + return result; +} + +static bool +has_expected_pdus(void) +{ + return !STAILQ_EMPTY(&expected_pdus); +} + +static void +init_db_full(void) +{ + bool changed; + ck_assert_int_eq(0, vrps_init()); + ck_assert_int_eq(0, vrps_update(&changed)); + ck_assert_uint_eq(true, changed); + ck_assert_int_eq(0, vrps_update(&changed)); + ck_assert_uint_eq(true, changed); + ck_assert_int_eq(0, vrps_update(&changed)); + ck_assert_uint_eq(true, changed); +} + +static void +init_reset_query(struct reset_query_pdu *query) +{ + query->header.protocol_version = RTR_V0; + query->header.pdu_type = PDU_TYPE_RESET_QUERY; + query->header.m.reserved = 0; + query->header.length = 8; +} + +static void +init_serial_query(struct serial_query_pdu *query, uint32_t serial) +{ + query->header.protocol_version = RTR_V0; + query->header.pdu_type = PDU_TYPE_SERIAL_QUERY; + query->header.m.session_id = get_current_session_id(RTR_V0); + query->header.length = 12; + query->serial_number = serial; +} + +/* Impersonator functions */ + +serial_t +clients_get_min_serial(void) +{ + return 0; +} + +void +init_sender_common(struct sender_common *common, int fd, uint8_t version) +{ + common->fd = fd; + common->version = version; + common->session_id = get_current_session_id(version); +} + +int +send_cache_reset_pdu(struct sender_common *common) +{ + pr_info(" Server sent Cache Reset."); + ck_assert_int_eq(pop_expected_pdu(), PDU_TYPE_CACHE_RESET); + return 0; +} + +int +send_cache_response_pdu(struct sender_common *common) +{ + pr_info(" Server sent Cache Response."); + ck_assert_int_eq(pop_expected_pdu(), PDU_TYPE_CACHE_RESPONSE); + return 0; +} + +int +send_prefix_pdu(struct sender_common *common, struct vrp const *vrp, + uint8_t flags) +{ + /* + * We don't care about order. + * If the server is expected to return `M` IPv4 PDUs and `N` IPv6 PDUs, + * we'll just check `M + N` contiguous Prefix PDUs. + */ + uint8_t pdu_type = pop_expected_pdu(); + pr_info(" Server sent Prefix PDU."); + ck_assert_msg(pdu_type == PDU_TYPE_IPV4_PREFIX + || pdu_type == PDU_TYPE_IPV6_PREFIX, + "Server's PDU type is %d, not one of the IP Prefixes.", pdu_type); + return 0; +} + +static int +handle_delta(struct delta const *delta, void *arg) +{ + ck_assert_int_eq(0, send_prefix_pdu(arg, &delta->vrp, delta->flags)); + return 0; +} + +int +send_pdus_delta(struct deltas_db *deltas, struct sender_common *common) +{ + struct delta_group *group; + + ARRAYLIST_FOREACH(deltas, group) + ck_assert_int_eq(0, deltas_foreach(group->serial, group->deltas, + handle_delta, common)); + + return 0; +} + +int +send_end_of_data_pdu(struct sender_common *common, serial_t end_serial) +{ + pr_info(" Server sent End of Data."); + ck_assert_int_eq(pop_expected_pdu(), PDU_TYPE_END_OF_DATA); + return 0; +} + +int +send_error_report_pdu(int fd, uint8_t version, uint16_t code, +struct pdu_header *err_pdu_header, char const *message) +{ + pr_info(" Server sent Error Report %u: '%s'", code, message); + ck_assert_int_eq(pop_expected_pdu(), PDU_TYPE_ERROR_REPORT); + return 0; +} + +/* Tests */ + +/* https://tools.ietf.org/html/rfc6810#section-6.1 */ +START_TEST(test_start_or_restart) +{ + struct reset_query_pdu client_pdu; + + pr_info("-- Start or Restart --"); + + /* Prepare DB */ + init_db_full(); + + /* Init client request */ + init_reset_query(&client_pdu); + + /* Define expected server response */ + expected_pdu_add(PDU_TYPE_CACHE_RESPONSE); + expected_pdu_add(PDU_TYPE_IPV4_PREFIX); + expected_pdu_add(PDU_TYPE_IPV6_PREFIX); + expected_pdu_add(PDU_TYPE_END_OF_DATA); + + /* Run and validate */ + ck_assert_int_eq(0, handle_reset_query_pdu(0, &client_pdu)); + ck_assert_uint_eq(false, has_expected_pdus()); + + /* Clean up */ + vrps_destroy(); +} +END_TEST + +/* https://tools.ietf.org/html/rfc6810#section-6.2 */ +START_TEST(test_typical_exchange) +{ + struct serial_query_pdu client_pdu; + + pr_info("-- Typical Exchange --"); + + /* Prepare DB */ + init_db_full(); + + /* From serial 0: Init client request */ + init_serial_query(&client_pdu, 0); + + /* From serial 0: Define expected server response */ + expected_pdu_add(PDU_TYPE_CACHE_RESPONSE); + expected_pdu_add(PDU_TYPE_IPV4_PREFIX); + expected_pdu_add(PDU_TYPE_IPV6_PREFIX); + expected_pdu_add(PDU_TYPE_IPV4_PREFIX); + expected_pdu_add(PDU_TYPE_IPV6_PREFIX); + expected_pdu_add(PDU_TYPE_END_OF_DATA); + + /* From serial 0: Run and validate */ + ck_assert_int_eq(0, handle_serial_query_pdu(0, &client_pdu)); + ck_assert_uint_eq(false, has_expected_pdus()); + + /* From serial 1: Init client request */ + init_serial_query(&client_pdu, 1); + + /* From serial 1: Define expected server response */ + expected_pdu_add(PDU_TYPE_CACHE_RESPONSE); + expected_pdu_add(PDU_TYPE_IPV4_PREFIX); + expected_pdu_add(PDU_TYPE_IPV6_PREFIX); + expected_pdu_add(PDU_TYPE_END_OF_DATA); + + /* From serial 1: Run and validate */ + ck_assert_int_eq(0, handle_serial_query_pdu(0, &client_pdu)); + ck_assert_uint_eq(false, has_expected_pdus()); + + /* From serial 2: Init client request */ + init_serial_query(&client_pdu, 2); + + /* From serial 2: Define expected server response */ + expected_pdu_add(PDU_TYPE_CACHE_RESPONSE); + expected_pdu_add(PDU_TYPE_END_OF_DATA); + + /* From serial 2: Run and validate */ + ck_assert_int_eq(0, handle_serial_query_pdu(0, &client_pdu)); + ck_assert_uint_eq(false, has_expected_pdus()); + + /* Clean up */ + vrps_destroy(); +} +END_TEST + +/* https://tools.ietf.org/html/rfc6810#section-6.3 */ +START_TEST(test_no_incremental_update_available) +{ + struct serial_query_pdu serial_query; + + pr_info("-- No Incremental Update Available --"); + + /* Prepare DB */ + init_db_full(); + + /* Init client request */ + init_serial_query(&serial_query, 10000); + + /* Define expected server response */ + expected_pdu_add(PDU_TYPE_CACHE_RESET); + + /* Run and validate */ + ck_assert_int_eq(0, handle_serial_query_pdu(0, &serial_query)); + ck_assert_uint_eq(false, has_expected_pdus()); + + /* The Reset Query is already tested in start_or_restart. */ + + /* Clean up */ + vrps_destroy(); +} +END_TEST + +/* https://tools.ietf.org/html/rfc6810#section-6.4 */ +START_TEST(test_cache_has_no_data_available) +{ + struct serial_query_pdu serial_query; + struct reset_query_pdu reset_query; + + pr_info("-- Cache Has No Data Available --"); + + /* Prepare DB */ + ck_assert_int_eq(0, vrps_init()); + + /* Serial Query: Init client request */ + init_serial_query(&serial_query, 0); + + /* Serial Query: Define expected server response */ + expected_pdu_add(PDU_TYPE_ERROR_REPORT); + + /* Serial Query: Run and validate */ + ck_assert_int_eq(0, handle_serial_query_pdu(0, &serial_query)); + ck_assert_uint_eq(false, has_expected_pdus()); + + /* Reset Query: Init client request */ + init_reset_query(&reset_query); + + /* Reset Query: Define expected server response */ + expected_pdu_add(PDU_TYPE_ERROR_REPORT); + + /* Reset Query: Run and validate */ + ck_assert_int_eq(0, handle_reset_query_pdu(0, &reset_query)); + ck_assert_uint_eq(false, has_expected_pdus()); + + /* Clean up */ + vrps_destroy(); +} +END_TEST + +Suite *pdu_suite(void) +{ + Suite *suite; + TCase *core; + + core = tcase_create("RFC6810-Defined Protocol Sequences"); + tcase_add_test(core, test_start_or_restart); + tcase_add_test(core, test_typical_exchange); + tcase_add_test(core, test_no_incremental_update_available); + tcase_add_test(core, test_cache_has_no_data_available); + + /* TODO (now) add an unhappy path TCase. */ + + suite = suite_create("PDU Handler"); + suite_add_tcase(suite, core); + return suite; +} + +int main(void) +{ + Suite *suite; + SRunner *runner; + int tests_failed; + + suite = pdu_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; +}