]> git.ipfire.org Git - thirdparty/FORT-validator.git/commitdiff
Remove some TODOs and re-classify others
authorpcarana <pc.moreno2099@gmail.com>
Fri, 10 May 2019 18:06:58 +0000 (13:06 -0500)
committerpcarana <pc.moreno2099@gmail.com>
Fri, 10 May 2019 18:06:58 +0000 (13:06 -0500)
-Move directory loading and file filtering to common.h.
-Accept a TALs directory in configuration.
-Don't check for overriden PDUs if there's only 1 delta to send.
-Add roa_table merge function, this allows to merge distinct roa tables so that the VRPs base can have all the data from multiple TALs.
-Remove 'loop' var (isn't necessary) and make 'sigaction act' a global var.

16 files changed:
src/common.c
src/common.h
src/config.c
src/console_handler.c
src/object/tal.c
src/rtr/db/roa_table.c
src/rtr/db/roa_table.h
src/rtr/db/vrps.c
src/rtr/pdu_sender.c
src/rtr/rtr.c
src/slurm/slurm_loader.c
src/slurm/slurm_parser.c
src/slurm/slurm_parser.h
src/validation_handler.c
src/validation_handler.h
test/rtr/db/roa_table_test.c

index e210c1a1ff0cd64ea6717439786cbfc78a6fd0c4..07627fdb054c6049b8dd91d70b9efe80bf5cf643 100644 (file)
@@ -1,7 +1,9 @@
 #include "common.h"
 
 #include <errno.h>
+#include <dirent.h>
 #include <stdlib.h>
+#include <string.h>
 #include "log.h"
 
 int
@@ -84,3 +86,75 @@ close_thread(pthread_t thread, char const *what)
        return error;
 }
 
+static int
+process_file(char const *dir_name, char const *file_name, char const *file_ext,
+    process_file_cb cb, void *arg)
+{
+       char *ext, *fullpath, *tmp;
+       int error;
+
+       if (file_ext != NULL) {
+               ext = strrchr(file_name, '.');
+               /* Ignore file if extension isn't the expected */
+               if (ext == NULL || strcmp(ext, file_ext) != 0)
+                       return 0;
+       }
+
+       /* Get the full file path */
+       tmp = strdup(dir_name);
+       if (tmp == NULL)
+               return -pr_errno(errno, "Couldn't create temporal char");
+
+       tmp = realloc(tmp, strlen(tmp) + 1 + strlen(file_name) + 1);
+       if (tmp == NULL)
+               return -pr_errno(errno, "Couldn't reallocate temporal char");
+
+       strcat(tmp, "/");
+       strcat(tmp, file_name);
+       fullpath = realpath(tmp, NULL);
+       if (fullpath == NULL) {
+               free(tmp);
+               return -pr_errno(errno,
+                   "Error getting real path for file '%s' at dir '%s'",
+                   dir_name, file_name);
+       }
+
+       error = cb(fullpath, arg);
+       free(tmp);
+       free(fullpath);
+       return error;
+}
+
+int
+process_dir_files(char const *location, char const *file_ext,
+    process_file_cb cb, void *arg)
+{
+       DIR *dir_loc;
+       struct dirent *dir_ent;
+       int error;
+
+       dir_loc = opendir(location);
+       if (dir_loc == NULL) {
+               error = -pr_errno(errno, "Couldn't open dir %s", location);
+               goto end;
+       }
+
+       errno = 0;
+       while ((dir_ent = readdir(dir_loc)) != NULL) {
+               error = process_file(location, dir_ent->d_name, file_ext, cb,
+                   arg);
+               if (error) {
+                       pr_err("The error was at file %s", dir_ent->d_name);
+                       goto close_dir;
+               }
+               errno = 0;
+       }
+       if (errno) {
+               pr_err("Error reading dir %s", location);
+               error = -errno;
+       }
+close_dir:
+       closedir(dir_loc);
+end:
+       return error;
+}
index 469a4c853b9a201323c180f815fe7f6e77e70c7c..c13756a870820266a795a9cae37951a8c5d3a084 100644 (file)
@@ -35,4 +35,7 @@ void rwlock_unlock(pthread_rwlock_t *);
 /** Also boilerplate. */
 int close_thread(pthread_t thread, char const *what);
 
