/* * mc.c: vlib reliable sequenced multicast distributed applications * * Copyright (c) 2010 Cisco and/or its affiliates. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include /* * 1 to enable msg id training wheels, which are useful for tracking * down catchup and/or partitioned network problems */ #define MSG_ID_DEBUG 0 static format_function_t format_mc_stream_state; static u32 elog_id_for_peer_id (mc_main_t * m, u64 peer_id) { uword *p, r; mhash_t *h = &m->elog_id_by_peer_id; if (!m->elog_id_by_peer_id.hash) mhash_init (h, sizeof (uword), sizeof (mc_peer_id_t)); p = mhash_get (h, &peer_id); if (p) return p[0]; r = elog_string (m->elog_main, "%U", m->transport.format_peer_id, peer_id); mhash_set (h, &peer_id, r, /* old_value */ 0); return r; } static u32 elog_id_for_msg_name (mc_main_t * m, char *msg_name) { uword *p, r; uword *h = m->elog_id_by_msg_name; u8 *name_copy; if (!h) h = m->elog_id_by_msg_name = hash_create_string (0, sizeof (uword)); p = hash_get_mem (h, msg_name); if (p) return p[0]; r = elog_string (m->elog_main, "%s", msg_name); name_copy = format (0, "%s%c", msg_name, 0); hash_set_mem (h, name_copy, r); m->elog_id_by_msg_name = h; return r; } static void elog_tx_msg (mc_main_t * m, u32 stream_id, u32 local_sequence, u32 retry_count) { if (MC_EVENT_LOGGING > 0) { /* *INDENT-OFF* */ ELOG_TYPE_DECLARE (e) = { .format = "tx-msg: stream %d local seq %d attempt %d", .format_args = "i4i4i4", }; /* *INDENT-ON* */ struct { u32 stream_id, local_sequence, retry_count; } *ed; ed = ELOG_DATA (m->elog_main, e); ed->stream_id = stream_id; ed->local_sequence = local_sequence; ed->retry_count = retry_count; } } /* * seq_cmp * correctly compare two unsigned sequence numbers. * This function works so long as x and y are within 2**(n-1) of each * other, where n = bits(x, y). * * Magic decoder ring: * seq_cmp == 0 => x and y are equal * seq_cmp < 0 => x is "in the past" with respect to y * seq_cmp > 0 => x is "in the future" with respect to y */ always_inline i32 mc_seq_cmp (u32 x, u32 y) { return (i32) x - (i32) y; } void * mc_get_vlib_buffer (vlib_main_t * vm, u32 n_bytes, u32 * bi_return) { u32 n_alloc, bi; vlib_buffer_t *b; n_alloc = vlib_buffer_alloc (vm, &bi, 1); ASSERT (n_alloc == 1); b = vlib_get_buffer (vm, bi); b->current_length = n_bytes; *bi_return = bi; return (void *) b->data; } static void delete_peer_with_index (mc_main_t * mcm, mc_stream_t * s, uword index, int notify_application) { mc_stream_peer_t *p = pool_elt_at_index (s->peers, index); ASSERT (p != 0); if (s->config.peer_died && notify_application) s->config.peer_died (mcm, s, p->id); s->all_peer_bitmap = clib_bitmap_andnoti (s->all_peer_bitmap, p - s->peers); if (MC_EVENT_LOGGING > 0) { /* *INDENT-OFF* */ ELOG_TYPE_DECLARE (e) = { .format = "delete peer %s from all_peer_bitmap", .format_args = "T4", }; /* *INDENT-ON* */ struct { u32 peer; } *ed = 0; ed = ELOG_DATA (mcm->elog_main, e); ed->peer = elog_id_for_peer_id (mcm, p->id.as_u64); } /* Do not delete the pool / hash table entries, or we lose sequence number state */ } static mc_stream_peer_t * get_or_create_peer_with_id (mc_main_t * mcm, mc_stream_t * s, mc_peer_id_t id, int *created) { uword *q = mhash_get (&s->peer_index_by_id, &id); mc_stream_peer_t *p; if (q) { p = pool_elt_at_index (s->peers, q[0]); goto done; } pool_get (s->peers, p); memset (p, 0, sizeof (p[0])); p->id = id; p->last_sequence_received = ~0; mhash_set (&s->peer_index_by_id, &id, p - s->peers, /* old_value */ 0); if (created) *created = 1; done: if (MC_EVENT_LOGGING > 0) { /* *INDENT-OFF* */ ELOG_TYPE_DECLARE (e) = { .format = "get_or_create %s peer %s stream %d seq %d", .format_args = "t4T4i4i4", .n_enum_strings = 2, .enum_strings = { "old", "new", }, }; /* *INDENT-ON* */ struct { u32 is_new, peer, stream_index, rx_sequence; } *ed = 0; ed = ELOG_DATA (mcm->elog_main, e); ed->is_new = q ? 0 : 1; ed->peer = elog_id_for_peer_id (mcm, p->id.as_u64); ed->stream_index = s->index; ed->rx_sequence = p->last_sequence_received; } /* $$$$ Enable or reenable this peer */ s->all_peer_bitmap = clib_bitmap_ori (s->all_peer_bitmap, p - s->peers); return p; } static void maybe_send_window_open_event (vlib_main_t * vm, mc_stream_t * stream) { vlib_one_time_waiting_process_t *p; if (pool_elts (stream->retry_pool) >= stream->config.window_size) return; vec_foreach (p, stream->procs_waiting_for_open_window) vlib_signal_one_time_waiting_process (vm, p); if (stream->procs_waiting_for_open_window) _vec_len (stream->procs_waiting_for_open_window) = 0; } static void mc_retry_free (mc_main_t * mcm, mc_stream_t * s, mc_retry_t * r) { mc_retry_t record, *retp; if (r->unacked_by_peer_bitmap) _vec_len (r->unacked_by_peer_bitmap) = 0; if (clib_fifo_elts (s->retired_fifo) >= 2 * s->config.window_size) { clib_fifo_sub1 (s->retired_fifo, record); vlib_buffer_free_one (mcm->vlib_main, record.buffer_index); } clib_fifo_add2 (s->retired_fifo, retp); retp->buffer_index = r->buffer_index; retp->local_sequence = r->local_sequence; r->buffer_index = ~0; /* poison buffer index in this retry */ } static void mc_resend_retired (mc_main_t * mcm, mc_stream_t * s, u32 local_sequence) { mc_retry_t *retry; if (MC_EVENT_LOGGING > 0) { /* *INDENT-OFF* */ ELOG_TYPE_DECLARE (e) = { .format = "resend-retired: search for local seq %d", .format_args = "i4", }; /* *INDENT-ON* */ struct { u32 local_sequence; } *ed; ed = ELOG_DATA (mcm->elog_main, e); ed->local_sequence = local_sequence; } /* *INDENT-OFF* */ clib_fifo_foreach (retry, s->retired_fifo, ({ if (retry->local_sequence == local_sequence) { elog_tx_msg (mcm, s->index, retry-> local_sequence, -13); mcm->transport.tx_buffer (mcm->transport.opaque, MC_TRANSPORT_USER_REQUEST_TO_RELAY, retry->buffer_index); return; } })); /* *INDENT-ON* */ if (MC_EVENT_LOGGING > 0) { /* *INDENT-OFF* */ ELOG_TYPE_DECLARE (e) = { .format = "resend-retired: FAILED search for local seq %d", .format_args = "i4", }; /* *INDENT-ON* */ struct { u32 local_sequence; } *ed; ed = ELOG_DATA (mcm->elog_main, e); ed->local_sequence = local_sequence; } } static uword * delete_retry_fifo_elt (mc_main_t * mcm, mc_stream_t * stream, mc_retry_t * r, uword * dead_peer_bitmap) { mc_stream_peer_t *p; /* *INDENT-OFF* */ pool_foreach (p, stream->peers, ({ uword pi = p - stream->peers; uword is_alive = 0 == clib_bitmap_get (r->unacked_by_peer_bitmap, pi); if (! is_alive) dead_peer_bitmap = clib_bitmap_ori (dead_peer_bitmap, pi); if (MC_EVENT_LOGGING > 0) { ELOG_TYPE_DECLARE (e) = { .format = "delete_retry_fifo_elt: peer %s is %s", .format_args = "T4t4", .n_enum_strings = 2, .enum_strings = { "alive", "dead", }, }; struct { u32 peer, is_alive; } * ed; ed = ELOG_DATA (mcm->elog_main, e); ed->peer = elog_id_for_peer_id (mcm, p->id.as_u64); ed->is_alive = is_alive; } })); /* *INDENT-ON* */ hash_unset (stream->retry_index_by_local_sequence, r->local_sequence); mc_retry_free (mcm, stream, r); return dead_peer_bitmap; } always_inline mc_retry_t * prev_retry (mc_stream_t * s, mc_retry_t * r) { return (r->prev_index != ~0 ? pool_elt_at_index (s->retry_pool, r->prev_index) : 0); } always_inline mc_retry_t * next_retry (mc_stream_t * s, mc_retry_t * r) { return (r->next_index != ~0 ? pool_elt_at_index (s->retry_pool, r->next_index) : 0); } always_inline void remove_retry_from_pool (mc_stream_t * s, mc_retry_t * r) { mc_retry_t *p = prev_retry (s, r); mc_retry_t *n = next_retry (s, r); if (p) p->next_index = r->next_index; else s->retry_head_index = r->next_index; if (n) n->prev_index = r->prev_index; else s->retry_tail_index = r->prev_index; pool_put_index (s->retry_pool, r - s->retry_pool); } static void check_retry (mc_main_t * mcm, mc_stream_t * s) { mc_retry_t *r; vlib_main_t *vm = mcm->vlib_main; f64 now = vlib_time_now (vm); uword *dead_peer_bitmap = 0; u32 ri, ri_next; for (ri = s->retry_head_index; ri != ~0; ri = ri_next) { r = pool_elt_at_index (s->retry_pool, ri); ri_next = r->next_index; if (now < r->sent_at + s->config.retry_interval) continue; r->n_retries += 1; if (r->n_retries > s->config.retry_limit) { dead_peer_bitmap = delete_retry_fifo_elt (mcm, s, r, dead_peer_bitmap); remove_retry_from_pool (s, r); } else { if (MC_EVENT_LOGGING > 0) { mc_stream_peer_t *p; /* *INDENT-OFF* */ ELOG_TYPE_DECLARE (t) = { .format = "resend local seq %d attempt %d", .format_args = "i4i4", }; /* *INDENT-ON* */ /* *INDENT-OFF* */ pool_foreach (p, s->peers, ({ if (clib_bitmap_get (r->unacked_by_peer_bitmap, p - s->peers)) { ELOG_TYPE_DECLARE (ev) = { .format = "resend: needed by peer %s local seq %d", .format_args = "T4i4", }; struct { u32 peer, rx_sequence; } * ed; ed = ELOG_DATA (mcm->elog_main, ev); ed->peer = elog_id_for_peer_id (mcm, p->id.as_u64); ed->rx_sequence = r->local_sequence; } })); /* *INDENT-ON* */ struct { u32 sequence; u32 trail; } *ed; ed = ELOG_DATA (mcm->elog_main, t); ed->sequence = r->local_sequence; ed->trail = r->n_retries; } r->sent_at = vlib_time_now (vm); s->stats.n_retries += 1; elog_tx_msg (mcm, s->index, r->local_sequence, r->n_retries); mcm->transport.tx_buffer (mcm->transport.opaque, MC_TRANSPORT_USER_REQUEST_TO_RELAY, r->buffer_index); } } maybe_send_window_open_event (mcm->vlib_main, s); /* Delete any dead peers we've found. */ if (!clib_bitmap_is_zero (dead_peer_bitmap)) { uword i; /* *INDENT-OFF* */ clib_bitmap_foreach (i, dead_peer_bitmap, ({ delete_peer_with_index (mcm, s, i, /* notify_application */ 1); /* Delete any references to just deleted peer in retry pool. */ pool_foreach (r, s->retry_pool, ({ r->unacked_by_peer_bitmap = clib_bitmap_andnoti (r->unacked_by_peer_bitmap, i); })); })); /* *INDENT-ON* */ clib_bitmap_free (dead_peer_bitmap); } } always_inline mc_main_t * mc_node_get_main (vlib_node_runtime_t * node) { mc_main_t **p = (void *) node->runtime_data; return p[0]; } static uword mc_retry_process (vlib_main_t * vm, vlib_node_runtime_t * node, vlib_frame_t * f) { mc_main_t *mcm = mc_node_get_main (node); mc_stream_t *s; while (1) { vlib_process_suspend (vm, 1.0); vec_foreach (s, mcm->stream_vector) { if (s->state != MC_STREAM_STATE_invalid) check_retry (mcm, s); } } return 0; /* not likely */ } static void send_join_or_leave_request (mc_main_t * mcm, u32 stream_index, u32 is_join) { vlib_main_t *vm = mcm->vlib_main; mc_msg_join_or_leave_request_t *mp; u32 bi; mp = mc_get_vlib_buffer (vm, sizeof (mp[0]), &bi); memset (mp, 0, sizeof (*mp)); mp->type = MC_MSG_TYPE_join_or_leave_request; mp->peer_id = mcm->transport.our_ack_peer_id; mp->stream_index = stream_index; mp->is_join = is_join; mc_byte_swap_msg_join_or_leave_request (mp); /* * These msgs are unnumbered, unordered so send on the from-relay * channel. */ mcm->transport.tx_buffer (mcm->transport.opaque, MC_TRANSPORT_JOIN, bi); } static uword mc_join_ager_process (vlib_main_t * vm, vlib_node_runtime_t * node, vlib_frame_t * f) { mc_main_t *mcm = mc_node_get_main (node); while (1) { if (mcm->joins_in_progress) { mc_stream_t *s; vlib_one_time_waiting_process_t *p; f64 now = vlib_time_now (vm); vec_foreach (s, mcm->stream_vector) { if (s->state != MC_STREAM_STATE_join_in_progress) continue; if (now > s->join_timeout) { s->state = MC_STREAM_STATE_ready; if (MC_EVENT_LOGGING > 0) { /* *INDENT-OFF* */ ELOG_TYPE_DECLARE (e) = { .format = "stream %d join timeout", }; /* *INDENT-ON* */ ELOG (mcm->elog_main, e, s->index); } /* Make sure that this app instance exists as a stream peer, or we may answer a catchup request with a NULL all_peer_bitmap... */ (void) get_or_create_peer_with_id (mcm, s, mcm->transport.our_ack_peer_id, /* created */ 0); vec_foreach (p, s->procs_waiting_for_join_done) vlib_signal_one_time_waiting_process (vm, p); if (s->procs_waiting_for_join_done) _vec_len (s->procs_waiting_for_join_done) = 0; mcm->joins_in_progress--; ASSERT (mcm->joins_in_progress >= 0); } else { /* Resent join request which may have been lost. */ send_join_or_leave_request (mcm, s->index, 1 /* is_join */ ); /* We're *not* alone, retry for as long as it takes */ if (mcm->relay_state == MC_RELAY_STATE_SLAVE) s->join_timeout = vlib_time_now (vm) + 2.0; if (MC_EVENT_LOGGING > 0) { /* *INDENT-OFF* */ ELOG_TYPE_DECLARE (e) = { .format = "stream %d resend join request", }; /* *INDENT-ON* */ ELOG (mcm->elog_main, e, s->index); } } } } vlib_process_suspend (vm, .5); } return 0; /* not likely */ } static void serialize_mc_register_stream_name (serialize_main_t * m, va_list * va) { char *name = va_arg (*va, char *); serialize_cstring (m, name); } static void elog_stream_name (char *buf, int n_buf_bytes, char *v) { clib_memcpy (buf, v, clib_min (n_buf_bytes - 1, vec_len (v))); buf[n_buf_bytes - 1] = 0; } static void unserialize_mc_register_stream_name (serialize_main_t * m, va_list * va) { mc_main_t *mcm = va_arg (*va, mc_main_t *); char *name; mc_stream_t *s; uword *p; unserialize_cstring (m, &name); if ((p = hash_get_mem (mcm->stream_index_by_name, name))) { if (MC_EVENT_LOGGING > 0) { /* *INDENT-OFF* */ ELOG_TYPE_DECLARE (e) = { .format = "stream index %d already named %s", .format_args = "i4s16", }; /* *INDENT-ON* */ struct { u32 stream_index; char name[16]; } *ed; ed = ELOG_DATA (mcm->elog_main, e); ed->stream_index = p[0]; elog_stream_name (ed->name, sizeof (ed->name), name); } vec_free (name); return; } vec_add2 (mcm->stream_vector, s, 1); mc_stream_init (s); s->state = MC_STREAM_STATE_name_known; s->index = s - mcm->stream_vector; s->config.name = name; if (MC_EVENT_LOGGING > 0) { /* *INDENT-OFF* */ ELOG_TYPE_DECLARE (e) = { .format = "stream index %d named %s", .format_args = "i4s16", }; /* *INDENT-ON* */ struct { u32 stream_index; char name[16]; } *ed; ed = ELOG_DATA (mcm->elog_main, e); ed->stream_index = s->index; elog_stream_name (ed->name, sizeof (ed->name), name); } hash_set_mem (mcm->stream_index_by_name, name, s->index); p = hash_get (mcm->procs_waiting_for_stream_name_by_name, name); if (p) { vlib_one_time_waiting_process_t *wp, **w; w = pool_elt_at_index (mcm->procs_waiting_for_stream_name_pool, p[0]); vec_foreach (wp, w[0]) vlib_signal_one_time_waiting_process (mcm->vlib_main, wp); pool_put (mcm->procs_waiting_for_stream_name_pool, w); hash_unset_mem (mcm->procs_waiting_for_stream_name_by_name, name); } } /* *INDENT-OFF* */ MC_SERIALIZE_MSG (mc_register_stream_name_msg, static) = { .name = "mc_register_stream_name", .serialize = serialize_mc_register_stream_name, .unserialize = unserialize_mc_register_stream_name, }; /* *INDENT-ON* */ void mc_rx_buffer_unserialize (mc_main_t * mcm, mc_stream_t * stream, mc_peer_id_t peer_id, u32 buffer_index) { return mc_unserialize (mcm, stream, buffer_index); } static u8 * mc_internal_catchup_snapshot (mc_main_t * mcm, u8 * data_vector, u32 last_global_sequence_processed) { serialize_main_t m; /* Append serialized data to data vector. */ serialize_open_vector (&m, data_vector); m.stream.current_buffer_index = vec_len (data_vector); serialize (&m, serialize_mc_main, mcm); return serialize_close_vector (&m); }
from abc import abstractmethod, ABCMeta
import socket

