]> git.ipfire.org Git - thirdparty/strongswan.git/commitdiff
traffic-selector-list: Add helper class to manage a collection of TS
authorTobias Brunner <tobias@strongswan.org>
Tue, 25 Mar 2025 11:43:26 +0000 (12:43 +0100)
committerTobias Brunner <tobias@strongswan.org>
Wed, 28 May 2025 09:06:19 +0000 (11:06 +0200)
Provides functions to optionally resolve dynamic TS and to narrow
them based on a list of supplied TS.

src/libstrongswan/Android.mk
src/libstrongswan/Makefile.am
src/libstrongswan/selectors/traffic_selector_list.c [new file with mode: 0644]
src/libstrongswan/selectors/traffic_selector_list.h [new file with mode: 0644]
src/libstrongswan/tests/suites/test_traffic_selector.c

index 1f2d0af5219ec92784b9aa2acfec834b19d5658b..ce78233d5cd3b8300d94846b6dd17c21bdd8cb20 100644 (file)
@@ -42,7 +42,7 @@ networking/streams/stream_tcp.c networking/streams/stream_service_tcp.c \
 pen/pen.c plugins/plugin_loader.c plugins/plugin_feature.c processing/jobs/job.c \
 processing/jobs/callback_job.c processing/processor.c processing/scheduler.c \
 processing/watcher.c resolver/resolver_manager.c resolver/rr_set.c \
-selectors/sec_label.c selectors/traffic_selector.c \
+selectors/sec_label.c selectors/traffic_selector.c selectors/traffic_selector_list.c \
 settings/settings.c settings/settings_types.c \
 settings/settings_parser.c settings/settings_lexer.c utils/cpu_feature.c \
 utils/utils.c utils/chunk.c utils/debug.c utils/enum.c utils/identification.c \
index 8d3c869bd986bf96c1abf4c2275ab1292de9143f..79d94be9b8426837ed918210761f49f88d1d254d 100644 (file)
@@ -40,7 +40,7 @@ networking/streams/stream_tcp.c networking/streams/stream_service_tcp.c \
 pen/pen.c plugins/plugin_loader.c plugins/plugin_feature.c processing/jobs/job.c \
 processing/jobs/callback_job.c processing/processor.c processing/scheduler.c \
 processing/watcher.c resolver/resolver_manager.c resolver/rr_set.c \
-selectors/sec_label.c selectors/traffic_selector.c \
+selectors/sec_label.c selectors/traffic_selector.c selectors/traffic_selector_list.c \
 settings/settings.c settings/settings_types.c \
 settings/settings_parser.y settings/settings_lexer.l utils/cpu_feature.c \
 utils/utils.c utils/chunk.c utils/debug.c utils/enum.c utils/identification.c \
@@ -125,7 +125,7 @@ resolver/rr.h resolver/resolver_manager.h \
 plugins/plugin_loader.h plugins/plugin.h plugins/plugin_feature.h \
 processing/jobs/job.h processing/jobs/callback_job.h processing/processor.h \
 processing/scheduler.h processing/watcher.h \
-selectors/sec_label.h selectors/traffic_selector.h \
+selectors/sec_label.h selectors/traffic_selector.h selectors/traffic_selector_list.h \
 settings/settings.h settings/settings_parser.h threading/thread_value.h \
 threading/thread.h threading/windows/thread.h \
 threading/mutex.h threading/condvar.h threading/spinlock.h threading/semaphore.h \