+typedef int (*process_file_cb)(char const *, void *);
+int process_dir_files(char const *, char const *, process_file_cb, void *);
+
 #endif /* SRC_RTR_COMMON_H_ */
index 9221debbb432a30f7c365bcd6724e9150d78bd62..a05239d681b29bae8babd79aebf2f39b5c9986a2 100644 (file)
@@ -26,7 +26,7 @@
  * Assuming you don't need to create a data type, that should be all.
  */
 struct rpki_config {
-       /** TAL file name or directory. TODO (urgent) support directories */
+       /** TAL file name or directory. */
        char *tal;
        /** Path of our local clone of the repository */
        char *local_repository;
@@ -143,7 +143,7 @@ static const struct option_field options[] = {
                .name = "tal",
                .type = &gt_string,
                .offset = offsetof(struct rpki_config, tal),
-               .doc = "Path to the TAL file",
+               .doc = "Path to the TAL file or TALs directory",
                .arg_doc = "<file>",
        }, {
                .id = 'r',
@@ -497,7 +497,7 @@ validate_config(void)
 {
        return (rpki_config.tal != NULL)
            ? 0
-           : pr_err("The TAL file (--tal) is mandatory.");
+           : pr_err("The TAL file/directory (--tal) is mandatory.");
 }
 
 static void
index e10a34e38981613b7ffa316f91accda52fb50f7c..2c87072cbc67a0500e28db360c097651de39920a 100644 (file)
@@ -27,6 +27,8 @@ validate_into_console(void)
 {
        struct validation_handler handler;
 
+       handler.merge = NULL;
+       handler.merge_arg = NULL;
        handler.reset = NULL;
        handler.traverse_down = NULL;
        handler.traverse_up = NULL;
index c06915776453619adb8919d53be025e3a2c1e06d..70abe80b53451952b724c99cc677b65aa8bf670d 100644 (file)
@@ -20,6 +20,8 @@
 #include "object/certificate.h"
 #include "rsync/rsync.h"
 
+#define TAL_FILE_EXTENSION     ".tal"
+
 struct uris {
        char **array; /* This is an array of string pointers. */
        unsigned int count;
@@ -328,7 +330,11 @@ handle_tal_uri(struct tal *tal, struct rpki_uri const *uri, void *arg)
                        break;
                }
        } else {
-               error = 1;
+               error = vhandler_merge(arg);
+               if (error)
+                       error = ENSURE_NEGATIVE(error);
+               else
+                       error = 1;
        }
 
 end:   validation_destroy(state);
@@ -336,29 +342,53 @@ end:      validation_destroy(state);
        return error;
 }
 
-int
-perform_standalone_validation(struct validation_handler *handler)
+static int
+do_file_validation(char const *tal_file, void *arg)
 {
        struct tal *tal;
        int error;
 
-       fnstack_init();
-       fnstack_push(config_get_tal());
+       fnstack_push(tal_file);
 
-       error = tal_load(config_get_tal(), &tal);
+       error = tal_load(tal_file, &tal);
        if (error)
                goto end;
 
        if (config_get_shuffle_tal_uris())
                tal_shuffle_uris(tal);
-       error = foreach_uri(tal, handle_tal_uri, handler);
+       error = foreach_uri(tal, handle_tal_uri, arg);
        if (error > 0)
                error = 0;
        else if (error == 0)
-               error = pr_err("None of the URIs of the TAL yielded a successful traversal.");
+               error = pr_err("None of the URIs of the TAL '%s' yielded a successful traversal.",
+                   tal_file);
 
 end:   tal_destroy(tal);
        fnstack_pop();
+       return error;
+}
+
+int
+perform_standalone_validation(struct validation_handler *handler)
+{
+       char const *config_tal;
+       struct stat attr;
+       int error;
+
+       config_tal = config_get_tal();
+       error = stat(config_tal, &attr);
+       if (error) {
+               pr_errno(errno, "Error reading path '%s'", config_tal);
+               return -errno;
+       }
+
+       fnstack_init();
+       if (S_ISDIR(attr.st_mode) == 0)
+               error = do_file_validation(config_tal, handler);
+       else
+               error = process_dir_files(config_tal, TAL_FILE_EXTENSION,
+                   do_file_validation, handler);
+
        fnstack_cleanup();
        return error;
 }
index 9c3aae3908df8a66c089aec69806b7be083200d8..28f0c12e2cd4816d23d0816d24bd8a397e3b6072 100644 (file)
@@ -100,6 +100,55 @@ add_roa(struct roa_table *table, struct hashable_roa *new)
        return 0;
 }
 
