#!/usr/bin/env python """ BFD tests """ from __future__ import division import unittest import hashlib import binascii import time from struct import pack, unpack from random import randint, shuffle, getrandbits from socket import AF_INET, AF_INET6, inet_ntop from scapy.packet import Raw from scapy.layers.l2 import Ether from scapy.layers.inet import UDP, IP from scapy.layers.inet6 import IPv6 from bfd import VppBFDAuthKey, BFD, BFDAuthType, VppBFDUDPSession, \ BFDDiagCode, BFDState, BFD_vpp_echo from framework import VppTestCase, VppTestRunner from vpp_pg_interface import CaptureTimeoutError from util import ppp from vpp_papi_provider import UnexpectedApiReturnValueError USEC_IN_SEC = 1000000 class AuthKeyFactory(object): """Factory class for creating auth keys with unique conf key ID""" def __init__(self): self._conf_key_ids = {} def create_random_key(self, test, auth_type=BFDAuthType.keyed_sha1): """ create a random key with unique conf key id """ conf_key_id = randint(0, 0xFFFFFFFF) while conf_key_id in self._conf_key_ids: conf_key_id = randint(0, 0xFFFFFFFF) self._conf_key_ids[conf_key_id] = 1 key = str(bytearray([randint(0, 255) for _ in range(randint(1, 20))])) return VppBFDAuthKey(test=test, auth_type=auth_type, conf_key_id=conf_key_id, key=key) class BFDAPITestCase(VppTestCase): """Bidirectional Forwarding Detection (BFD) - API""" pg0 = None pg1 = None @classmethod def setUpClass(cls): super(BFDAPITestCase, cls).setUpClass() try: cls.create_pg_interfaces(range(2)) for i in cls.pg_interfaces: i.config_ip4() i.config_ip6() i.resolve_arp() except Exception: super(BFDAPITestCase, cls).tearDownClass() raise def setUp(self): super(BFDAPITestCase, self).setUp() self.factory = AuthKeyFactory() def test_add_bfd(self): """ create a BFD session """ session = VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip4) session.add_vpp_config() self.logger.debug("Session state is %s", session.state) session.remove_vpp_config() session.add_vpp_config() self.logger.debug("Session state is %s", session.state) session.remove_vpp_config() def test_double_add(self): """ create the same BFD session twice (negative case) """ session = VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip4) session.add_vpp_config() with self.vapi.expect_negative_api_retval(): session.add_vpp_config() session.remove_vpp_config() def test_add_bfd6(self): """ create IPv6 BFD session """ session = VppBFDUDPSession( self, self.pg0, self.pg0.remote_ip6, af=AF_INET6) session.add_vpp_config() self.logger.debug("Session state is %s", session.state) session.remove_vpp_config() session.add_vpp_config() self.logger.debug("Session state is %s", session.state) session.remove_vpp_config() def test_mod_bfd(self): """ modify BFD session parameters """ session = VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip4, desired_min_tx=50000, required_min_rx=10000, detect_mult=1) session.add_vpp_config() s = session.get_bfd_udp_session_dump_entry() self.assert_equal(session.desired_min_tx, s.desired_min_tx, "desired min transmit interval") self.assert_equal(session.required_min_rx, s.required_min_rx, "required min receive interval") self.assert_equal(session.detect_mult, s.detect_mult, "detect mult") session.modify_parameters(desired_min_tx=session.desired_min_tx * 2, required_min_rx=session.required_min_rx * 2, detect_mult=session.detect_mult * 2) s = session.get_bfd_udp_session_dump_entry() self.assert_equal(session.desired_min_tx, s.desired_min_tx, "desired min transmit interval") self.assert_equal(session.required_min_rx, s.required_min_rx, "required min receive interval") self.assert_equal(session.detect_mult, s.detect_mult, "detect mult") def test_add_sha1_keys(self): """ add SHA1 keys """ key_count = 10 keys = [self.factory.create_random_key( self) for i in range(0, key_count)] for key in keys: self.assertFalse(key.query_vpp_config()) for key in keys: key.add_vpp_config() for key in keys: self.assertTrue(key.query_vpp_config()) # remove randomly indexes = range(key_count) shuffle(indexes) removed = [] for i in indexes: key = keys[i] key.remove_vpp_config() removed.append(i) for j in range(key_count): key = keys[j] if j in removed: self.assertFalse(key.query_vpp_config()) else: self.assertTrue(key.query_vpp_config()) # should be removed now for key in keys: self.assertFalse(key.query_vpp_config()) # add back and remove again for key in keys: key.add_vpp_config() for key in keys: self.assertTrue(key.query_vpp_config()) for key in keys: key.remove_vpp_config() for key in keys: self.assertFalse(key.query_vpp_config()) def test_add_bfd_sha1(self): """ create a BFD session (SHA1) """ key = self.factory.create_random_key(self) key.add_vpp_config() session = VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip4, sha1_key=key) session.add_vpp_config() self.logger.debug("Session state is %s", session.state) session.remove_vpp_config() session.add_vpp_config() self.logger.debug("Session state is %s", session.state) session.remove_vpp_config() def test_double_add_sha1(self): """ create the same BFD session twice (negative case) (SHA1) """ key = self.factory.create_random_key(self) key.add_vpp_config() session = VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip4, sha1_key=key) session.add_vpp_config() with self.assertRaises(Exception): session.add_vpp_config() def test_add_auth_nonexistent_key(self): """ create BFD session using non-existent SHA1 (negative case) """ session = VppBFDUDPSession( self, self.pg0, self.pg0.remote_ip4, sha1_key=self.factory.create_random_key(self)) with self.assertRaises(Exception): session.add_vpp_config() def test_shared_sha1_key(self): """ share single SHA1 key between multiple BFD sessions """ key = self.factory.create_random_key(self) key.add_vpp_config() sessions = [ VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip4, sha1_key=key), VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip6, sha1_key=key, af=AF_INET6), VppBFDUDPSession(self, self.pg1, self.pg1.remote_ip4, sha1_key=key), VppBFDUDPSession(self, self.pg1, self.pg1.remote_ip6, sha1_key=key, af=AF_INET6)] for s in sessions: s.add_vpp_config() removed = 0 for s in sessions: e = key.get_bfd_auth_keys_dump_entry() self.assert_equal(e.use_count, len(sessions) - removed, "Use count for shared key") s.remove_vpp_config() removed += 1 e = key.get_bfd_auth_keys_dump_entry() self.assert_equal(e.use_count, len(sessions) - removed, "Use count for shared key") def test_activate_auth(self): """ activate SHA1 authentication """ key = self.factory.create_random_key(self) key.add_vpp_config() session = VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip4) session.add_vpp_config() session.activate_auth(key) def test_deactivate_auth(self): """ deactivate SHA1 authentication """ key = self.factory.create_random_key(self) key.add_vpp_config() session = VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip4) session.add_vpp_config() session.activate_auth(key) session.deactivate_auth() def test_change_key(self): """ change SHA1 key """ key1 = self.factory.create_random_key(self) key2 = self.factory.create_random_key(self) while key2.conf_key_id == key1.conf_key_id: key2 = self.factory.create_random_key(self) key1.add_vpp_config() key2.add_vpp_config() session = VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip4, sha1_key=key1) session.add_vpp_config() session.activate_auth(key2) class BFDTestSession(object): """ BFD session as seen from test framework side """ def __init__(self, test, interface, af, detect_mult=3, sha1_key=None, bfd_key_id=None, our_seq_number=None): self.test = test self.af = af self.sha1_key = sha1_key self.bfd_key_id = bfd_key_id self.interface = interface self.udp_sport = randint(49152, 65535) if our_seq_number is None: self.our_seq_number = randint(0, 40000000) else: self.our_seq_number = our_seq_number self.vpp_seq_number = None self.my_discriminator = 0 self.desired_min_tx = 100000 self.required_min_rx = 100000 self.required_min_echo_rx = None self.detect_mult = detect_mult self.diag = BFDDiagCode.no_diagnostic self.your_discriminator = None self.state = BFDState.down self.auth_type = BFDAuthType.no_auth def inc_seq_num(self): """ increment sequence number, wrapping if needed """ if self.our_seq_number == 0xFFFFFFFF: self.our_seq_number = 0 else: self.our_seq_number += 1 def update(self, my_discriminator=None, your_discriminator=None, desired_min_tx=None, required_min_rx=None, required_min_echo_rx=None, detect_mult=None, diag=None, state=None, auth_type=None): """ update BFD parameters associated with session """ if my_discriminator is not None: self.my_discriminator = my_discriminator if your_discriminator is not None: self.your_discriminator = your_discriminator if required_min_rx is not None: self.required_min_rx = required_min_rx if required_min_echo_rx is not None: self.required_min_echo_rx = required_min_echo_rx if desired_min_tx is not None: self.desired_min_tx = desired_min_tx if detect_mult is not None: self.detect_mult = detect_mult if diag is not None: self.diag = diag if state is not None: self.state = state if auth_type is not None: self.auth_type = auth_type def fill_packet_fields(self, packet): """ set packet fields with known values in packet """ bfd = packet[BFD] if self.my_discriminator: self.test.logger.debug("BFD: setting packet.my_discriminator=%s", self.my_discriminator) bfd.my_discriminator = self.my_discriminator if self.your_discriminator: self.test.logger.debug("BFD: setting packet.your_discriminator=%s", self.your_discriminator) bfd.your_discriminator = self.your_discriminator if self.required_min_rx: self.test.logger.debug( "BFD: setting packet.required_min_rx_interval=%s", self.required_min_rx) bfd.required_min_rx_interval = self.required_min_rx if self.required_min_echo_rx: self.test.logger.debug( "BFD: setting packet.required_min_echo_rx=%s", self.required_min_echo_rx) bfd.required_min_echo_rx_interval = self.required_min_echo_rx if self.desired_min_tx: self.test.logger.debug( "BFD: setting packet.desired_min_tx_interval=%s", self.desired_min_tx) bfd.desired_min_tx_interval = self.desired_min_tx if self.detect_mult: self.test.logger.debug( "BFD: setting packet.detect_mult=%s", self.detect_mult) bfd.detect_mult = self.detect_mult if self.diag: self.test.logger.debug("BFD: setting packet.diag=%s", self.diag) bfd.diag = self.diag if self.state: self.test.logger.debug("BFD: setting packet.state=%s", self.state) bfd.state = self.state if self.auth_type: # this is used by a negative test-case self.test.logger.debug("BFD: setting packet.auth_type=%s", self.auth_type) bfd.auth_type = self.auth_type def create_packet(self): """ create a BFD packet, reflecting the current state of session """ if self.sha1_key: bfd = BFD(flags="A") bfd.auth_type = self.sha1_key.auth_type bfd.auth_len = BFD.sha1_auth_len bfd.auth_key_id = self.bfd_key_id bfd.auth_seq_num = self.our_seq_number bfd.length = BFD.sha1_auth_len + BFD.bfd_pkt_len else: bfd = BFD() if self.af == AF_INET6: packet = (Ether(src=self.interface.remote_mac, dst=self.interface.local_mac) / IPv6(src=self.interface.remote_ip6,
/*
* Copyright (c) 2017 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 <nat/dslite.h>
#include <nat/nat_inlines.h>
typedef enum
{
DSLITE_OUT2IN_NEXT_IP4_LOOKUP,
DSLITE_OUT2IN_NEXT_IP6_LOOKUP,
DSLITE_OUT2IN_NEXT_DROP,
DSLITE_OUT2IN_N_NEXT,
} dslite_out2in_next_t;
static char *dslite_out2in_error_strings[] = {
#define _(sym,string) string,
foreach_dslite_error
#undef _
};
static inline u32
dslite_icmp_out2in (dslite_main_t * dm, ip4_header_t * ip4,
dslite_session_t ** sp, u32 next, u8 * error,
u32 thread_index)
{
dslite_session_t *s = 0;
icmp46_header_t *icmp = ip4_next_header (ip4);
clib_bihash_kv_8_8_t kv, value;
snat_session_key_t key;
u32 n = next;
icmp_echo_header_t *echo;
u32 new_addr, old_addr;
u16 old_id, new_id;
ip_csum_t sum;
echo = (icmp_echo_header_t *) (icmp + 1);
if (icmp_is_error_message (icmp) || (icmp->type != ICMP4_echo_reply))
{
n = DSLITE_OUT2IN_NEXT_DROP;
*error = DSLITE_ERROR_BAD_ICMP_TYPE;
goto done;
}
key.addr = ip4->dst_address;
key.port = echo->identifier;
key.protocol = SNAT_PROTOCOL_ICMP;
key.fib_index = 0;
kv.key = key.as_u64;
if (clib_bihash_search_8_8
(&dm->per_thread_data[thread_index].out2in, &kv, &value))
{
next = DSLITE_OUT2IN_NEXT_DROP;
*error = DSLITE_ERROR_NO_TRANSLATION;
goto done;
}
else
{
s =
pool_elt_at_index (dm->per_thread_data[thread_index].sessions,
value.value);
}
old_id = echo->identifier;
echo->identifier = new_id = s->in2out.port;
sum = icmp->checksum;
sum = ip_csum_update (sum, old_id, new_id, icmp_echo_header_t, identifier);
icmp->checksum = ip_csum_fold (sum);
old_addr = ip4->dst_address.as_u32;
ip4->dst_address = s->in2out.addr;
new_addr = ip4->dst_address.as_u32;
sum = ip4->checksum;
sum = ip_csum_update (sum, old_addr, new_addr, ip4_header_t, dst_address);
ip4->checksum = ip_csum_fold (sum);
done:
*sp = s;
return n;
}
VLIB_NODE_FN (dslite_out2in_node) (vlib_main_t * vm,
vlib_node_runtime_t * node,
vlib_frame_t * frame)
{
u32 n_left_from, *from, *to_next;
dslite_out2in_next_t next_index;
vlib_node_runtime_t *error_node;
u32 thread_index = vm->thread_index;
f64 now = vlib_time_now (vm);
dslite_main_t *dm = &dslite_main;
error_node = vlib_node_get_runtime (vm, dm->dslite_out2in_node_index);
from = vlib_frame_vector_args (frame);
n_left_from = frame->n_vectors;
next_index = node->cached_next_index;
while (n_left_from > 0)
{
u32 n_left_to_next;
vlib_get_next_frame (vm, node, next_index, to_next, n_left_to_next);
while (n_left_from > 0 && n_left_to_next > 0)
{
u32 bi0;
vlib_buffer_t *b0;
u32 next0 = DSLITE_OUT2IN_NEXT_IP6_LOOKUP;
u8 error0 = DSLITE_ERROR_OUT2IN;
ip4_header_t *ip40;
ip6_header_t *ip60;
u32 proto0;
udp_header_t *udp0;
tcp_header_t *tcp0;
clib_bihash_kv_8_8_t kv0, value0;
snat_session_key_t key0;
dslite_session_t *s0 = 0;
ip_csum_t sum0;
u32 new_addr0, old_addr0;
u16 new_port0, old_port0;
/* speculatively enqueue b0 to the current next frame */
bi0 = from[0];
to_next[0] = bi0;
from += 1;
to_next += 1;
n_left_from -= 1;
n_left_to_next -= 1;
b0 = vlib_get_buffer (vm, bi0);
ip40 = vlib_buffer_get_current (b0);
proto0 = ip_proto_to_snat_proto (ip40->protocol);
if (PREDICT_FALSE (proto0 == ~0))
{
error0 = DSLITE_ERROR_UNSUPPORTED_PROTOCOL;
next0 = DSLITE_OUT2IN_NEXT_DROP;
goto trace0;
}
if (PREDICT_FALSE (proto0 == SNAT_PROTOCOL_ICMP))
{
next0 =
dslite_icmp_out2in (dm, ip40, &s0, next0, &error0,
thread_index);
if (PREDICT_FALSE (next0 == DSLITE_OUT2IN_NEXT_DROP))
goto trace0;
goto encap0;
}
udp0 = ip4_next_header (ip40);
tcp0 = (tcp_header_t *) udp0;
key0.addr = ip40->dst_address;
key0.port = udp0->dst_port;
key0.protocol = proto0;
key0.fib_index = 0;
kv0.key = key0.as_u64;
if (clib_bihash_search_8_8
(&dm->per_thread_data[thread_index].out2in, &kv0, &value0))
{
next0 = DSLITE_OUT2IN_NEXT_DROP;
error0 = DSLITE_ERROR_NO_TRANSLATION;
goto trace0;
}
else
{
s0 =
pool_elt_at_index (dm->per_thread_data[thread_index].sessions,
value0.value);
}
old_addr0 = ip40->dst_address.as_u32;
ip40->dst_address = s0->in2out.addr;
new_addr0 = ip40->dst_address.as_u32;
sum0 = ip40->checksum;
sum0 =
ip_csum_update (sum0, old_addr0, new_addr0, ip4_header_t,
dst_address);
ip40->checksum = ip_csum_fold (sum0);
if (PREDICT_TRUE (proto0 == SNAT_PROTOCOL_TCP))
{
old_port0 = tcp0->dst_port;
tcp0->dst_port = s0->in2out.port;
new_port0 = tcp0->dst_port;
sum0 = tcp0->checksum;
sum0 =
ip_csum_update (sum0, old_addr0, new_addr0, ip4_header_t,
dst_address);
sum0 =
ip_csum_update (sum0, old_port0, new_port0, ip4_header_t,
length);
tcp0->checksum = ip_csum_fold (sum0);
}
else
{
old_port0 = udp0->dst_port;
udp0->dst_port = s0->in2out.port;
udp0->checksum = 0;
}
encap0:
/* Construct IPv6 header */
vlib_buffer_advance (b0, -(sizeof (ip6_header_t)));
ip60 = vlib_buffer_get_current (b0);
ip60->ip_version_traffic_class_and_flow_label =
clib_host_to_net_u32 ((6 << 28) + (ip40->tos << 20));
ip60->payload_length = ip40->length;
ip60->protocol = IP_PROTOCOL_IP_IN_IP;
ip60->hop_limit = ip40->ttl;
ip60->src_address.as_u64[0] = dm->aftr_ip6_addr.as_u64[0];
ip60->src_address.as_u64[1] = dm->aftr_ip6_addr.as_u64[1];
ip60->dst_address.as_u64[0] = s0->in2out.softwire_id.as_u64[0];
ip60->dst_address.as_u64[1] = s0->in2out.softwire_id.as_u64[1];
/* Accounting */
s0->last_heard = now;
s0->total_pkts++;
s0->total_bytes += vlib_buffer_length_in_chain (vm, b0);
/* Per-B4 LRU list maintenance */
clib_dlist_remove (dm->per_thread_data[thread_index].list_pool,
s0->per_b4_index);
clib_dlist_addtail (dm->per_thread_data[thread_index].list_pool,
s0->per_b4_list_head_index, s0->per_b4_index);
trace0:
if (PREDICT_FALSE ((node->flags & VLIB_NODE_FLAG_TRACE)
&& (b0->flags & VLIB_BUFFER_IS_TRACED)))
{
dslite_trace_t *t = vlib_add_trace (vm, node, b0, sizeof (*t));
t->next_index = next0;
t->session_index = ~0;
if (s0)
t->session_index =
s0 - dm->per_thread_data[thread_index].sessions;
}
b0->error = error_node->errors[error0];
/* verify speculative enqueue, maybe switch current next frame */
vlib_validate_buffer_enqueue_x1 (vm, node, next_index, to_next,
n_left_to_next, bi0, next0);
}
vlib_put_next_frame (vm, node, next_index, n_left_to_next);
}
return frame->n_vectors;
}
/* *INDENT-OFF* */
VLIB_REGISTER_NODE (dslite_out2in_node) = {
.name = "dslite-out2in",
.vector_size = sizeof (u32),
.format_trace = format_dslite_trace,
.type = VLIB_NODE_TYPE_INTERNAL,
.n_errors = ARRAY_LEN (dslite_out2in_error_strings),
.error_strings = dslite_out2in_error_strings,
.n_next_nodes = DSLITE_OUT2IN_N_NEXT,
/* edit / add dispositions here */
.next_nodes = {
[DSLITE_OUT2IN_NEXT_DROP] = "error-drop",
[DSLITE_OUT2IN_NEXT_IP4_LOOKUP] = "ip4-lookup",
[DSLITE_OUT2IN_NEXT_IP6_LOOKUP] = "ip6-lookup",
},
};
/* *INDENT-ON* */
/*
* fd.io coding-style-patch-verification: ON
*
* Local Variables:
* eval: (c-set-style "gnu")
* End:
*/