diff --git a/src/libstrongswan/selectors/traffic_selector_list.c b/src/libstrongswan/selectors/traffic_selector_list.c
new file mode 100644 (file)
index 0000000..71c83fb
--- /dev/null
@@ -0,0 +1,292 @@
+/*
+ * Copyright (C) 2008-2025 Tobias Brunner
+ * Copyright (C) 2005-2007 Martin Willi
+ *
+ * Copyright (C) secunet Security Networks AG
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.  See <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * for more details.
+ */
+
+#include "traffic_selector_list.h"
+
+#include <utils/debug.h>
+
+typedef struct private_traffic_selector_list_t private_traffic_selector_list_t;
+
+/**
+ * Private data.
+ */
+struct private_traffic_selector_list_t {
+
+       /**
+        * Public interface.
+        */
+       traffic_selector_list_t public;
+
+       /**
+        * List of managed traffic selectors.
+        */
+       linked_list_t *ts;
+};
+
+METHOD(traffic_selector_list_t, add, void,
+       private_traffic_selector_list_t *this, traffic_selector_t *ts)
+{
+       this->ts->insert_last(this->ts, ts);
+}
+
+METHOD(traffic_selector_list_t, create_enumerator, enumerator_t*,
+       private_traffic_selector_list_t *this)
+{
+       return this->ts->create_enumerator(this->ts);
+}
+
+/**
+ * Create a copy of the traffic selectors in the given list, while resolving
+ * "dynamic" traffic selectors using the given hosts, if any. When not narrowing
+ * as initiator, we also replace TS in transport mode.
+ */
+static linked_list_t *resolve_dynamic_ts(private_traffic_selector_list_t *this,
+                                                                                linked_list_t *hosts, bool narrowing,
+                                                                                bool force_dynamic)
+{
+       enumerator_t *e1, *e2;
+       traffic_selector_t *ts1, *ts2;
+       linked_list_t *result;
+       host_t *host;
+
+       if (!hosts || !hosts->get_count(hosts))
+       {
+               return this->ts->clone_offset(this->ts,
+                                                                         offsetof(traffic_selector_t, clone));
+       }
+
+       result = linked_list_create();
+       e1 = this->ts->create_enumerator(this->ts);
+       while (e1->enumerate(e1, &ts1))
+       {
+               /* set hosts if TS is dynamic or if forced as initiator in
+                * transport mode */
+               bool dynamic = ts1->is_dynamic(ts1);
+               if (!dynamic && !force_dynamic)
+               {
+                       result->insert_last(result, ts1->clone(ts1));
+                       continue;
+               }
+               e2 = hosts->create_enumerator(hosts);
+               while (e2->enumerate(e2, &host))
+               {
+                       if (!dynamic && !host->is_anyaddr(host) &&
+                               !ts1->includes(ts1, host))
+                       {       /* for transport mode, we skip TS that don't match
+                                * specific IPs */
+                               continue;
+                       }
+                       ts2 = ts1->clone(ts1);
+                       if (dynamic || !host->is_anyaddr(host))
+                       {       /* don't make regular TS larger than they were */
+                               ts2->set_address(ts2, host);
+                       }
+                       result->insert_last(result, ts2);
+               }
+               e2->destroy(e2);
+       }
+       e1->destroy(e1);
+       return result;
+}
+
+/**
+ * Remove duplicate traffic selectors in the given list.
+ */
+static void remove_duplicate_ts(linked_list_t *list)
+{
+       enumerator_t *e1, *e2;
+       traffic_selector_t *ts1, *ts2;
+
+       e1 = list->create_enumerator(list);
+       e2 = list->create_enumerator(list);
+       while (e1->enumerate(e1, &ts1))
+       {
+               while (e2->enumerate(e2, &ts2))
+               {
+                       if (ts1 != ts2)
+                       {
+                               if (ts2->is_contained_in(ts2, ts1))
+                               {
+                                       list->remove_at(list, e2);
+                                       ts2->destroy(ts2);
+                                       list->reset_enumerator(list, e1);
+                                       break;
+                               }
+                               if (ts1->is_contained_in(ts1, ts2))
+                               {
+                                       list->remove_at(list, e1);
+                                       ts1->destroy(ts1);
+                                       break;
+                               }
+                       }
+               }
+               list->reset_enumerator(list, e2);
+       }
+       e1->destroy(e1);
+       e2->destroy(e2);
+}
+
+METHOD(traffic_selector_list_t, get, linked_list_t*,
+       private_traffic_selector_list_t *this, linked_list_t *hosts,
+       bool force_dynamic)
+{
+       linked_list_t *result;
+
+       result = resolve_dynamic_ts(this, hosts, FALSE, force_dynamic);
+       remove_duplicate_ts(result);
+       return result;
+}
+
+METHOD(traffic_selector_list_t, select_, linked_list_t*,
+       private_traffic_selector_list_t *this, linked_list_t *supplied,
+       linked_list_t *hosts, bool force_dynamic, bool *narrowed)
+{
+       enumerator_t *e1, *e2;
+       traffic_selector_t *ts1, *ts2, *selected;
+       linked_list_t *resolved, *result;
+
+       result = linked_list_create();
+       resolved = resolve_dynamic_ts(this, hosts, supplied != NULL, force_dynamic);
+
+       if (!supplied)
+       {
+               while (resolved->remove_first(resolved, (void**)&ts1) == SUCCESS)
+               {
+                       DBG2(DBG_CFG, " %R", ts1);
+                       result->insert_last(result, ts1);
+               }
+       }
+       else
+       {
+               e1 = resolved->create_enumerator(resolved);
+               e2 = supplied->create_enumerator(supplied);
+               /* enumerate all configured/resolved selectors */
+               while (e1->enumerate(e1, &ts1))
+               {
+                       /* enumerate all supplied traffic selectors */
+                       while (e2->enumerate(e2, &ts2))
+                       {
+                               selected = ts1->get_subset(ts1, ts2);
+                               if (selected)
+                               {
+                                       DBG2(DBG_CFG, " config: %R, received: %R => match: %R",
+                                                ts1, ts2, selected);
+                                       result->insert_last(result, selected);
+                               }
+                               else
+                               {
+                                       DBG2(DBG_CFG, " config: %R, received: %R => no match",
+                                                ts1, ts2);
+                               }
+                       }
+                       supplied->reset_enumerator(supplied, e2);
+               }
+               e1->destroy(e1);
+               e2->destroy(e2);
+
+               if (narrowed)
+               {
+                       *narrowed = FALSE;
+
+                       e1 = resolved->create_enumerator(resolved);
+                       e2 = result->create_enumerator(result);
+                       while (e1->enumerate(e1, &ts1))
+                       {
+                               if (!e2->enumerate(e2, &ts2) || !ts1->equals(ts1, ts2))
+                               {
+                                       *narrowed = TRUE;
+                                       break;
+                               }
+                       }
+                       e1->destroy(e1);
+                       e2->destroy(e2);
+               }
+       }
+       resolved->destroy_offset(resolved, offsetof(traffic_selector_t, destroy));
+       remove_duplicate_ts(result);
+       return result;
+}
+
+METHOD(traffic_selector_list_t, equals, bool,
+       private_traffic_selector_list_t *this, traffic_selector_list_t *other_pub)
+{
+       private_traffic_selector_list_t *other = (private_traffic_selector_list_t*)other_pub;
+       return this->ts->equals_offset(this->ts, other->ts,
+                                                                  offsetof(traffic_selector_t, equals));
+}
+
+METHOD(traffic_selector_list_t, destroy, void,
+       private_traffic_selector_list_t *this)
+{
+       this->ts->destroy_offset(this->ts, offsetof(traffic_selector_t, destroy));
+       free(this);
+}
+
+METHOD(traffic_selector_list_t, clone_, traffic_selector_list_t*,
+       private_traffic_selector_list_t *this)
+{
+       return traffic_selector_list_create_from_list(
+               this->ts->clone_offset(this->ts, offsetof(traffic_selector_t, clone)));
+}
+
+/*
+ * Described in header
+ */
+traffic_selector_list_t *traffic_selector_list_create_from_list(linked_list_t *list)
+{
+       private_traffic_selector_list_t *this;
+
+       INIT(this,
+               .public = {
+                       .add = _add,
+                       .create_enumerator = _create_enumerator,
+                       .get = _get,
+                       .select = _select_,
+                       .equals = _equals,
+                       .clone = _clone_,
+                       .destroy = _destroy,
+               },
+               .ts = list,
+       );
+       return &this->public;
+}
+
+/*
+ * Described in header
+ */
+traffic_selector_list_t *traffic_selector_list_create()
+{
+       return traffic_selector_list_create_from_list(linked_list_create());
+}
+
+/*
+ * Described in header
+ */
+traffic_selector_list_t *traffic_selector_list_create_from_enumerator(
+                                                                                                       enumerator_t *enumerator)
+{
+       traffic_selector_list_t *this = traffic_selector_list_create();
+       traffic_selector_t *ts;
+
+       while (enumerator->enumerate(enumerator, &ts))
+       {
+               add((private_traffic_selector_list_t*)this, ts->clone(ts));
+       }
+       enumerator->destroy(enumerator);
+
+       return this;
+}
diff --git a/src/libstrongswan/selectors/traffic_selector_list.h b/src/libstrongswan/selectors/traffic_selector_list.h
new file mode 100644 (file)
index 0000000..30fd607
--- /dev/null
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2025 Tobias Brunner
+ *
+ * Copyright (C) secunet Security Networks AG
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.  See <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * for more details.
+ */
+
+/**
+ * @defgroup traffic_selector_list traffic_selector_list
+ * @{ @ingroup selectors
+ */
+
+#ifndef TRAFFIC_SELECTOR_LIST_H_
+#define TRAFFIC_SELECTOR_LIST_H_
+
+typedef struct traffic_selector_list_t traffic_selector_list_t;
+
+#include "traffic_selector.h"
+
+/**
+ * Collection of traffic selectors that can be narrowed to a new set of
+ * traffic selectors.
+ */
+struct traffic_selector_list_t {
+
+       /**
+        * Add a traffic selector to the collection.
+        *
+        * @param ts                    traffic_selector to add (adopted)
+        */
+       void (*add)(traffic_selector_list_t *this, traffic_selector_t *ts);
+
+       /**
+        * Enumerate all traffic selectors in the collection.
+        *
+        * Similar to calling get() without \p hosts, but does not clone the traffic
+        * selectors and duplicates are not removed.
+        *
+        * @return                              enumerator over traffic_selector_t*
+        */
+       enumerator_t *(*create_enumerator)(traffic_selector_list_t *this);
+
+       /**
+        * Get a list of traffic selectors contained in the collection.
+        *
+        * Some traffic selectors may be "dynamic", meaning they are narrowed down
+        * to a specific address (host-to-host or virtual-IP setups). Use the
+        * \p hosts parameter to narrow such traffic selectors to an address. If
+        * \p force_dynamic is also passed, even non-dynamic traffic selectors that
+        * match are replaced using the IPs in \p hosts (useful as initiator with
+        * transport mode).
+        *
+        * If \p hosts is not passed, the list of traffic selectors is returned as
+        * configured, except that exact duplicates are removed. However, note that
+        * "dynamic" traffic selectors are not considered duplicates.
+        *
+        * Returned list and its traffic selectors must be destroyed after use.
+        *
+        * Note that this method does not log anything. If logging is required, use
+        * select() without passing supplied traffic selectors.
+        *
+        * @param hosts                 addresses to use for narrowing "dynamic" TS, host_t
+        * @param force_dynamic TRUE to replace non-"dynamic" TS with \p hosts as
+        *                                              initiator in transport mode
+        * @return                              list containing the traffic selectors
+        */
+       linked_list_t *(*get)(traffic_selector_list_t *this, linked_list_t *hosts,
+                                                 bool force_dynamic);
+
+       /**
+        * Select a list of traffic selectors contained in the collection.
+        *
+        * If a list with traffic selectors is supplied, these are used to narrow
+        * down the traffic selectors to the greatest common subset.
+        *
+        * Some traffic selectors may be "dynamic", meaning they are narrowed down
+        * to a specific address (host-to-host or virtual-IP setups). Use the
+        * \p hosts parameter to narrow such traffic selectors to an address. If
+        * \p force_dynamic is also passed, even non-dynamic traffic selectors that
+        * match are replaced using the IPs in \p hosts (useful as initiator with
+        * transport mode).
+        *
+        * Details about the selection of each individual traffic selector are
+        * logged.
+        *
+        * Returned list and its traffic selectors must be destroyed after use.
+        *
+        * @param supplied              list with TS to select from, or NULL
+        * @param hosts                 addresses to use for narrowing "dynamic" TS', host_t
+        * @param force_dynamic TRUE to replace non-"dynamic" TS with \p hosts as
+        *                                              initiator in transport mode
+        * @param narrowed[out] optional flag that indicates if any TS were narrowed
+        * @return                              list containing the traffic selectors
+        */
+       linked_list_t *(*select)(traffic_selector_list_t *this,
+                                                        linked_list_t *supplied, linked_list_t *hosts,
+                                                        bool force_dynamic, bool *narrowed);
+
+       /**
+        * Compare two collections of traffic selectors.
+        *
+        * @param other                 collection to compare with this
+        * @return                              TRUE if equal, FALSE otherwise
+        */
+       bool (*equals)(traffic_selector_list_t *this, traffic_selector_list_t *other);
+
+       /**
+        * Clone this collection of traffic selectors.
+        *
+        * @return                              cloned collection
+        */
+       traffic_selector_list_t *(*clone)(traffic_selector_list_t *this);
+
+       /**
+        * Destroys this collection.
+        */
+       void (*destroy)(traffic_selector_list_t *this);
+};
+
+/**
+ * Create an empty traffic selector collection.
+ *
+ * @return                                     created object
+ */
+traffic_selector_list_t *traffic_selector_list_create();
+
+/**
+ * Create a collection with traffic selectors from the given list (adopted).
+ *
+ * @param list                         list of traffic_selector_t (adopted)
+ * @return                                     created object
+ */
+traffic_selector_list_t *traffic_selector_list_create_from_list(
+                                                                                                               linked_list_t *list);
+
+/**
+ * Create a collection with traffic selectors from the given enumerator (objects
+ * are cloned, the enumerator is destroyed).
+ *
+ * @param enumerator           enumerator over traffic_selector_t (cloned/destroyed)
+ * @return                                     created object
+ */
+traffic_selector_list_t *traffic_selector_list_create_from_enumerator(
+                                                                                                       enumerator_t *enumerator);
+
+#endif /** TRAFFIC_SELECTOR_LIST_H_ @}*/
index cbcc0c08d39bdfd381b59eab0160b6731e9bfdb8..45daa908289477fad55139752ffebc4833bb2b0d 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2015 Tobias Brunner
+ * Copyright (C) 2015-2025 Tobias Brunner
  * Copyright (C) 2015 Martin Willi
  *
  * Copyright (C) secunet Security Networks AG
