#!/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, running_extended_tests from vpp_pg_interface import CaptureTimeoutError, is_ipv6_misc from vpp_lo_interface import VppLoInterface from util import ppp from vpp_papi_provider import UnexpectedApiReturnValueError from vpp_ip import DpoProto from vpp_ip_route import VppIpRoute, VppRoutePath 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) @unittest.skipUnless(running_extended_tests(), "part of extended tests") class BFDAPITestCase(VppTestCase): """Bidirectional Forwarding Detection (BFD) - API""" pg0 = None pg1 = None @classmethod def setUpClass(cls): super(BFDAPITestCase, cls).setUpClass() cls.vapi.cli("set log class bfd level debug") 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 sh
/*
* ipsec_itf.c: IPSec dedicated interface type
*
* Copyright (c) 2020 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.
*/
#ifndef __IPSEC_ITF_H__
#define __IPSEC_ITF_H__
#include <vnet/tunnel/tunnel.h>
#include <vnet/ipsec/ipsec_sa.h>
/**
* @brief A dedicated IPSec interface type
*
* In order to support route based VPNs one needs 3 elements: an interface,
* for routing to resolve routes through, an SA from the peer to describe
* security, and encap, to describe how to reach the peer. There are two
* ways one could model this:
*
* interface + encap + SA = (interface + encap) + SA =
* ipip-interface + SA transport mode
*
* or
*
* interface + encap + SA = interface + (encap + SA) =
* IPSec-interface + SA tunnel mode
*
* It's a question of where you add the parenthesis, from the perspective
* of the external user the effect is identical.
*
* The IPsec interface serves as the encap-free interface to be used
* in conjunction with an encap-describing tunnel mode SA.
*
* VPP supports both models, which modelshould you pick?
* A route based VPN could impose 0, 1 or 2 encaps. the support matrix for
* these use cases is:
*
* | 0 | 1 | 2 |
* --------------------------
* ipip | N | Y | Y |
* ipsec | P | Y | P |
*
* Where P = potentially.
* ipsec could potnetially support 0 encap (i.e. transport mode) since neither
* the interface nor the SA *requires* encap. However, for a route beased VPN
* to use transport mode is probably wrong since one shouldn't use thransport
* mode for transit traffic, since without encap it is not guaranteed to return.
* ipsec could potnetially support 2 encaps, but that would require the SA to
* describe both, something it does not do at this time.
*
* ipsec currently does not support:
* - multipoint interfaces
* but this is only because it is not yet implemented, rather than it cannot
* be done.
*
* Internally the difference is that the midchain adjacency for the IPSec
* interface has no associated encap (whereas for an ipip tunnel it describes
* the peer). Consequently, features on the output arc see packets without
* any encap. Since the protecting SAs are in tunnel mode,
* they apply the encap. The midchain adj is stacked only once the proctecting
* SA is known, since only then is the peer known. Otherwise the VLIB graph
* nodes used are the same:
* (routing) --> ipX-michain --> espX-encrypt --> adj-midchain-tx --> (routing)
* where X = 4 or 6.
*
* Some benefits to the ipsec interface:
* - it is slightly more efficient since the encapsulating IP header has
* its checksum updated only once.
* - even when the interface is admin up traffic cannot be sent to a peer
* unless the SA is available (since it's the SA that determines the
* encap). With ipip interfaces a client must use the admin state to
* prevent sending until the SA is available.
*
* The best recommendations i can make are:
* - pick a model that supports your use case
* - make sure any other features you wish to use are supported by the model
* - choose the model that best fits your control plane's model.
*
*
* gun reloaded, fire away.
*/
typedef struct ipsec_itf_t_
{
tunnel_mode_t ii_mode;
int ii_user_instance;
u32 ii_sw_if_index;
} __clib_packed ipsec_itf_t;
extern int ipsec_itf_create (u32 user_instance,
tunnel_mode_t mode, u32 * sw_if_indexp);
extern int ipsec_itf_delete (u32 sw_if_index);
extern void ipsec_itf_adj_stack (adj_index_t ai, u32 sai);
extern void ipsec_itf_adj_unstack (adj_index_t ai);
extern u8 *format_ipsec_itf (u8 * s, va_list * a);
extern ipsec_itf_t *ipsec_itf_get (index_t ii);
typedef walk_rc_t (*ipsec_itf_walk_cb_t) (ipsec_itf_t *itf, void *ctx);
extern void ipsec_itf_walk (ipsec_itf_walk_cb_t cd, void *ctx);
/*
* fd.io coding-style-patch-verification: ON
*
* Local Variables:
* eval: (c-set-style "gnu")
* End:
*/
#endif