summaryrefslogtreecommitdiffstats
path: root/src/plugins
diff options
context:
space:
mode:
authorAndrew Yourtchenko <ayourtch@gmail.com>2017-06-08 20:03:35 +0200
committerOle Trøan <otroan@employees.org>2017-06-15 20:27:39 +0000
commit779c3e3a632f887a7249a5cae8cce6eeacb67e3f (patch)
treef240f028618e3a064302823b45d75085387c862e /src/plugins
parentb2d5ff349d2c6cb2b733375dca4952cdeab2e7d3 (diff)
acl-plugin: store sessions in a single hash table instead of a per-interface
A bihash-per-interface is convenient, but turns out tricky difficult from the maintenance standpoint with the large number of interfaces. This patch makes the sessions reside in a single hash table for all the interfaces, adding the lower 16 bit of sw_if_index as part of the key into the previously unused space. There is a tradeoff, that a session with an identical 5-tuple and the same sw_if_index modulo 65536 will match on either of the interfaces. The probability of that is deemed sufficiently small to not worry about it. In case it still happens before the heat death of the universe, there is a clib_warning and the colliding packet will be dropped, at which point we will need to bump the hash key size by another u64, but rather not pay the cost of doing that right now. Change-Id: I2747839cfcceda73e597cbcafbe1e377fb8f1889 Signed-off-by: Andrew Yourtchenko <ayourtch@gmail.com>
Diffstat (limited to 'src/plugins')
-rw-r--r--src/plugins/acl/acl.c5
-rw-r--r--src/plugins/acl/acl.h9
-rw-r--r--src/plugins/acl/fa_node.c45
-rw-r--r--src/plugins/acl/fa_node.h2
4 files changed, 39 insertions, 22 deletions
diff --git a/src/plugins/acl/acl.c b/src/plugins/acl/acl.c
index 4174a570183..e7b8549535d 100644
--- a/src/plugins/acl/acl.c
+++ b/src/plugins/acl/acl.c
@@ -1823,6 +1823,11 @@ acl_show_aclplugin_fn (vlib_main_t * vm,
u64 n_dels = sw_if_index < vec_len(am->fa_session_dels_by_sw_if_index) ? am->fa_session_dels_by_sw_if_index[sw_if_index] : 0;
out0 = format(out0, "sw_if_index %d: add %lu - del %lu = %lu\n", sw_if_index, n_adds, n_dels, n_adds - n_dels);
}));
+ {
+ u64 n_adds = am->fa_session_total_adds;
+ u64 n_dels = am->fa_session_total_dels;
+ out0 = format(out0, "TOTAL: add %lu - del %lu = %lu\n", n_adds, n_dels, n_adds - n_dels);
+ }
out0 = format(out0, "\n\nPer-worker data:\n");
for (wk = 0; wk < vec_len (am->per_worker_data); wk++) {
acl_fa_per_worker_data_t *pw = &am->per_worker_data[wk];
diff --git a/src/plugins/acl/acl.h b/src/plugins/acl/acl.h
index 02623a9ce4b..65bc9e74ead 100644
--- a/src/plugins/acl/acl.h
+++ b/src/plugins/acl/acl.h
@@ -135,9 +135,9 @@ typedef struct {
/* bitmaps when set the processing is enabled on the interface */
uword *fa_in_acl_on_sw_if_index;
uword *fa_out_acl_on_sw_if_index;
- /* bitmap, when set the hash is initialized */
- uword *fa_sessions_on_sw_if_index;
- clib_bihash_40_8_t *fa_sessions_by_sw_if_index;
+ /* bihash holding all of the sessions */
+ int fa_sessions_hash_is_initialized;
+ clib_bihash_40_8_t fa_sessions_hash;
/* The process node which orcherstrates the cleanup */
u32 fa_cleaner_node_index;
/* FA session timeouts, in seconds */
@@ -145,6 +145,9 @@ typedef struct {
/* session add/delete counters */
u64 *fa_session_adds_by_sw_if_index;
u64 *fa_session_dels_by_sw_if_index;
+ /* total session adds/dels */
+ u64 fa_session_total_adds;
+ u64 fa_session_total_dels;
/* L2 datapath glue */
diff --git a/src/plugins/acl/fa_node.c b/src/plugins/acl/fa_node.c
index 78b10dc9504..66621b6ba78 100644
--- a/src/plugins/acl/fa_node.c
+++ b/src/plugins/acl/fa_node.c
@@ -494,9 +494,7 @@ acl_make_5tuple_session_key (int is_input, fa_5tuple_t * p5tuple_pkt,
static int
acl_fa_ifc_has_sessions (acl_main_t * am, int sw_if_index0)
{
- int has_sessions =
- clib_bitmap_get (am->fa_sessions_on_sw_if_index, sw_if_index0);
- return has_sessions;
+ return am->fa_sessions_hash_is_initialized;
}
static int
@@ -594,13 +592,11 @@ acl_fa_ifc_init_sessions (acl_main_t * am, int sw_if_index0)
sw_if_index0, am->fa_conn_table_hash_num_buckets,
am->fa_conn_table_hash_memory_size);
#endif
- vec_validate (am->fa_sessions_by_sw_if_index, sw_if_index0);
- BV (clib_bihash_init) (&am->fa_sessions_by_sw_if_index
- [sw_if_index0], "ACL plugin FA session bihash",
+ BV (clib_bihash_init) (&am->fa_sessions_hash,
+ "ACL plugin FA session bihash",
am->fa_conn_table_hash_num_buckets,
am->fa_conn_table_hash_memory_size);
- am->fa_sessions_on_sw_if_index =
- clib_bitmap_set (am->fa_sessions_on_sw_if_index, sw_if_index0, 1);
+ am->fa_sessions_hash_is_initialized = 1;
}
static inline fa_session_t *get_session_ptr(acl_main_t *am, u16 thread_index, u32 session_index)
@@ -715,7 +711,7 @@ acl_fa_delete_session (acl_main_t * am, u32 sw_if_index, fa_full_session_id_t se
{
fa_session_t *sess = get_session_ptr(am, sess_id.thread_index, sess_id.session_index);
ASSERT(sess->thread_index == os_get_thread_index ());
- BV (clib_bihash_add_del) (&am->fa_sessions_by_sw_if_index[sw_if_index],
+ BV (clib_bihash_add_del) (&am->fa_sessions_hash,
&sess->info.kv, 0);
acl_fa_per_worker_data_t *pw = &am->per_worker_data[sess_id.thread_index];
pool_put_index (pw->fa_sessions_pool, sess_id.session_index);
@@ -723,18 +719,15 @@ acl_fa_delete_session (acl_main_t * am, u32 sw_if_index, fa_full_session_id_t se
as the caller must have dealt with the timers. */
vec_validate (am->fa_session_dels_by_sw_if_index, sw_if_index);
am->fa_session_dels_by_sw_if_index[sw_if_index]++;
+ clib_smp_atomic_add(&am->fa_session_total_dels, 1);
}
static int
acl_fa_can_add_session (acl_main_t * am, int is_input, u32 sw_if_index)
{
- u64 curr_sess;
- vec_validate (am->fa_session_adds_by_sw_if_index, sw_if_index);
- vec_validate (am->fa_session_dels_by_sw_if_index, sw_if_index);
- curr_sess =
- am->fa_session_adds_by_sw_if_index[sw_if_index] -
- am->fa_session_dels_by_sw_if_index[sw_if_index];
- return (curr_sess < am->fa_conn_table_max_entries);
+ u64 curr_sess_count;
+ curr_sess_count = am->fa_session_total_adds - am->fa_session_total_dels;
+ return (curr_sess_count < am->fa_conn_table_max_entries);
}
static u64
@@ -889,12 +882,13 @@ acl_fa_add_session (acl_main_t * am, int is_input, u32 sw_if_index, u64 now,
acl_fa_ifc_init_sessions (am, sw_if_index);
}
- BV (clib_bihash_add_del) (&am->fa_sessions_by_sw_if_index[sw_if_index],
+ BV (clib_bihash_add_del) (&am->fa_sessions_hash,
&kv, 1);
acl_fa_conn_list_add_session(am, f_sess_id, now);
vec_validate (am->fa_session_adds_by_sw_if_index, sw_if_index);
am->fa_session_adds_by_sw_if_index[sw_if_index]++;
+ clib_smp_atomic_add(&am->fa_session_total_adds, 1);
}
static int
@@ -902,7 +896,7 @@ acl_fa_find_session (acl_main_t * am, u32 sw_if_index0, fa_5tuple_t * p5tuple,
clib_bihash_kv_40_8_t * pvalue_sess)
{
return (BV (clib_bihash_search)
- (&am->fa_sessions_by_sw_if_index[sw_if_index0], &p5tuple->kv,
+ (&am->fa_sessions_hash, &p5tuple->kv,
pvalue_sess) == 0);
}
@@ -977,6 +971,7 @@ acl_fa_node_fn (vlib_main_t * vm,
*/
acl_fill_5tuple (am, b0, is_ip6, is_input, is_l2_path, &fa_5tuple);
+ fa_5tuple.l4.lsb_of_sw_if_index = sw_if_index0 & 0xffff;
acl_make_5tuple_session_key (is_input, &fa_5tuple, &kv_sess);
#ifdef FA_NODE_VERBOSE_DEBUG
clib_warning
@@ -1024,6 +1019,20 @@ acl_fa_node_fn (vlib_main_t * vm,
0x00010000 + ((0xff & old_timeout_type) << 8) +
(0xff & new_timeout_type);
}
+ /*
+ * I estimate the likelihood to be very low - the VPP needs
+ * to have >64K interfaces to start with and then on
+ * exactly 64K indices apart needs to be exactly the same
+ * 5-tuple... Anyway, since this probability is nonzero -
+ * print an error and drop the unlucky packet.
+ * If this shows up in real world, we would need to bump
+ * the hash key length.
+ */
+ if (PREDICT_FALSE(sess->sw_if_index != sw_if_index0)) {
+ clib_warning("BUG: session LSB16(sw_if_index) and 5-tuple collision!");
+ acl_check_needed = 0;
+ action = 0;
+ }
}
}
diff --git a/src/plugins/acl/fa_node.h b/src/plugins/acl/fa_node.h
index a94e7db9eea..671593a8c99 100644
--- a/src/plugins/acl/fa_node.h
+++ b/src/plugins/acl/fa_node.h
@@ -36,7 +36,7 @@ typedef union {
struct {
u16 port[2];
u16 proto;
- u16 rsvd;
+ u16 lsb_of_sw_if_index;
};
} fa_session_l4_key_t;
ktick */ .highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */ .highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */ .highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */ .highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */ .highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */ .highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */ .highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */ .highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */ .highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */ .highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */ .highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */ .highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */ .highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */ .highlight .vc { color: #336699 } /* Name.Variable.Class */ .highlight .vg { color: #dd7700 } /* Name.Variable.Global */ .highlight .vi { color: #3333bb } /* Name.Variable.Instance */ .highlight .vm { color: #336699 } /* Name.Variable.Magic */ .highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */ }
#!/usr/bin/env python3

import unittest

from framework import VppTestCase, VppTestRunner
from vpp_ip_route import VppIpTable

from scapy.packet import Raw
from scapy.layers.l2 import Ether
from scapy.layers.inet import IP, UDP, ICMP
from scapy.layers.inet6 import IPv6

from vpp_papi import VppEnum

NUM_PKTS = 67


class TestSVS(VppTestCase):
    """ SVS Test Case """

    @classmethod
    def setUpClass(cls):
        super(TestSVS, cls).setUpClass()

    @classmethod
    def tearDownClass(cls):
        super(TestSVS, cls).tearDownClass()

    def setUp(self):
        super(TestSVS, self).setUp()

        # create 2 pg interfaces
        self.create_pg_interfaces(range(4))

        table_id = 0

        for i in self.pg_interfaces:
            i.admin_up()

            if table_id != 0:
                tbl = VppIpTable(self, table_id)
                tbl.add_vpp_config()
                tbl = VppIpTable(self, table_id, is_ip6=1)
                tbl.add_vpp_config()

            i.set_table_ip4(table_id)
            i.set_table_ip6(table_id)
            i.config_ip4()
            i.resolve_arp()
            i.config_ip6()
            i.resolve_ndp()
            table_id += 1

    def tearDown(self):
        for i in self.pg_interfaces:
            i.unconfig_ip4()
            i.unconfig_ip6()
            i.set_table_ip4(0)
            i.set_table_ip6(0)
            i.admin_down()
        super(TestSVS, self).tearDown()

    def test_svs4(self):
        """ Source VRF Select IP4 """

        #
        # packets destined out of the 3 non-default table interfaces
        #
        pkts_0 = [(Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
                   IP(src="1.1.1.1", dst=self.pg1.remote_ip4) /
                   UDP(sport=1234, dport=1234) /
                   Raw(b'\xa5' * 100)),
                  (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
                   IP(src="2.2.2.2", dst=self.pg2.remote_ip4) /
                   UDP(sport=1234, dport=1234) /
                   Raw(b'\xa5' * 100)),
                  (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
                   IP(src="3.3.3.3", dst=self.pg3.remote_ip4) /
                   UDP(sport=1234, dport=1234) /
                   Raw(b'\xa5' * 100))]
        pkts_1 = [(Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac) /
                   IP(src="1.1.1.1", dst=self.pg1.remote_ip4) /
                   UDP(sport=1234, dport=1234) /
                   Raw(b'\xa5' * 100)),
                  (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac) /
                   IP(src="2.2.2.2", dst=self.pg2.remote_ip4) /
                   UDP(sport=1234, dport=1234) /
                   Raw(b'\xa5' * 100)),
                  (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac) /
                   IP(src="3.3.3.3", dst=self.pg3.remote_ip4) /
                   UDP(sport=1234, dport=1234) /
                   Raw(b'\xa5' * 100))]

        #
        # before adding the SVS config all these packets are dropped when
        # ingressing on pg0 since pg0 is in the default table
        #
        for p in pkts_0:
            self.send_and_assert_no_replies(self.pg0, p * 1)

        #
        # Add table 1001 & 1002 into which we'll add the routes
        # determining the source VRF selection
        #
        table_ids = [101, 102]

        for table_id in table_ids:
            self.vapi.svs_table_add_del(
                is_add=1,
                af=VppEnum.vl_api_address_family_t.ADDRESS_IP4,
                table_id=table_id)

            #
            # map X.0.0.0/8 to each SVS table for lookup in table X
            #
            for i in range(1, 4):
                self.vapi.svs_route_add_del(
                    is_add=1,
                    prefix="%d.0.0.0/8" % i,
                    table_id=table_id,
                    source_table_id=i)

        #
        # Enable SVS on pg0/pg1 using table 1001/1002
        #
        self.vapi.svs_enable_disable(
            is_enable=1,
            af=VppEnum.vl_api_address_family_t.ADDRESS_IP4,
            table_id=table_ids[0],
            sw_if_index=self.pg0.sw_if_index)
        self.vapi.svs_enable_disable(
            is_enable=1,
            af=VppEnum.vl_api_address_family_t.ADDRESS_IP4,
            table_id=table_ids[1],
            sw_if_index=self.pg1.sw_if_index)

        #
        # now all the packets should be delivered out the respective interface
        #
        self.send_and_expect(self.pg0, pkts_0[0] * NUM_PKTS, self.pg1)
        self.send_and_expect(self.pg0, pkts_0[1] * NUM_PKTS, self.pg2)
        self.send_and_expect(self.pg0, pkts_0[2] * NUM_PKTS, self.pg3)
        self.send_and_expect(self.pg1, pkts_1[0] * NUM_PKTS, self.pg1)
        self.send_and_expect(self.pg1, pkts_1[1] * NUM_PKTS, self.pg2)
        self.send_and_expect(self.pg1, pkts_1[2] * NUM_PKTS, self.pg3)

        #
        # check that if the SVS lookup does not match a route the packet
        # is forwarded using the interface's routing table
        #
        p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
             IP(src=self.pg0.remote_ip4, dst=self.pg0.remote_ip4) /
             UDP(sport=1234, dport=1234) /
             Raw(b'\xa5' * 100))
        self.send_and_expect(self.pg0, p * NUM_PKTS, self.pg0)

        p = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac) /
             IP(src=self.pg1.remote_ip4, dst=self.pg1.remote_ip4) /
             UDP(sport=1234, dport=1234) /
             Raw(b'\xa5' * 100))
        self.send_and_expect(self.pg1, p * NUM_PKTS, self.pg1)

        #
        # dump the SVS configs
        #
        ss = self.vapi.svs_dump()

        self.assertEqual(ss[0].table_id, table_ids[0])
        self.assertEqual(ss[0].sw_if_index, self.pg0.sw_if_index)
        self.assertEqual(ss[0].af, VppEnum.vl_api_address_family_t.ADDRESS_IP4)
        self.assertEqual(ss[1].table_id, table_ids[1])
        self.assertEqual(ss[1].sw_if_index, self.pg1.sw_if_index)
        self.assertEqual(ss[1].af, VppEnum.vl_api_address_family_t.ADDRESS_IP4)

        #
        # cleanup
        #
        self.vapi.svs_enable_disable(
            is_enable=0,
            af=VppEnum.vl_api_address_family_t.ADDRESS_IP4,
            table_id=table_ids[0],
            sw_if_index=self.pg0.sw_if_index)
        self.vapi.svs_enable_disable(
            is_enable=0,
            af=VppEnum.vl_api_address_family_t.ADDRESS_IP4,
            table_id=table_ids[1],
            sw_if_index=self.pg1.sw_if_index)

        for table_id in table_ids:
            for i in range(1, 4):
                self.vapi.svs_route_add_del(
                    is_add=0,
                    prefix="%d.0.0.0/8" % i,
                    table_id=table_id,
                    source_table_id=0)

            self.vapi.svs_table_add_del(
                is_add=0,
                af=VppEnum.vl_api_address_family_t.ADDRESS_IP4,
                table_id=table_id)

    def test_svs6(self):
        """ Source VRF Select IP6 """

        #
        # packets destined out of the 3 non-default table interfaces
        #
        pkts_0 = [(Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
                   IPv6(src="2001:1::1", dst=self.pg1.remote_ip6) /
                   UDP(sport=1234, dport=1234) /
                   Raw(b'\xa5' * 100)),
                  (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
                   IPv6(src="2001:2::1", dst=self.pg2.remote_ip6) /
                   UDP(sport=1234, dport=1234) /
                   Raw(b'\xa5' * 100)),
                  (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
                   IPv6(src="2001:3::1", dst=self.pg3.remote_ip6) /
                   UDP(sport=1234, dport=1234) /
                   Raw(b'\xa5' * 100))]
        pkts_1 = [(Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac) /
                   IPv6(src="2001:1::1", dst=self.pg1.remote_ip6) /
                   UDP(sport=1234, dport=1234) /
                   Raw(b'\xa5' * 100)),
                  (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac) /
                   IPv6(src="2001:2::1", dst=self.pg2.remote_ip6) /
                   UDP(sport=1234, dport=1234) /
                   Raw(b'\xa5' * 100)),
                  (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac) /
                   IPv6(src="2001:3::1", dst=self.pg3.remote_ip6) /
                   UDP(sport=1234, dport=1234) /
                   Raw(b'\xa5' * 100))]

        #
        # before adding the SVS config all these packets are dropped when
        # ingressing on pg0 since pg0 is in the default table
        #
        for p in pkts_0:
            self.send_and_assert_no_replies(self.pg0, p * 1)

        #
        # Add table 1001 & 1002 into which we'll add the routes
        # determining the source VRF selection
        #
        table_ids = [101, 102]

        for table_id in table_ids:
            self.vapi.svs_table_add_del(
                is_add=1,
                af=VppEnum.vl_api_address_family_t.ADDRESS_IP6,
                table_id=table_id)

            #
            # map X.0.0.0/8 to each SVS table for lookup in table X
            #
            for i in range(1, 4):
                self.vapi.svs_route_add_del(
                    is_add=1,
                    prefix="2001:%d::/32" % i,
                    table_id=table_id,
                    source_table_id=i)

        #
        # Enable SVS on pg0/pg1 using table 1001/1002
        #
        self.vapi.svs_enable_disable(
            is_enable=1,
            af=VppEnum.vl_api_address_family_t.ADDRESS_IP6,
            table_id=table_ids[0],
            sw_if_index=self.pg0.sw_if_index)
        self.vapi.svs_enable_disable(
            is_enable=1,
            af=VppEnum.vl_api_address_family_t.ADDRESS_IP6,
            table_id=table_ids[1],
            sw_if_index=self.pg1.sw_if_index)

        #
        # now all the packets should be delivered out the respective interface
        #
        self.send_and_expect(self.pg0, pkts_0[0] * NUM_PKTS, self.pg1)
        self.send_and_expect(self.pg0, pkts_0[1] * NUM_PKTS, self.pg2)
        self.send_and_expect(self.pg0, pkts_0[2] * NUM_PKTS, self.pg3)
        self.send_and_expect(self.pg1, pkts_1[0] * NUM_PKTS, self.pg1)
        self.send_and_expect(self.pg1, pkts_1[1] * NUM_PKTS, self.pg2)
        self.send_and_expect(self.pg1, pkts_1[2] * NUM_PKTS, self.pg3)

        #
        # check that if the SVS lookup does not match a route the packet
        # is forwarded using the interface's routing table
        #
        p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
             IPv6(src=self.pg0.remote_ip6, dst=self.pg0.remote_ip6) /
             UDP(sport=1234, dport=1234) /
             Raw(b'\xa5' * 100))
        self.send_and_expect(self.pg0, p * NUM_PKTS, self.pg0)

        p = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac) /
             IPv6(src=self.pg1.remote_ip6, dst=self.pg1.remote_ip6) /
             UDP(sport=1234, dport=1234) /
             Raw(b'\xa5' * 100))
        self.send_and_expect(self.pg1, p * NUM_PKTS, self.pg1)

        #
        # dump the SVS configs
        #
        ss = self.vapi.svs_dump()

        self.assertEqual(ss[0].table_id, table_ids[0])
        self.assertEqual(ss[0].sw_if_index, self.pg0.sw_if_index)
        self.assertEqual(ss[0].af, VppEnum.vl_api_address_family_t.ADDRESS_IP6)
        self.assertEqual(ss[1].table_id, table_ids[1])
        self.assertEqual(ss[1].sw_if_index, self.pg1.sw_if_index)
        self.assertEqual(ss[1].af, VppEnum.vl_api_address_family_t.ADDRESS_IP6)

        #
        # cleanup
        #
        self.vapi.svs_enable_disable(
            is_enable=0,
            af=VppEnum.vl_api_address_family_t.ADDRESS_IP6,
            table_id=table_ids[0],
            sw_if_index=self.pg0.sw_if_index)
        self.vapi.svs_enable_disable(
            is_enable=0,
            af=VppEnum.vl_api_address_family_t.ADDRESS_IP6,
            table_id=table_ids[1],
            sw_if_index=self.pg1.sw_if_index)

        for table_id in table_ids:
            for i in range(1, 4):
                self.vapi.svs_route_add_del(
                    is_add=0,
                    prefix="2001:%d::/32" % i,
                    table_id=table_id,
                    source_table_id=0)

            self.vapi.svs_table_add_del(
                is_add=0,
                af=VppEnum.vl_api_address_family_t.ADDRESS_IP6,
                table_id=table_id)

if __name__ == '__main__':
    unittest.main(testRunner=VppTestRunner)