+static int
+duplicate_roa(struct roa_table *dst, struct hashable_roa *new)
+{
+       struct vrp vrp;
+       struct ipv4_prefix prefix4;
+       struct ipv6_prefix prefix6;
+
+       vrp = new->data;
+       switch (vrp.addr_fam) {
+       case AF_INET:
+               prefix4.addr = vrp.prefix.v4;
+               prefix4.len = vrp.prefix_length;
+               return rtrhandler_handle_roa_v4(dst, vrp.asn, &prefix4,
+                   vrp.max_prefix_length);
+       case AF_INET6:
+               prefix6.addr = vrp.prefix.v6;
+               prefix6.len = vrp.prefix_length;
+               return rtrhandler_handle_roa_v6(dst, vrp.asn, &prefix6,
+                   vrp.max_prefix_length);
+       }
+       return pr_crit("Unknown address family: %d", vrp.addr_fam);
+}
+
+static int
+roa_table_merge(struct roa_table *dst, struct roa_table *src)
+{
+       struct hashable_roa *node, *tmp, *found;
+       int error;
+
+       /** Must look for it due to the new mem allocation */
+       HASH_ITER(hh, src->roas, node, tmp) {
+               HASH_FIND(hh, dst->roas, &node->data, sizeof(node->data),
+                   found);
+               if (found != NULL)
+                       continue;
+               error = duplicate_roa(dst, node);
+               if (error)
+                       return error;
+       }
+
+       return 0;
+}
+
+int
+rtrhandler_merge(struct roa_table *dst, struct roa_table *src)
+{
+       return roa_table_merge(dst, src);
+}
+
 void
 roa_table_remove_roa(struct roa_table *table, struct vrp *del)
 {
index 9c18e0f50eb389c219a354e67e9513183fd08b1b..86541d1f9a7b1aa1b22ce1d8469153a2a58b9222 100644 (file)
@@ -17,6 +17,7 @@ int rtrhandler_handle_roa_v4(struct roa_table *, uint32_t,
     struct ipv4_prefix const *, uint8_t);
 int rtrhandler_handle_roa_v6(struct roa_table *, uint32_t,
     struct ipv6_prefix const *, uint8_t);
+int rtrhandler_merge(struct roa_table *, struct roa_table *);
 
 int compute_deltas(struct roa_table *, struct roa_table *, struct deltas **);
 
index 182f29bdf329776ff0a3930d28ffa825e3c96a64..3a0971394d3d684ae67a178ede32e38daaa26929 100644 (file)
@@ -93,6 +93,12 @@ vrps_destroy(void)
        pthread_rwlock_destroy(&lock); /* Nothing to do with error code */
 }
 