from util import Host, mk_ll_addr
from vpp_neighbor import VppNeighbor


class VppInterface(object):
    """Generic VPP interface."""
    __metaclass__ = ABCMeta

    @property
    def sw_if_index(self):
        """Interface index assigned by VPP."""
        return self._sw_if_index

    @property
    def remote_mac(self):
        """MAC-address of the remote interface "connected" to this interface"""
        return self._remote_hosts[0].mac

    @property
    def local_mac(self):
        """MAC-address of the VPP interface."""
        return self._local_mac

    @property
    def local_ip4(self):
        """Local IPv4 address on VPP interface (string)."""
        return self._local_ip4

    @property
    def local_ip4n(self):
        """Local IPv4 address - raw, suitable as API parameter."""
        return socket.inet_pton(socket.AF_INET, self._local_ip4)

    @property
    def remote_ip4(self):
        """IPv4 address of remote peer "connected" to this interface."""
        return self._remote_hosts[0].ip4

    @property
    def remote_ip4n(self):
        """IPv4 address of remote peer - raw, suitable as API parameter."""
        return socket.inet_pton(socket.AF_INET, self.remote_ip4)

    @property
    def local_ip6(self):
        """Local IPv6 address on VPP interface (string)."""
        return self._local_ip6

    @property
    def local_ip6n(self):
        """Local IPv6 address - raw, suitable as API parameter."""
        return socket.inet_pton(socket.AF_INET6, self.local_ip6)

    @property
    def remote_ip6(self):
        """IPv6 address of remote peer "connected" to this interface."""
        return self._remote_hosts[0].ip6

    @property
    def remote_ip6n(self):
        """IPv6 address of remote peer - raw, suitable as API parameter"""
        return socket.inet_pton(socket.AF_INET6, self.remote_ip6)

    @property
    def local_ip6_ll(self):
        """Local IPv6 linnk-local address on VPP interface (string)."""
        return self._local_ip6_ll

    @property
    def local_ip6n_ll(self):
        """Local IPv6 link-local address - raw, suitable as API parameter."""
        return self._local_ip6n_ll

    @property
    def remote_ip6_ll(self):
        """Link-local IPv6 address of remote peer
        "connected" to this interface."""
        return self._remote_ip6_ll

    @property
    def remote_ip6n_ll(self):
        """Link-local IPv6 address of remote peer
        - raw, suitable as API parameter"""
        return self._remote_ip6n_ll

    @property
    def name(self):
        """Name of the interface."""
        return self._name

    @property
    def dump(self):
        """RAW result of sw_interface_dump for this interface."""
        return self._dump

    @property
    def test(self):
        """Test case creating this interface."""
        return self._test

    @property
    def remote_hosts(self):
        """Remote hosts list"""
        return self._remote_hosts

    @remote_hosts.setter
    def remote_hosts(self, value):
        """
        :param list value: List of remote hosts.
        """
        self._remote_hosts = value
        self._hosts_by_mac = {}
        self._hosts_by_ip4 = {}
        self._hosts_by_ip6 = {}
        for host in self._remote_hosts:
            self._hosts_by_mac[host.mac] = host
            self._hosts_by_ip4[host.ip4] = host
            self._hosts_by_ip6[host.ip6] = host

    def host_by_mac(self, mac):
        """
        :param mac: MAC address to find host by.
        :return: Host object assigned to interface.
        """
        return self._hosts_by_mac[mac]

    def host_by_ip4(self, ip):
        """
        :param ip: IPv4 address to find host by.
        :return: Host object assigned to interface.
        """
        return self._hosts_by_ip4[ip]

    def host_by_ip6(self, ip):
        """
        :param ip: IPv6 address to find host by.
        :return: Host object assigned to interface.
        """
        return self._hosts_by_ip6[ip]

    def generate_remote_hosts(self, count=1):
        """Generate and add remote hosts for the interface.

        :param int count: Number of generated remote hosts.
        """
        self._remote_hosts = []
        self._hosts_by_mac = {}
        self._hosts_by_ip4 = {}
        self._hosts_by_ip6 = {}
        for i in range(
                2, count + 2):  # 0: network address, 1: local vpp address
            mac = "02:%02x:00:00:ff:%02x" % (self.sw_if_index, i)
            ip4 = "172.16.%u.%u" % (self.sw_if_index, i)
            ip6 = "fd01:%x::%x" % (self.sw_if_index, i)
            ip6_ll = mk_ll_addr(mac)
            host = Host(mac, ip4, ip6, ip6_ll)
            self._remote_hosts.append(host)
            self._hosts_by_mac[mac] = host
            self._hosts_by_ip4[ip4] = host
            self._hosts_by_ip6[ip6] = host

    @abstractmethod
    def __init__(self, test):
        self._test = test

        self._remote_hosts = []
        self._hosts_by_mac = {}
        self._hosts_by_ip4 = {}
        self._hosts_by_ip6 = {}

        self.generate_remote_hosts()

        self._local_ip4 = "172.16.%u.1" % self.sw_if_index
        self._local_ip4n = socket.inet_pton(socket.AF_INET, self.local_ip4)
        self._local_ip4_subnet = "172.16.%u.0" % self.sw_if_index
        self._local_ip4n_subnet = socket.inet_pton(socket.AF_INET,
                                                   self._local_ip4_subnet)
        self._local_ip4_bcast = "172.16.%u.255" % self.sw_if_index
        self._local_ip4n_bcast = socket.inet_pton(socket.AF_INET,
                                                  self._local_ip4_bcast)
        self.local_ip4_prefix_len = 24
        self.has_ip4_config = False
        self.ip4_table_id = 0

        self._local_ip6 = "fd01:%x::1" % self.sw_if_index
        self._local_ip6n = socket.inet_pton(socket.AF_INET6, self.local_ip6)
        self.local_ip6_prefix_len = 64
        self.has_ip6_config = False
        self.ip6_table_id = 0

        r = self.test.vapi.sw_interface_dump()
        for intf in r:
            if intf.sw_if_index == self.sw_if_index:
                self._name = intf.interface_name.split(b'\0', 1)[0]
                self._local_mac = \
                    ':'.join(intf.l2_address.encode('hex')[i:i + 2]
                             for i in range(0, 12, 2))
                self._dump = intf
                break
        else:
            raise Exception(
                "Could not find interface with sw_if_index %d "
                "in interface dump %s" %
                (self.sw_if_index, repr(r)))
        self._local_ip6_ll = mk_ll_addr(self.local_mac)
        self._local_ip6n_ll = socket.inet_pton(socket.AF_INET6,
                                               self.local_ip6_ll)
        self._remote_ip6_ll = mk_ll_addr(self.remote_mac)
        self._remote_ip6n_ll = socket.inet_pton(socket.AF_INET6,
                                                self.remote_ip6_ll)

    def config_ip4(self):
        """Configure IPv4 address on the VPP interface."""
        self.test.vapi.sw_interface_add_del_address(
            self.sw_if_index, self.local_ip4n, self.local_ip4_prefix_len)
        self.has_ip4_config = True

    def unconfig_ip4(self):
        """Remove IPv4 address on the VPP interface."""
        try:
            if self.has_ip4_config:
                self.test.vapi.sw_interface_add_del_address(
                    self.sw_if_index,
                    self.local_ip4n,
                    self.local_ip4_prefix_len,
                    is_add=0)
        except AttributeError:
            self.has_ip4_config = False
        self.has_ip4_config = False

    def configure_ipv4_neighbors(self):
        """For every remote host assign neighbor's MAC to IPv4 addresses.

        :param vrf_id: The FIB table / VRF ID. (Default value = 0)
        """
        for host in self._remote_hosts:
            macn = host.mac.replace(":", "").decode('hex')
            ipn = host.ip4n
            self.test.vapi.ip_neighbor_add_del(
                self.sw_if_index, macn, ipn)

    def config_ip6(self):
        """Configure IPv6 address on the VPP interface."""
        self.test.vapi.sw_interface_add_del_address(
            self.sw_if_index, self._local_ip6n, self.local_ip6_prefix_len,
            is_ipv6=1)
        self.has_ip6_config = True

    def unconfig_ip6(self):
        """Remove IPv6 address on the VPP interface."""
        try:
            if self.has_ip6_config:
                self.test.vapi.sw_interface_add_del_address(
                    self.sw_if_index,
                    self.local_ip6n,
                    self.local_ip6_prefix_len,
                    is_ipv6=1, is_add=0)
        except AttributeError:
            self.has_ip6_config = False
        self.has_ip6_config = False

    def configure_ipv6_neighbors(self):
        """For every remote host assign neighbor's MAC to IPv6 addresses.

        :param vrf_id: The FIB table / VRF ID. (Default value = 0)
        """
        for host in self._remote_hosts:
            macn = host.mac.replace(":", "").decode('hex')
            ipn = host.ip6n
            self.test.vapi.ip_neighbor_add_del(
                self.sw_if_index, macn, ipn, is_ipv6=1)

    def unconfig(self):
        """Unconfigure IPv6 and IPv4 address on the VPP interface."""
        self.unconfig_ip4()
        self.unconfig_ip6()

    def set_table_ip4(self, table_id):
        """Set the interface in a IPv4 Table.

        .. note:: Must be called before configuring IP4 addresses.
        """
        self.ip4_table_id = table_id
        self.test.vapi.sw_interface_set_table(
            self.sw_if_index, 0, self.ip4_table_id)

    def set_table_ip6(self, table_id):
        """Set the interface in a IPv6 Table.

        .. note:: Must be called before configuring IP6 addresses.
        """
        self.ip6_table_id = table_id
        self.test.vapi.sw_interface_set_table(
            self.