@@ -18,6 +18,7 @@
 #include "test_suite.h"
 
 #include <selectors/traffic_selector.h>
+#include <selectors/traffic_selector_list.h>
 
 
 static void verify(const char *str, const char *alt, traffic_selector_t *ts)
@@ -814,6 +815,199 @@ START_TEST(test_printf_hook_hash)
 }
 END_TEST
 
+/**
+ * Create a linked list of either traffic selectors or hosts from a
+ * comma-separated list.
+ */
+static linked_list_t *create_list(char *str, bool hosts)
+{
+       linked_list_t *list = linked_list_create();
+       enumerator_t *enumerator;
+       char *item;
+       void *obj;
+
+       enumerator = enumerator_create_token(str, " ", "");
+       while (enumerator->enumerate(enumerator, &item))
+       {
+               if (hosts)
+               {
+                       obj = host_create_from_string(item, 0);
+               }
+               else if (streq(item, "dynamic"))
+               {
+                       obj = traffic_selector_create_dynamic(0, 0, 65535);
+               }
+               else
+               {
+                       obj = traffic_selector_create_from_cidr(item, 0, 0, 65535);
+               }
+               list->insert_last(list, obj);
+       }
+       enumerator->destroy(enumerator);
+       return list;
+}
+
+struct {
+       char *ts;
+       char *hosts;
+       char *get;
+       char *get_force;
+} list_get_tests[] = {
+       { "dynamic", NULL, "dynamic", "dynamic" },
+       { "dynamic", "10.0.1.1", "10.0.1.1/32", "10.0.1.1/32" },
+       { "dynamic", "10.0.1.1 192.168.0.1",
+               "10.0.1.1/32 192.168.0.1/32", "10.0.1.1/32 192.168.0.1/32" },
+       { "0.0.0.0/0", "10.0.1.1", "0.0.0.0/0", "10.0.1.1/32" },
+       { "0.0.0.0/0", "10.0.1.1  192.168.0.1",
+               "0.0.0.0/0", "10.0.1.1/32 192.168.0.1/32" },
+       { "10.0.1.0/24", "10.0.1.1", "10.0.1.0/24", "10.0.1.1/32" },
+       { "10.0.2.0/24", "10.0.1.1", "10.0.2.0/24", "" },
+       { "10.0.2.0/24", "10.0.1.1 10.0.2.1", "10.0.2.0/24", "10.0.2.1/32" },
+       /* two dynamic TS are not treated as duplicates */
+       { "dynamic dynamic", NULL, "dynamic dynamic", "dynamic dynamic" },
+       { "dynamic dynamic", "10.0.1.1", "10.0.1.1/32", "10.0.1.1/32" },
+       { "dynamic dynamic", "10.0.1.1 192.168.0.1",
+               "10.0.1.1/32 192.168.0.1/32", "10.0.1.1/32 192.168.0.1/32" },
+       { "0.0.0.0/0 0.0.0.0/0", "10.0.1.1", "0.0.0.0/0", "10.0.1.1/32" },
+       { "0.0.0.0/0 0.0.0.0/0", "10.0.1.1  192.168.0.1",
+               "0.0.0.0/0", "10.0.1.1/32 192.168.0.1/32" },
+       { "10.0.1.0/24 10.0.1.0/24", NULL, "10.0.1.0/24", "10.0.1.0/24" },
+       { "10.0.1.1/32 10.0.1.0/24", NULL, "10.0.1.0/24", "10.0.1.0/24" },
+       { "10.0.1.0/24 10.0.1.1/32", NULL, "10.0.1.0/24", "10.0.1.0/24" },
+       { "10.0.1.0/24 10.0.2.0/24", NULL,
+               "10.0.1.0/24 10.0.2.0/24", "10.0.1.0/24 10.0.2.0/24" },
+       { "10.0.1.0/24 10.0.2.0/24", "10.0.1.1",
+               "10.0.1.0/24 10.0.2.0/24", "10.0.1.1/32" },
+       { "10.0.1.0/24 10.0.2.0/24", "10.0.1.1 10.0.2.1",
+               "10.0.1.0/24 10.0.2.0/24", "10.0.1.1/32 10.0.2.1/32" },
+};
+
+START_TEST(test_list_get)
+{
+       traffic_selector_list_t *ts;
+       linked_list_t *list, *hosts, *result;
+
+       list = create_list(list_get_tests[_i].ts, FALSE);
+       ts = traffic_selector_list_create_from_enumerator(list->create_enumerator(list));
+       hosts = list_get_tests[_i].hosts ? create_list(list_get_tests[_i].hosts, TRUE) : NULL;
+
+       result = ts->get(ts, hosts, FALSE);
+       verify_list(list_get_tests[_i].get, NULL, result);
+
+       result = ts->get(ts, hosts, TRUE);
+       verify_list(list_get_tests[_i].get_force, NULL, result);
+
+       DESTROY_OFFSET_IF(hosts, offsetof(host_t, destroy));
+       list->destroy_offset(list, offsetof(traffic_selector_t, destroy));
+       ts->destroy(ts);
+}
+END_TEST
+
+struct {
+       char *ts;
+       char *hosts;
+       char *supplied;
+       char *select;
+       char *select_force;
+       bool narrowed;
+} list_select_tests[] = {
+       { "dynamic", NULL, NULL, "dynamic", "dynamic", FALSE },
+       { "dynamic", NULL, "10.0.1.0/24", "", "", TRUE },
+       { "dynamic", "10.0.1.1", "0.0.0.0/0", "10.0.1.1/32", "10.0.1.1/32", FALSE },
+       { "dynamic", "10.0.1.1", "10.0.1.1/32", "10.0.1.1/32", "10.0.1.1/32", FALSE },
+       { "dynamic", "10.0.1.1 192.168.0.1", "10.0.1.0/24",
+               "10.0.1.1/32", "10.0.1.1/32", TRUE },
+       { "dynamic", "10.0.1.1 192.168.0.1", "10.0.1.0/24 192.168.0.0/24",
+               "10.0.1.1/32 192.168.0.1/32", "10.0.1.1/32 192.168.0.1/32", FALSE },
+       { "0.0.0.0/0", NULL, "0.0.0.0/0", "0.0.0.0/0", "0.0.0.0/0", FALSE },
+       { "0.0.0.0/0", "10.0.1.1", "0.0.0.0/0", "0.0.0.0/0", "10.0.1.1/32", FALSE },
+       { "0.0.0.0/0", NULL, "10.0.1.0/24", "10.0.1.0/24", "10.0.1.0/24", TRUE },
+       { "0.0.0.0/0", NULL, "10.0.1.0/24 10.0.2.0/24",
+               "10.0.1.0/24 10.0.2.0/24", "10.0.1.0/24 10.0.2.0/24", TRUE },
+       { "0.0.0.0/0", NULL, "10.0.2.0/24 10.0.1.0/24",
+               "10.0.2.0/24 10.0.1.0/24", "10.0.2.0/24 10.0.1.0/24", TRUE },
+       { "10.0.1.0/24", NULL, "0.0.0.0/0", "10.0.1.0/24", "10.0.1.0/24", FALSE },
+       { "10.0.1.0/24", "10.0.1.1", "0.0.0.0/0", "10.0.1.0/24", "10.0.1.1/32", FALSE },
+       { "10.0.1.0/24 10.0.2.0/24", NULL, "0.0.0.0/0",
+               "10.0.1.0/24 10.0.2.0/24", "10.0.1.0/24 10.0.2.0/24", FALSE },
+       { "10.0.2.0/24 10.0.1.0/24", NULL, "0.0.0.0/0",
+               "10.0.2.0/24 10.0.1.0/24", "10.0.2.0/24 10.0.1.0/24", FALSE },
+       { "10.0.1.0/24 10.0.2.0/24", NULL, "10.0.2.0/24 10.0.1.0/24",
+               "10.0.1.0/24 10.0.2.0/24", "10.0.1.0/24 10.0.2.0/24", FALSE },
+       { "10.0.1.0/24 10.0.2.0/24", NULL, "10.0.1.1/32 10.0.1.0/24",
+               "10.0.1.0/24", "10.0.1.0/24", TRUE },
+       { "10.0.1.0/24 10.0.2.0/24", NULL, "10.0.2.1/32 10.0.1.0/24",
+               "10.0.1.0/24 10.0.2.1/32", "10.0.1.0/24 10.0.2.1/32", TRUE },
+};
+
+START_TEST(test_list_select)
+{
+       traffic_selector_list_t *ts;
+       linked_list_t *list, *hosts, *supplied, *result;
+       bool narrowed = FALSE;
+
+       list = create_list(list_select_tests[_i].ts, FALSE);
+       ts = traffic_selector_list_create_from_enumerator(list->create_enumerator(list));
+       hosts = list_select_tests[_i].hosts ? create_list(list_select_tests[_i].hosts, TRUE) : NULL;
+       supplied = list_select_tests[_i].supplied ? create_list(list_select_tests[_i].supplied, FALSE) : NULL;
+
+       result = ts->select(ts, supplied, hosts, FALSE, &narrowed);
+       verify_list(list_select_tests[_i].select, NULL, result);
+       ck_assert(narrowed == list_select_tests[_i].narrowed);
+
+       result = ts->select(ts, supplied, hosts, TRUE, &narrowed);
+       verify_list(list_select_tests[_i].select_force, NULL, result);
+       ck_assert(narrowed == list_select_tests[_i].narrowed);
+
+       DESTROY_OFFSET_IF(hosts, offsetof(host_t, destroy));
+       DESTROY_OFFSET_IF(supplied, offsetof(traffic_selector_t, destroy));
+       list->destroy_offset(list, offsetof(traffic_selector_t, destroy));
+       ts->destroy(ts);
+}
+END_TEST
+
+START_TEST(test_list_equals)
+{
+       traffic_selector_list_t *ts1, *ts2;
+
+       ts1 = traffic_selector_list_create();
+       ts2 = traffic_selector_list_create();
+       ck_assert(ts1->equals(ts1, ts2));
+
+       ts1->add(ts1, traffic_selector_create_from_cidr("10.0.1.0/24", 0, 0, 65535));
+       ck_assert(!ts1->equals(ts1, ts2));
+
+       ts2->add(ts2, traffic_selector_create_from_cidr("10.0.1.0/24", 0, 0, 65535));
+       ck_assert(ts1->equals(ts1, ts2));
+
+       ts1->destroy(ts1);
+       ts2->destroy(ts2);
+}
+END_TEST
+
+START_TEST(test_list_clone)
+{
+       traffic_selector_list_t *ts1, *ts2;
+
+       ts1 = traffic_selector_list_create();
+       ts2 = ts1->clone(ts1);
+       ck_assert(ts1->equals(ts1, ts2));
+       ts2->destroy(ts2);
+
+       ts1->add(ts1, traffic_selector_create_from_cidr("10.0.1.0/24", 0, 0, 65535));
+       ts2 = ts1->clone(ts1);
+       ck_assert(ts1->equals(ts1, ts2));
+       ts2->destroy(ts2);
+
+       ts1->add(ts1, traffic_selector_create_from_cidr("10.0.1.0/24", 0, 0, 65535));
+       ts2 = ts1->clone(ts1);
+       ck_assert(ts1->equals(ts1, ts2));
+
+       ts1->destroy(ts1);
+       ts2->destroy(ts2);
+}
+END_TEST
+
 Suite *traffic_selector_suite_create()
 {
        Suite *s;
@@ -890,5 +1084,12 @@ Suite *traffic_selector_suite_create()
        tcase_add_test(tc, test_printf_hook_hash);
        suite_add_tcase(s, tc);
 
+       tc = tcase_create("list");
+       tcase_add_loop_test(tc, test_list_get, 0, 0/*countof(list_get_tests)*/);
+       tcase_add_loop_test(tc, test_list_select, 0, countof(list_select_tests));
+       tcase_add_test(tc, test_list_equals);
+       tcase_add_test(tc, test_list_clone);
+       suite_add_tcase(s, tc);
+
        return s;
 }