+static int
+__merge(void *dst, void *src)
+{
+       return rtrhandler_merge(dst, src);
+}
+
 static int
 __reset(void *arg)
 {
@@ -116,7 +122,7 @@ __handle_roa_v6(uint32_t as, struct ipv6_prefix const * prefix,
 static int
 __perform_standalone_validation(struct roa_table **result)
 {
-       struct roa_table *roas;
+       struct roa_table *roas, *global_roas;
        struct validation_handler validation_handler;
        int error;
 
@@ -124,6 +130,14 @@ __perform_standalone_validation(struct roa_table **result)
        if (roas == NULL)
                return pr_enomem();
 
+       global_roas = roa_table_create();
+       if (global_roas == NULL) {
+               roa_table_destroy(roas);
+               return pr_enomem();
+       }
+
+       validation_handler.merge = __merge;
+       validation_handler.merge_arg = global_roas;
        validation_handler.reset = __reset;
        validation_handler.traverse_down = NULL;
        validation_handler.traverse_up = NULL;
@@ -132,12 +146,13 @@ __perform_standalone_validation(struct roa_table **result)
        validation_handler.arg = roas;
 
        error = perform_standalone_validation(&validation_handler);
+       roa_table_destroy(roas);
        if (error) {
-               roa_table_destroy(roas);
+               roa_table_destroy(global_roas);
                return error;
        }
 
-       *result = roas;
+       *result = global_roas;
        return 0;
 }
 
index 3dd12cbf65729124b8feed1c43cd46504a7bc942..26cb6285607a21b6f9e58f0171cb8e30e656a872 100644 (file)
@@ -79,7 +79,7 @@ length_end_of_data_pdu(struct end_of_data_pdu *pdu)
        return len;
 }
 
-/* TODO Include Router Key PDU serials */
+/* TODO (next iteration) Include Router Key PDU serials */
 /**
  * static uint32_t
  * length_router_key_pdu(struct router_key_pdu *pdu)
@@ -322,6 +322,11 @@ send_pdus_delta(struct sender_common *common)
        struct vrp_node *ptr;
        int error;
 
+       /** Just 1 delta to send, no need to check for overridden PDUs */
+       if (*common->start_serial == *common->end_serial - 1)
+               return vrps_foreach_delta_roa(*common->start_serial,
+                   *common->end_serial, send_prefix_pdu, common);
+
        SLIST_INIT(&filtered_vrps);
        error = vrps_foreach_delta_roa(*common->start_serial,
            *common->end_serial, vrp_ovrd_remove, &filtered_vrps);
index b2cf7fdcd4645defc9cd754b52849280d99ab141..9ad8dbd8a98dc3b539682b35f1f597e4f4cfc66d 100644 (file)
@@ -22,8 +22,7 @@
 /* TODO (next iteration) Support both RTR v0 and v1 */
 #define RTR_VERSION_SUPPORTED  RTR_V0
 
-/* TODO (urgent) this needs to be atomic */
-volatile bool loop;
+struct sigaction act;
 
 /*
  * Arguments that the server socket thread will send to the client
@@ -181,7 +180,7 @@ client_thread_cb(void *param_void)
        int err;
        uint8_t rtr_version;
 
-       while (loop) { /* For each PDU... */
+       while (true) { /* For each PDU... */
                err = pdu_load(param->client_fd, &pdu, &meta, &rtr_version);
                if (err)
                        return end_client(param->client_fd, NULL, NULL);
@@ -285,7 +284,6 @@ signal_handler(int signal, siginfo_t *info, void *param)
 static int
 init_signal_handler(void)
 {
-       struct sigaction act;
        int error;
 
        memset(&act, 0, sizeof act);
@@ -293,7 +291,6 @@ init_signal_handler(void)
        act.sa_flags = SA_SIGINFO;
        act.sa_sigaction = signal_handler;
 
-       /* TODO is this really legal? @act might need to be global. */
        error = sigaction(SIGINT, &act, NULL);
        if (error) {
                pr_errno(errno, "Error initializing signal handler");
@@ -308,7 +305,6 @@ wait_threads(void)
 {
        struct thread_node *ptr;
 
-       loop = false;
        while (!SLIST_EMPTY(&threads)) {
                ptr = SLIST_FIRST(&threads);
                SLIST_REMOVE_HEAD(&threads, next);
@@ -343,7 +339,6 @@ rtr_listen(void)
                goto revert_server_socket;
 
        /* Init global vars */
-       loop = true;
        SLIST_INIT(&threads);
 
        error = init_signal_handler();
index 71bec6a4bfb0e1b0b72fa3d2fb7880b367b406e8..fe9dbe5a07d3797a3096c4bd9d1cb7ac538d3d15 100644 (file)
@@ -1,97 +1,29 @@
 #include "slurm_loader.h"
 
-#include <errno.h>
-#include <dirent.h>
 #include <stdlib.h>
 #include <stdbool.h>
-#include <string.h>
 
 #include "log.h"
 #include "config.h"
+#include "common.h"
 #include "slurm/slurm_db.h"
 #include "slurm/slurm_parser.h"
 
 #define SLURM_FILE_EXTENSION   ".slurm"
 
-static int
-single_slurm_load(const char *dir_name, const char *file_name)
-{
-       char *ext, *fullpath, *tmp;
-       int error;
-
-       ext = strrchr(file_name, '.');
-       /* Ignore file if extension isn't the expected */
-       if (ext == NULL || strcmp(ext, SLURM_FILE_EXTENSION) != 0)
-               return 0;
-
-       /* Get the full file path */
-       tmp = strdup(dir_name);
-       if (tmp == NULL)
-               return -pr_errno(errno,
-                   "Couldn't create temporal char for SLURM");
-
-       tmp = realloc(tmp, strlen(tmp) + 1 + strlen(file_name) + 1);
-       if (tmp == NULL)
-               return -pr_errno(errno,
-                   "Couldn't reallocate temporal char for SLURM");
-
-       strcat(tmp, "/");
-       strcat(tmp, file_name);
-       fullpath = realpath(tmp, NULL);
-       if (fullpath == NULL) {
-               free(tmp);
-               return -pr_errno(errno,
-                   "Error getting real path for file '%s' at dir '%s'",
-                   dir_name, file_name);
-       }
-
-       error = slurm_parse(fullpath);
-       free(tmp);
-       free(fullpath);
-       return error;
-}
-
 static int
 slurm_load(bool *loaded)
 {
-       DIR *dir_loc;
-       struct dirent *dir_ent;
-       char const *slurm_dir;
-       int error;
-
        /* Optional configuration */
        *loaded = false;
-       slurm_dir = config_get_slurm_location();
-       if (slurm_dir == NULL)
+       if (config_get_slurm_location() == NULL)
                return 0;
 
        *loaded = true;
        slurm_db_init();
 
-       dir_loc = opendir(slurm_dir);
-       if (dir_loc == NULL) {
-               error = -pr_errno(errno, "Couldn't open dir %s", slurm_dir);
-               goto end;
-       }
-
-       errno = 0;
-       while ((dir_ent = readdir(dir_loc)) != NULL) {
-               error = single_slurm_load(slurm_dir, dir_ent->d_name);
-               if (error) {
-                       pr_err("The error was at SLURM file %s",
-                           dir_ent->d_name);
-                       goto close_dir;
-               }
-               errno = 0;
-       }
-       if (errno) {
-               pr_err("Error reading dir %s", slurm_dir);
-               error = -errno;
-       }
-close_dir:
-       closedir(dir_loc);
-end:
-       return error;
+       return process_dir_files(config_get_slurm_location(),
+           SLURM_FILE_EXTENSION, slurm_parse, NULL);
 }
 
 static void
@@ -167,7 +99,7 @@ slurm_apply(struct roa_table *base)
 
        error = slurm_pfx_assertions_apply(base);
 
-       /** TODO Apply BGPsec filters and assertions */
+       /** TODO (next iteration) Apply BGPsec filters and assertions */
 
 cleanup:
        slurm_cleanup();
index ce6c1d86af3de7ff98c3de14bcedfa08391ab731..90dd4c61b437c27f74b82db5334195238b4a9f22 100644 (file)
@@ -35,7 +35,7 @@
 static int handle_json(json_t *);
 
 int
-slurm_parse(char const *location)
+slurm_parse(char const *location, void *arg)
 {
        json_t *json_root;
        json_error_t json_error;
@@ -292,14 +292,12 @@ set_router_pub_key(json_t *object, bool is_assertion,
                return pr_err("'%s' couldn't be decoded", str_encoded);
 
        /*
-        * TODO Validate that 'routerPublicKey' is: "the equivalent to the
-        * subjectPublicKeyInfo value of the router certificate's public key,
-        * as described in [RFC8208].  This is the full ASN.1 DER encoding of
-        * the subjectPublicKeyInfo, including the ASN.1 tag and length values
+        * TODO (next iteration) Reuse the functions to validate that
+        * 'routerPublicKey' is: "the equivalent to the subjectPublicKeyInfo
+        * value of the router certificate's public key, as described in
+        * [RFC8208]. This is the full ASN.1 DER encoding of the
+        * subjectPublicKeyInfo, including the ASN.1 tag and length values
         * of the subjectPublicKeyInfo SEQUENCE.
-        */
-       /*
-        * TODO When the merge is done, reuse the functions at fort-validator
         *
         * #include <libcmscodec/SubjectPublicKeyInfo.h>
         * #include "asn1/decode.h"
index 6a1b0ef8eeaa96100a0fbd885f5943025188d5f8..cb64f035d55e159cc9c7e724acb5f933aba93dcf 100644 (file)
@@ -31,7 +31,7 @@ struct slurm_bgpsec {
 };
 
 
-int slurm_parse(char const *);
+int slurm_parse(char const *, void *);
 
 
 #endif /* SRC_SLURM_SLURM_PARSER_H_ */
index 971af061ab48171c3d83c2cd34c8b9596b085151..656333083667cdc260cbb01835d26b38357f883a 100644 (file)
@@ -4,6 +4,13 @@
 #include "log.h"
 #include "thread_var.h"
 
+int
+vhandler_merge(struct validation_handler *handler)
+{
+       return (handler->merge != NULL) ?
+           handler->merge(handler->merge_arg, handler->arg) : 0;
+}
+
 int
 vhandler_reset(struct validation_handler *handler)
 {
index 72ca4260dfbc140077484dbe8a64e917f8bf569b..5b21be54825c6de750e8e4cbb173d543da9f0be7 100644 (file)
@@ -7,6 +7,8 @@
 struct validation_handler {
        /* All of these can be NULL. */
 
+       int (*merge)(void *, void *);
+       void *merge_arg;
        int (*reset)(void *);
        int (*traverse_down)(struct rfc5280_name *, void *);
        int (*traverse_up)(void *);
@@ -17,6 +19,7 @@ struct validation_handler {
        void *arg;
 };
 
+int vhandler_merge(struct validation_handler *);
 int vhandler_reset(struct validation_handler *);
 int vhandler_traverse_down(struct rfc5280_name *);
 int vhandler_traverse_up(void);
index 6b3850f84b5d456bc163b284b78cb7afc7d294ca..31c6d3e353736377cd440c1bdfaa7947b53c7157 100644 (file)
@@ -156,20 +156,120 @@ 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
+
+START_TEST(test_merge)
+{
+       struct ipv4_prefix prefix4;
+       struct ipv6_prefix prefix6;
+       struct roa_table *left, *right, *merged;
+       array_index i;
+       int left_count, right_count, total_merged;
+
+       left = roa_table_create();
+       ck_assert_ptr_ne(NULL, left);
+       right = roa_table_create();
+       ck_assert_ptr_ne(NULL, right);
+       merged = roa_table_create();
+       ck_assert_ptr_ne(NULL, merged);
+
+       prefix4.addr.s_addr = ADDR1;
+       prefix4.len = 24;
+       prefix6.addr.s6_addr32[0] = htonl(0x20010DB8);
+       prefix6.addr.s6_addr32[1] = 0;
+       prefix6.addr.s6_addr32[2] = 0;
+       prefix6.addr.s6_addr32[3] = htonl(0x00000001);
+       prefix6.len = 120;
+
+       left_count = 0;
+       right_count = 0;
+       total_merged = 0;
+
+       /** Add the same roas on both tables*/
+       ck_assert_int_eq(0, rtrhandler_handle_roa_v4(left, 10, &prefix4, 32));
+       left_count++;
+       ck_assert_int_eq(0, rtrhandler_handle_roa_v4(right, 10, &prefix4, 32));
+       right_count++;
+       ck_assert_int_eq(0, rtrhandler_handle_roa_v4(left, 11, &prefix4, 32));
+       left_count++;
+       ck_assert_int_eq(0, rtrhandler_handle_roa_v4(right, 11, &prefix4, 32));
+       right_count++;
+
+       /** And add distinct roas on each table */
+       prefix4.addr.s_addr = ADDR2;
+       ck_assert_int_eq(0, rtrhandler_handle_roa_v4(left, 10, &prefix4, 32));
+       left_count++;
+
+       prefix4.addr.s_addr = ADDR1;
+       prefix4.len = 25;
+       ck_assert_int_eq(0, rtrhandler_handle_roa_v4(right, 10, &prefix4, 32));
+       right_count++;
+
+       prefix4.len = 24;
+       ck_assert_int_eq(0, rtrhandler_handle_roa_v4(left, 10, &prefix4, 30));
+       left_count++;
+
+       /* IPv6 */
+       ck_assert_int_eq(0, rtrhandler_handle_roa_v6(right, 10, &prefix6, 128));
+       right_count++;
+       ck_assert_int_eq(0, rtrhandler_handle_roa_v6(left, 11, &prefix6, 128));
+       left_count++;
+
+       prefix6.addr.s6_addr32[3] = htonl(0x00000002);
+       ck_assert_int_eq(0, rtrhandler_handle_roa_v6(right, 10, &prefix6, 128));
+       right_count++;
+
+       prefix6.addr.s6_addr32[3] = htonl(0x00000001);
+       prefix6.len = 121;
+       ck_assert_int_eq(0, rtrhandler_handle_roa_v6(left, 10, &prefix6, 128));
+       left_count++;
+
+       prefix6.len = 120;
+       ck_assert_int_eq(0, rtrhandler_handle_roa_v6(right, 10, &prefix6, 127));
+       right_count++;
+
+       /** Do the merge */
+       ck_assert_int_eq(0, rtrhandler_merge(merged, left));
+       ck_assert_int_eq(0, rtrhandler_merge(merged, right));
+
+       /**
+        * Must have:
+        * count(left) + count(right) - 2 (duplicated elements)
+        */
+       total_merged = left_count + right_count - 2;
+       ck_assert_int_eq(total_merged, TOTAL_ROAS);
+
+       /* Check table contents and that merged table has new memory refs */
+       roa_table_destroy(left);
+       roa_table_destroy(right);
+
+       memset(roas_found, 0, sizeof(roas_found));
+       total_found = 0;
+       ck_assert_int_eq(0, roa_table_foreach_roa(merged, foreach_cb, NULL));
+       ck_assert_int_eq(TOTAL_ROAS, total_found);
+       for (i = 0; i < TOTAL_ROAS; i++)
+               ck_assert_int_eq(true, roas_found[i]);
+
+       roa_table_destroy(merged);
 }
 END_TEST
 
 Suite *pdu_suite(void)
 {
        Suite *suite;
-       TCase *core;
+       TCase *core, *merge;
 
        core = tcase_create("Core");
        tcase_add_test(core, test_basic);
 
+       merge = tcase_create("Merge");
+       tcase_add_test(core, test_merge);
+
        suite = suite_create("ROA Table");
        suite_add_tcase(suite, core);
+       suite_add_tcase(suite, merge);
        return suite;
 }