4 from __future__ import division
7 from collections import namedtuple
13 from random import randint, shuffle, getrandbits
14 from socket import AF_INET, AF_INET6, inet_ntop
15 from struct import pack, unpack
18 from scapy.layers.inet import UDP, IP
19 from scapy.layers.inet6 import IPv6
20 from scapy.layers.l2 import Ether, GRE
21 from scapy.packet import Raw
23 from bfd import VppBFDAuthKey, BFD, BFDAuthType, VppBFDUDPSession, \
24 BFDDiagCode, BFDState, BFD_vpp_echo
25 from framework import tag_fixme_vpp_workers
26 from framework import VppTestCase, VppTestRunner, running_extended_tests
27 from framework import tag_run_solo
29 from vpp_ip import DpoProto
30 from vpp_ip_route import VppIpRoute, VppRoutePath
31 from vpp_lo_interface import VppLoInterface
32 from vpp_papi_provider import UnexpectedApiReturnValueError, \
34 from vpp_pg_interface import CaptureTimeoutError, is_ipv6_misc
35 from vpp_gre_interface import VppGreInterface
36 from vpp_papi import VppEnum
41 class AuthKeyFactory(object):
42 """Factory class for creating auth keys with unique conf key ID"""
45 self._conf_key_ids = {}
47 def create_random_key(self, test, auth_type=BFDAuthType.keyed_sha1):
48 """ create a random key with unique conf key id """
49 conf_key_id = randint(0, 0xFFFFFFFF)
50 while conf_key_id in self._conf_key_ids:
51 conf_key_id = randint(0, 0xFFFFFFFF)
52 self._conf_key_ids[conf_key_id] = 1
53 key = scapy.compat.raw(
54 bytearray([randint(0, 255) for _ in range(randint(1, 20))]))
55 return VppBFDAuthKey(test=test, auth_type=auth_type,
56 conf_key_id=conf_key_id, key=key)
59 class BFDAPITestCase(VppTestCase):
60 """Bidirectional Forwarding Detection (BFD) - API"""
67 super(BFDAPITestCase, cls).setUpClass()
68 cls.vapi.cli("set log class bfd level debug")
70 cls.create_pg_interfaces(range(2))
71 for i in cls.pg_interfaces:
77 super(BFDAPITestCase, cls).tearDownClass()
81 def tearDownClass(cls):
82 super(BFDAPITestCase, cls).tearDownClass()
85 super(BFDAPITestCase, self).setUp()
86 self.factory = AuthKeyFactory()
88 def test_add_bfd(self):
89 """ create a BFD session """
90 session = VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip4)
91 session.add_vpp_config()
92 self.logger.debug("Session state is %s", session.state)
93 session.remove_vpp_config()
94 session.add_vpp_config()
95 self.logger.debug("Session state is %s", session.state)
96 session.remove_vpp_config()
98 def test_double_add(self):
99 """ create the same BFD session twice (negative case) """
100 session = VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip4)
101 session.add_vpp_config()
103 with self.vapi.assert_negative_api_retval():
104 session.add_vpp_config()
106 session.remove_vpp_config()
108 def test_add_bfd6(self):
109 """ create IPv6 BFD session """
110 session = VppBFDUDPSession(
111 self, self.pg0, self.pg0.remote_ip6, af=AF_INET6)
112 session.add_vpp_config()
113 self.logger.debug("Session state is %s", session.state)
114 session.remove_vpp_config()
115 session.add_vpp_config()
116 self.logger.debug("Session state is %s", session.state)
117 session.remove_vpp_config()
119 def test_mod_bfd(self):
120 """ modify BFD session parameters """
121 session = VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip4,
122 desired_min_tx=50000,
123 required_min_rx=10000,
125 session.add_vpp_config()
126 s = session.get_bfd_udp_session_dump_entry()
127 self.assert_equal(session.desired_min_tx,
129 "desired min transmit interval")
130 self.assert_equal(session.required_min_rx,
132 "required min receive interval")
133 self.assert_equal(session.detect_mult, s.detect_mult, "detect mult")
134 session.modify_parameters(desired_min_tx=session.desired_min_tx * 2,
135 required_min_rx=session.required_min_rx * 2,
136 detect_mult=session.detect_mult * 2)
137 s = session.get_bfd_udp_session_dump_entry()
138 self.assert_equal(session.desired_min_tx,
140 "desired min transmit interval")
141 self.assert_equal(session.required_min_rx,
143 "required min receive interval")
144 self.assert_equal(session.detect_mult, s.detect_mult, "detect mult")
146 def test_upd_bfd(self):
147 """ Create/Modify w/ Update BFD session parameters """
148 session = VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip4,
149 desired_min_tx=50000,
150 required_min_rx=10000,
152 session.upd_vpp_config()
153 s = session.get_bfd_udp_session_dump_entry()
154 self.assert_equal(session.desired_min_tx,
156 "desired min transmit interval")
157 self.assert_equal(session.required_min_rx,
159 "required min receive interval")
161 self.assert_equal(session.detect_mult, s.detect_mult, "detect mult")
162 session.upd_vpp_config(desired_min_tx=session.desired_min_tx * 2,
163 required_min_rx=session.required_min_rx * 2,
164 detect_mult=session.detect_mult * 2)
165 s = session.get_bfd_udp_session_dump_entry()
166 self.assert_equal(session.desired_min_tx,
168 "desired min transmit interval")
169 self.assert_equal(session.required_min_rx,
171 "required min receive interval")
172 self.assert_equal(session.detect_mult, s.detect_mult, "detect mult")
174 def test_add_sha1_keys(self):
175 """ add SHA1 keys """
177 keys = [self.factory.create_random_key(
178 self) for i in range(0, key_count)]
180 self.assertFalse(key.query_vpp_config())
184 self.assertTrue(key.query_vpp_config())
186 indexes = list(range(key_count))
191 key.remove_vpp_config()
193 for j in range(key_count):
196 self.assertFalse(key.query_vpp_config())
198 self.assertTrue(key.query_vpp_config())
199 # should be removed now
201 self.assertFalse(key.query_vpp_config())
202 # add back and remove again
206 self.assertTrue(key.query_vpp_config())
208 key.remove_vpp_config()
210 self.assertFalse(key.query_vpp_config())
212 def test_add_bfd_sha1(self):
213 """ create a BFD session (SHA1) """
214 key = self.factory.create_random_key(self)
216 session = VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip4,
218 session.add_vpp_config()
219 self.logger.debug("Session state is %s", session.state)
220 session.remove_vpp_config()
221 session.add_vpp_config()
222 self.logger.debug("Session state is %s", session.state)
223 session.remove_vpp_config()
225 def test_double_add_sha1(self):
226 """ create the same BFD session twice (negative case) (SHA1) """
227 key = self.factory.create_random_key(self)
229 session = VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip4,
231 session.add_vpp_config()
232 with self.assertRaises(Exception):
233 session.add_vpp_config()
235 def test_add_auth_nonexistent_key(self):
236 """ create BFD session using non-existent SHA1 (negative case) """
237 session = VppBFDUDPSession(
238 self, self.pg0, self.pg0.remote_ip4,
239 sha1_key=self.factory.create_random_key(self))
240 with self.assertRaises(Exception):
241 session.add_vpp_config()
243 def test_shared_sha1_key(self):
244 """ single SHA1 key shared by multiple BFD sessions """
245 key = self.factory.create_random_key(self)
248 VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip4,
250 VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip6,
251 sha1_key=key, af=AF_INET6),
252 VppBFDUDPSession(self, self.pg1, self.pg1.remote_ip4,
254 VppBFDUDPSession(self, self.pg1, self.pg1.remote_ip6,
255 sha1_key=key, af=AF_INET6)]
260 e = key.get_bfd_auth_keys_dump_entry()
261 self.assert_equal(e.use_count, len(sessions) - removed,
262 "Use count for shared key")
263 s.remove_vpp_config()
265 e = key.get_bfd_auth_keys_dump_entry()
266 self.assert_equal(e.use_count, len(sessions) - removed,
267 "Use count for shared key")
269 def test_activate_auth(self):
270 """ activate SHA1 authentication """
271 key = self.factory.create_random_key(self)
273 session = VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip4)
274 session.add_vpp_config()
275 session.activate_auth(key)
277 def test_deactivate_auth(self):
278 """ deactivate SHA1 authentication """
279 key = self.factory.create_random_key(self)
281 session = VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip4)
282 session.add_vpp_config()
283 session.activate_auth(key)
284 session.deactivate_auth()
286 def test_change_key(self):
287 """ change SHA1 key """
288 key1 = self.factory.create_random_key(self)
289 key2 = self.factory.create_random_key(self)
290 while key2.conf_key_id == key1.conf_key_id:
291 key2 = self.factory.create_random_key(self)
292 key1.add_vpp_config()
293 key2.add_vpp_config()
294 session = VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip4,
296 session.add_vpp_config()
297 session.activate_auth(key2)
299 def test_set_del_udp_echo_source(self):
300 """ set/del udp echo source """
301 self.create_loopback_interfaces(1)
302 self.loopback0 = self.lo_interfaces[0]
303 self.loopback0.admin_up()
304 echo_source = self.vapi.bfd_udp_get_echo_source()
305 self.assertFalse(echo_source.is_set)
306 self.assertFalse(echo_source.have_usable_ip4)
307 self.assertFalse(echo_source.have_usable_ip6)
309 self.vapi.bfd_udp_set_echo_source(
310 sw_if_index=self.loopback0.sw_if_index)
311 echo_source = self.vapi.bfd_udp_get_echo_source()
312 self.assertTrue(echo_source.is_set)
313 self.assertEqual(echo_source.sw_if_index, self.loopback0.sw_if_index)
314 self.assertFalse(echo_source.have_usable_ip4)
315 self.assertFalse(echo_source.have_usable_ip6)
317 self.loopback0.config_ip4()
318 echo_ip4 = ipaddress.IPv4Address(int(ipaddress.IPv4Address(
319 self.loopback0.local_ip4)) ^ 1).packed
320 echo_source = self.vapi.bfd_udp_get_echo_source()
321 self.assertTrue(echo_source.is_set)
322 self.assertEqual(echo_source.sw_if_index, self.loopback0.sw_if_index)
323 self.assertTrue(echo_source.have_usable_ip4)
324 self.assertEqual(echo_source.ip4_addr.packed, echo_ip4)
325 self.assertFalse(echo_source.have_usable_ip6)
327 self.loopback0.config_ip6()
328 echo_ip6 = ipaddress.IPv6Address(int(ipaddress.IPv6Address(
329 self.loopback0.local_ip6)) ^ 1).packed
331 echo_source = self.vapi.bfd_udp_get_echo_source()
332 self.assertTrue(echo_source.is_set)
333 self.assertEqual(echo_source.sw_if_index, self.loopback0.sw_if_index)
334 self.assertTrue(echo_source.have_usable_ip4)
335 self.assertEqual(echo_source.ip4_addr.packed, echo_ip4)
336 self.assertTrue(echo_source.have_usable_ip6)
337 self.assertEqual(echo_source.ip6_addr.packed, echo_ip6)
339 self.vapi.bfd_udp_del_echo_source()
340 echo_source = self.vapi.bfd_udp_get_echo_source()
341 self.assertFalse(echo_source.is_set)
342 self.assertFalse(echo_source.have_usable_ip4)
343 self.assertFalse(echo_source.have_usable_ip6)
346 class BFDTestSession(object):
347 """ BFD session as seen from test framework side """
349 def __init__(self, test, interface, af, detect_mult=3, sha1_key=None,
350 bfd_key_id=None, our_seq_number=None,
351 tunnel_header=None, phy_interface=None):
354 self.sha1_key = sha1_key
355 self.bfd_key_id = bfd_key_id
356 self.interface = interface
358 self.phy_interface = phy_interface
360 self.phy_interface = self.interface
361 self.udp_sport = randint(49152, 65535)
362 if our_seq_number is None:
363 self.our_seq_number = randint(0, 40000000)
365 self.our_seq_number = our_seq_number
366 self.vpp_seq_number = None
367 self.my_discriminator = 0
368 self.desired_min_tx = 300000
369 self.required_min_rx = 300000
370 self.required_min_echo_rx = None
371 self.detect_mult = detect_mult
372 self.diag = BFDDiagCode.no_diagnostic
373 self.your_discriminator = None
374 self.state = BFDState.down
375 self.auth_type = BFDAuthType.no_auth
376 self.tunnel_header = tunnel_header
379 self.tx_packets_echo = 0
380 self.rx_packets_echo = 0
382 def inc_seq_num(self):
383 """ increment sequence number, wrapping if needed """
384 if self.our_seq_number == 0xFFFFFFFF:
385 self.our_seq_number = 0
387 self.our_seq_number += 1
389 def update(self, my_discriminator=None, your_discriminator=None,
390 desired_min_tx=None, required_min_rx=None,
391 required_min_echo_rx=None, detect_mult=None,
392 diag=None, state=None, auth_type=None):
393 """ update BFD parameters associated with session """
394 if my_discriminator is not None:
395 self.my_discriminator = my_discriminator
396 if your_discriminator is not None:
397 self.your_discriminator = your_discriminator
398 if required_min_rx is not None:
399 self.required_min_rx = required_min_rx
400 if required_min_echo_rx is not None:
401 self.required_min_echo_rx = required_min_echo_rx
402 if desired_min_tx is not None:
403 self.desired_min_tx = desired_min_tx
404 if detect_mult is not None:
405 self.detect_mult = detect_mult
408 if state is not None:
410 if auth_type is not None:
411 self.auth_type = auth_type
413 def fill_packet_fields(self, packet):
414 """ set packet fields with known values in packet """
416 if self.my_discriminator:
417 self.test.logger.debug("BFD: setting packet.my_discriminator=%s",
418 self.my_discriminator)
419 bfd.my_discriminator = self.my_discriminator
420 if self.your_discriminator:
421 self.test.logger.debug("BFD: setting packet.your_discriminator=%s",
422 self.your_discriminator)
423 bfd.your_discriminator = self.your_discriminator
424 if self.required_min_rx:
425 self.test.logger.debug(
426 "BFD: setting packet.required_min_rx_interval=%s",
427 self.required_min_rx)
428 bfd.required_min_rx_interval = self.required_min_rx
429 if self.required_min_echo_rx:
430 self.test.logger.debug(
431 "BFD: setting packet.required_min_echo_rx=%s",
432 self.required_min_echo_rx)
433 bfd.required_min_echo_rx_interval = self.required_min_echo_rx
434 if self.desired_min_tx:
435 self.test.logger.debug(
436 "BFD: setting packet.desired_min_tx_interval=%s",
438 bfd.desired_min_tx_interval = self.desired_min_tx
440 self.test.logger.debug(
441 "BFD: setting packet.detect_mult=%s", self.detect_mult)
442 bfd.detect_mult = self.detect_mult
444 self.test.logger.debug("BFD: setting packet.diag=%s", self.diag)
447 self.test.logger.debug("BFD: setting packet.state=%s", self.state)
448 bfd.state = self.state
450 # this is used by a negative test-case
451 self.test.logger.debug("BFD: setting packet.auth_type=%s",
453 bfd.auth_type = self.auth_type
455 def create_packet(self):
456 """ create a BFD packet, reflecting the current state of session """
459 bfd.auth_type = self.sha1_key.auth_type
460 bfd.auth_len = BFD.sha1_auth_len
461 bfd.auth_key_id = self.bfd_key_id
462 bfd.auth_seq_num = self.our_seq_number
463 bfd.length = BFD.sha1_auth_len + BFD.bfd_pkt_len
466 packet = Ether(src=self.phy_interface.remote_mac,
467 dst=self.phy_interface.local_mac)
468 if self.tunnel_header:
469 packet = packet / self.tunnel_header
470 if self.af == AF_INET6:
472 IPv6(src=self.interface.remote_ip6,
473 dst=self.interface.local_ip6,
475 UDP(sport=self.udp_sport, dport=BFD.udp_dport) /
479 IP(src=self.interface.remote_ip4,
480 dst=self.interface.local_ip4,
482 UDP(sport=self.udp_sport, dport=BFD.udp_dport) /
484 self.test.logger.debug("BFD: Creating packet")
485 self.fill_packet_fields(packet)
487 hash_material = scapy.compat.raw(
488 packet[BFD])[:32] + self.sha1_key.key + \
489 b"\0" * (20 - len(self.sha1_key.key))
490 self.test.logger.debug("BFD: Calculated SHA1 hash: %s" %
491 hashlib.sha1(hash_material).hexdigest())
492 packet[BFD].auth_key_hash = hashlib.sha1(hash_material).digest()
495 def send_packet(self, packet=None, interface=None):
496 """ send packet on interface, creating the packet if needed """
498 packet = self.create_packet()
499 if interface is None:
500 interface = self.phy_interface
501 self.test.logger.debug(ppp("Sending packet:", packet))
502 interface.add_stream(packet)
506 def verify_sha1_auth(self, packet):
507 """ Verify correctness of authentication in BFD layer. """
509 self.test.assert_equal(bfd.auth_len, 28, "Auth section length")
510 self.test.assert_equal(bfd.auth_type, self.sha1_key.auth_type,
512 self.test.assert_equal(bfd.auth_key_id, self.bfd_key_id, "Key ID")
513 self.test.assert_equal(bfd.auth_reserved, 0, "Reserved")
514 if self.vpp_seq_number is None:
515 self.vpp_seq_number = bfd.auth_seq_num
516 self.test.logger.debug("Received initial sequence number: %s" %
519 recvd_seq_num = bfd.auth_seq_num
520 self.test.logger.debug("Received followup sequence number: %s" %
522 if self.vpp_seq_number < 0xffffffff:
523 if self.sha1_key.auth_type == \
524 BFDAuthType.meticulous_keyed_sha1:
525 self.test.assert_equal(recvd_seq_num,
526 self.vpp_seq_number + 1,
527 "BFD sequence number")
529 self.test.assert_in_range(recvd_seq_num,
531 self.vpp_seq_number + 1,
532 "BFD sequence number")
534 if self.sha1_key.auth_type == \
535 BFDAuthType.meticulous_keyed_sha1:
536 self.test.assert_equal(recvd_seq_num, 0,
537 "BFD sequence number")
539 self.test.assertIn(recvd_seq_num, (self.vpp_seq_number, 0),
540 "BFD sequence number not one of "
541 "(%s, 0)" % self.vpp_seq_number)
542 self.vpp_seq_number = recvd_seq_num
543 # last 20 bytes represent the hash - so replace them with the key,
544 # pad the result with zeros and hash the result
545 hash_material = bfd.original[:-20] + self.sha1_key.key + \
546 b"\0" * (20 - len(self.sha1_key.key))
547 expected_hash = hashlib.sha1(hash_material).hexdigest()
548 self.test.assert_equal(binascii.hexlify(bfd.auth_key_hash),
549 expected_hash.encode(), "Auth key hash")
551 def verify_bfd(self, packet):
552 """ Verify correctness of BFD layer. """
554 self.test.assert_equal(bfd.version, 1, "BFD version")
555 self.test.assert_equal(bfd.your_discriminator,
556 self.my_discriminator,
557 "BFD - your discriminator")
559 self.verify_sha1_auth(packet)
562 def bfd_session_up(test):
563 """ Bring BFD session up """
564 test.logger.info("BFD: Waiting for slow hello")
565 p = wait_for_bfd_packet(test, 2, is_tunnel=test.vpp_session.is_tunnel)
567 if hasattr(test, 'vpp_clock_offset'):
568 old_offset = test.vpp_clock_offset
569 test.vpp_clock_offset = time.time() - float(p.time)
570 test.logger.debug("BFD: Calculated vpp clock offset: %s",
571 test.vpp_clock_offset)
573 test.assertAlmostEqual(
574 old_offset, test.vpp_clock_offset, delta=0.5,
575 msg="vpp clock offset not stable (new: %s, old: %s)" %
576 (test.vpp_clock_offset, old_offset))
577 test.logger.info("BFD: Sending Init")
578 test.test_session.update(my_discriminator=randint(0, 40000000),
579 your_discriminator=p[BFD].my_discriminator,
581 if test.test_session.sha1_key and test.test_session.sha1_key.auth_type == \
582 BFDAuthType.meticulous_keyed_sha1:
583 test.test_session.inc_seq_num()
584 test.test_session.send_packet()
585 test.logger.info("BFD: Waiting for event")
586 e = test.vapi.wait_for_event(1, "bfd_udp_session_event")
587 verify_event(test, e, expected_state=BFDState.up)
588 test.logger.info("BFD: Session is Up")
589 test.test_session.update(state=BFDState.up)
590 if test.test_session.sha1_key and test.test_session.sha1_key.auth_type == \
591 BFDAuthType.meticulous_keyed_sha1:
592 test.test_session.inc_seq_num()
593 test.test_session.send_packet()
594 test.assert_equal(test.vpp_session.state, BFDState.up, BFDState)
597 def bfd_session_down(test):
598 """ Bring BFD session down """
599 test.assert_equal(test.vpp_session.state, BFDState.up, BFDState)
600 test.test_session.update(state=BFDState.down)
601 if test.test_session.sha1_key and test.test_session.sha1_key.auth_type == \
602 BFDAuthType.meticulous_keyed_sha1:
603 test.test_session.inc_seq_num()
604 test.test_session.send_packet()
605 test.logger.info("BFD: Waiting for event")
606 e = test.vapi.wait_for_event(1, "bfd_udp_session_event")
607 verify_event(test, e, expected_state=BFDState.down)
608 test.logger.info("BFD: Session is Down")
609 test.assert_equal(test.vpp_session.state, BFDState.down, BFDState)
612 def verify_bfd_session_config(test, session, state=None):
613 dump = session.get_bfd_udp_session_dump_entry()
614 test.assertIsNotNone(dump)
615 # since dump is not none, we have verified that sw_if_index and addresses
616 # are valid (in get_bfd_udp_session_dump_entry)
618 test.assert_equal(dump.state, state, "session state")
619 test.assert_equal(dump.required_min_rx, session.required_min_rx,
620 "required min rx interval")
621 test.assert_equal(dump.desired_min_tx, session.desired_min_tx,
622 "desired min tx interval")
623 test.assert_equal(dump.detect_mult, session.detect_mult,
625 if session.sha1_key is None:
626 test.assert_equal(dump.is_authenticated, 0, "is_authenticated flag")
628 test.assert_equal(dump.is_authenticated, 1, "is_authenticated flag")
629 test.assert_equal(dump.bfd_key_id, session.bfd_key_id,
631 test.assert_equal(dump.conf_key_id,
632 session.sha1_key.conf_key_id,
636 def verify_ip(test, packet):
637 """ Verify correctness of IP layer. """
638 if test.vpp_session.af == AF_INET6:
640 local_ip = test.vpp_session.interface.local_ip6
641 remote_ip = test.vpp_session.interface.remote_ip6
642 test.assert_equal(ip.hlim, 255, "IPv6 hop limit")
645 local_ip = test.vpp_session.interface.local_ip4
646 remote_ip = test.vpp_session.interface.remote_ip4
647 test.assert_equal(ip.ttl, 255, "IPv4 TTL")
648 test.assert_equal(ip.src, local_ip, "IP source address")
649 test.assert_equal(ip.dst, remote_ip, "IP destination address")
652 def verify_udp(test, packet):
653 """ Verify correctness of UDP layer. """
655 test.assert_equal(udp.dport, BFD.udp_dport, "UDP destination port")
656 test.assert_in_range(udp.sport, BFD.udp_sport_min, BFD.udp_sport_max,
660 def verify_event(test, event, expected_state):
661 """ Verify correctness of event values. """
663 test.logger.debug("BFD: Event: %s" % reprlib.repr(e))
664 test.assert_equal(e.sw_if_index,
665 test.vpp_session.interface.sw_if_index,
666 "BFD interface index")
668 test.assert_equal(str(e.local_addr), test.vpp_session.local_addr,
669 "Local IPv6 address")
670 test.assert_equal(str(e.peer_addr), test.vpp_session.peer_addr,
672 test.assert_equal(e.state, expected_state, BFDState)
675 def wait_for_bfd_packet(test, timeout=1, pcap_time_min=None, is_tunnel=False):
676 """ wait for BFD packet and verify its correctness
678 :param timeout: how long to wait
679 :param pcap_time_min: ignore packets with pcap timestamp lower than this
681 :returns: tuple (packet, time spent waiting for packet)
683 test.logger.info("BFD: Waiting for BFD packet")
684 deadline = time.time() + timeout
689 test.assert_in_range(counter, 0, 100, "number of packets ignored")
690 time_left = deadline - time.time()
692 raise CaptureTimeoutError("Packet did not arrive within timeout")
693 p = test.pg0.wait_for_packet(timeout=time_left)
694 test.test_session.rx_packets += 1
695 test.logger.debug(ppp("BFD: Got packet:", p))
696 if pcap_time_min is not None and p.time < pcap_time_min:
697 test.logger.debug(ppp("BFD: ignoring packet (pcap time %s < "
698 "pcap time min %s):" %
699 (p.time, pcap_time_min), p))
703 # strip an IP layer and move to the next
708 raise Exception(ppp("Unexpected or invalid BFD packet:", p))
710 raise Exception(ppp("Unexpected payload in BFD packet:", bfd))
713 test.test_session.verify_bfd(p)
717 BFDStats = namedtuple("BFDStats", "rx rx_echo tx tx_echo")
720 def bfd_grab_stats_snapshot(test, bs_idx=0, thread_index=None):
724 rx = s['/bfd/rx-session-counters'][:, bs_idx].sum_packets()
725 rx_echo = s['/bfd/rx-session-echo-counters'][:, bs_idx].sum_packets()
726 tx = s['/bfd/tx-session-counters'][:, bs_idx].sum_packets()
727 tx_echo = s['/bfd/tx-session-echo-counters'][:, bs_idx].sum_packets()
729 rx = s['/bfd/rx-session-counters'][ti, bs_idx].sum_packets()
730 rx_echo = s['/bfd/rx-session-echo-counters'][ti, bs_idx].sum_packets()
731 tx = s['/bfd/tx-session-counters'][ti, bs_idx].sum_packets()
732 tx_echo = s['/bfd/tx-session-echo-counters'][ti, bs_idx].sum_packets()
733 return BFDStats(rx, rx_echo, tx, tx_echo)
736 def bfd_stats_diff(stats_before, stats_after):
737 rx = stats_after.rx - stats_before.rx
738 rx_echo = stats_after.rx_echo - stats_before.rx_echo
739 tx = stats_after.tx - stats_before.tx
740 tx_echo = stats_after.tx_echo - stats_before.tx_echo
741 return BFDStats(rx, rx_echo, tx, tx_echo)
745 class BFD4TestCase(VppTestCase):
746 """Bidirectional Forwarding Detection (BFD)"""
749 vpp_clock_offset = None
755 super(BFD4TestCase, cls).setUpClass()
756 cls.vapi.cli("set log class bfd level debug")
758 cls.create_pg_interfaces([0])
759 cls.create_loopback_interfaces(1)
760 cls.loopback0 = cls.lo_interfaces[0]
761 cls.loopback0.config_ip4()
762 cls.loopback0.admin_up()
764 cls.pg0.configure_ipv4_neighbors()
766 cls.pg0.resolve_arp()
769 super(BFD4TestCase, cls).tearDownClass()
773 def tearDownClass(cls):
774 super(BFD4TestCase, cls).tearDownClass()
777 super(BFD4TestCase, self).setUp()
778 self.factory = AuthKeyFactory()
779 self.vapi.want_bfd_events()
780 self.pg0.enable_capture()
782 self.bfd_udp4_sessions = self.statistics['/bfd/udp4/sessions']
783 self.bfd_udp6_sessions = self.statistics['/bfd/udp6/sessions']
784 self.vpp_session = VppBFDUDPSession(self, self.pg0,
786 self.vpp_session.add_vpp_config()
787 self.vpp_session.admin_up()
788 self.test_session = BFDTestSession(self, self.pg0, AF_INET)
789 except BaseException:
790 self.vapi.want_bfd_events(enable_disable=0)
794 if not self.vpp_dead:
795 self.vapi.want_bfd_events(enable_disable=0)
796 self.vapi.collect_events() # clear the event queue
797 super(BFD4TestCase, self).tearDown()
799 def test_session_up(self):
800 """ bring BFD session up """
802 bfd_udp4_sessions = self.statistics['/bfd/udp4/sessions']
803 bfd_udp6_sessions = self.statistics['/bfd/udp6/sessions']
804 self.assert_equal(bfd_udp4_sessions - self.bfd_udp4_sessions, 1)
805 self.assert_equal(bfd_udp6_sessions, self.bfd_udp6_sessions)
807 def test_session_up_by_ip(self):
808 """ bring BFD session up - first frame looked up by address pair """
809 self.logger.info("BFD: Sending Slow control frame")
810 self.test_session.update(my_discriminator=randint(0, 40000000))
811 self.test_session.send_packet()
812 self.pg0.enable_capture()
813 p = self.pg0.wait_for_packet(1)
814 self.assert_equal(p[BFD].your_discriminator,
815 self.test_session.my_discriminator,
816 "BFD - your discriminator")
817 self.assert_equal(p[BFD].state, BFDState.init, BFDState)
818 self.test_session.update(your_discriminator=p[BFD].my_discriminator,
820 self.logger.info("BFD: Waiting for event")
821 e = self.vapi.wait_for_event(1, "bfd_udp_session_event")
822 verify_event(self, e, expected_state=BFDState.init)
823 self.logger.info("BFD: Sending Up")
824 self.test_session.send_packet()
825 self.logger.info("BFD: Waiting for event")
826 e = self.vapi.wait_for_event(1, "bfd_udp_session_event")
827 verify_event(self, e, expected_state=BFDState.up)
828 self.logger.info("BFD: Session is Up")
829 self.test_session.update(state=BFDState.up)
830 self.test_session.send_packet()
831 self.assert_equal(self.vpp_session.state, BFDState.up, BFDState)
833 def test_session_down(self):
834 """ bring BFD session down """
836 bfd_session_down(self)
838 def test_hold_up(self):
839 """ hold BFD session up """
841 for dummy in range(self.test_session.detect_mult * 2):
842 wait_for_bfd_packet(self)
843 self.test_session.send_packet()
844 self.assert_equal(len(self.vapi.collect_events()), 0,
845 "number of bfd events")
847 def test_slow_timer(self):
848 """ verify slow periodic control frames while session down """
850 self.logger.info("BFD: Waiting for %d BFD packets", packet_count)
851 prev_packet = wait_for_bfd_packet(self, 2)
852 for dummy in range(packet_count):
853 next_packet = wait_for_bfd_packet(self, 2)
854 time_diff = next_packet.time - prev_packet.time
855 # spec says the range should be <0.75, 1>, allow extra 0.05 margin
856 # to work around timing issues
857 self.assert_in_range(
858 time_diff, 0.70, 1.05, "time between slow packets")
859 prev_packet = next_packet
861 def test_zero_remote_min_rx(self):
862 """ no packets when zero remote required min rx interval """
864 self.test_session.update(required_min_rx=0)
865 self.test_session.send_packet()
866 for dummy in range(self.test_session.detect_mult):
867 self.sleep(self.vpp_session.required_min_rx / USEC_IN_SEC,
868 "sleep before transmitting bfd packet")
869 self.test_session.send_packet()
871 p = wait_for_bfd_packet(self, timeout=0)
872 self.logger.error(ppp("Received unexpected packet:", p))
873 except CaptureTimeoutError:
876 len(self.vapi.collect_events()), 0, "number of bfd events")
877 self.test_session.update(required_min_rx=300000)
878 for dummy in range(3):
879 self.test_session.send_packet()
881 self, timeout=self.test_session.required_min_rx / USEC_IN_SEC)
883 len(self.vapi.collect_events()), 0, "number of bfd events")
885 def test_conn_down(self):
886 """ verify session goes down after inactivity """
888 detection_time = self.test_session.detect_mult *\
889 self.vpp_session.required_min_rx / USEC_IN_SEC
890 self.sleep(detection_time, "waiting for BFD session time-out")
891 e = self.vapi.wait_for_event(1, "bfd_udp_session_event")
892 verify_event(self, e, expected_state=BFDState.down)
894 def test_peer_discr_reset_sess_down(self):
895 """ peer discriminator reset after session goes down """
897 detection_time = self.test_session.detect_mult *\
898 self.vpp_session.required_min_rx / USEC_IN_SEC
899 self.sleep(detection_time, "waiting for BFD session time-out")
900 self.test_session.my_discriminator = 0
901 wait_for_bfd_packet(self,
902 pcap_time_min=time.time() - self.vpp_clock_offset)
904 def test_large_required_min_rx(self):
905 """ large remote required min rx interval """
907 p = wait_for_bfd_packet(self)
909 self.test_session.update(required_min_rx=interval)
910 self.test_session.send_packet()
911 time_mark = time.time()
913 # busy wait here, trying to collect a packet or event, vpp is not
914 # allowed to send packets and the session will timeout first - so the
915 # Up->Down event must arrive before any packets do
916 while time.time() < time_mark + interval / USEC_IN_SEC:
918 p = wait_for_bfd_packet(self, timeout=0)
919 # if vpp managed to send a packet before we did the session
920 # session update, then that's fine, ignore it
921 if p.time < time_mark - self.vpp_clock_offset:
923 self.logger.error(ppp("Received unexpected packet:", p))
925 except CaptureTimeoutError:
927 events = self.vapi.collect_events()
929 verify_event(self, events[0], BFDState.down)
931 self.assert_equal(count, 0, "number of packets received")
933 def test_immediate_remote_min_rx_reduction(self):
934 """ immediately honor remote required min rx reduction """
935 self.vpp_session.remove_vpp_config()
936 self.vpp_session = VppBFDUDPSession(
937 self, self.pg0, self.pg0.remote_ip4, desired_min_tx=10000)
938 self.pg0.enable_capture()
939 self.vpp_session.add_vpp_config()
940 self.test_session.update(desired_min_tx=1000000,
941 required_min_rx=1000000)
943 reference_packet = wait_for_bfd_packet(self)
944 time_mark = time.time()
946 self.test_session.update(required_min_rx=interval)
947 self.test_session.send_packet()
948 extra_time = time.time() - time_mark
949 p = wait_for_bfd_packet(self)
950 # first packet is allowed to be late by time we spent doing the update
951 # calculated in extra_time
952 self.assert_in_range(p.time - reference_packet.time,
953 .95 * 0.75 * interval / USEC_IN_SEC,
954 1.05 * interval / USEC_IN_SEC + extra_time,
955 "time between BFD packets")
957 for dummy in range(3):
958 p = wait_for_bfd_packet(self)
959 diff = p.time - reference_packet.time
960 self.assert_in_range(diff, .95 * .75 * interval / USEC_IN_SEC,
961 1.05 * interval / USEC_IN_SEC,
962 "time between BFD packets")
965 def test_modify_req_min_rx_double(self):
966 """ modify session - double required min rx """
968 p = wait_for_bfd_packet(self)
969 self.test_session.update(desired_min_tx=10000,
970 required_min_rx=10000)
971 self.test_session.send_packet()
972 # double required min rx
973 self.vpp_session.modify_parameters(
974 required_min_rx=2 * self.vpp_session.required_min_rx)
975 p = wait_for_bfd_packet(
976 self, pcap_time_min=time.time() - self.vpp_clock_offset)
977 # poll bit needs to be set
978 self.assertIn("P", p.sprintf("%BFD.flags%"),
979 "Poll bit not set in BFD packet")
980 # finish poll sequence with final packet
981 final = self.test_session.create_packet()
982 final[BFD].flags = "F"
983 timeout = self.test_session.detect_mult * \
984 max(self.test_session.desired_min_tx,
985 self.vpp_session.required_min_rx) / USEC_IN_SEC
986 self.test_session.send_packet(final)
987 time_mark = time.time()
988 e = self.vapi.wait_for_event(2 * timeout, "bfd_udp_session_event")
989 verify_event(self, e, expected_state=BFDState.down)
990 time_to_event = time.time() - time_mark
991 self.assert_in_range(time_to_event, .9 * timeout,
992 1.1 * timeout, "session timeout")
994 def test_modify_req_min_rx_halve(self):
995 """ modify session - halve required min rx """
996 self.vpp_session.modify_parameters(
997 required_min_rx=2 * self.vpp_session.required_min_rx)
999 p = wait_for_bfd_packet(self)
1000 self.test_session.update(desired_min_tx=10000,
1001 required_min_rx=10000)
1002 self.test_session.send_packet()
1003 p = wait_for_bfd_packet(
1004 self, pcap_time_min=time.time() - self.vpp_clock_offset)
1005 # halve required min rx
1006 old_required_min_rx = self.vpp_session.required_min_rx
1007 self.vpp_session.modify_parameters(
1008 required_min_rx=self.vpp_session.required_min_rx // 2)
1009 # now we wait 0.8*3*old-req-min-rx and the session should still be up
1010 self.sleep(0.8 * self.vpp_session.detect_mult *
1011 old_required_min_rx / USEC_IN_SEC,
1012 "wait before finishing poll sequence")
1013 self.assert_equal(len(self.vapi.collect_events()), 0,
1014 "number of bfd events")
1015 p = wait_for_bfd_packet(self)
1016 # poll bit needs to be set
1017 self.assertIn("P", p.sprintf("%BFD.flags%"),
1018 "Poll bit not set in BFD packet")
1019 # finish poll sequence with final packet
1020 final = self.test_session.create_packet()
1021 final[BFD].flags = "F"
1022 self.test_session.send_packet(final)
1023 # now the session should time out under new conditions
1024 detection_time = self.test_session.detect_mult *\
1025 self.vpp_session.required_min_rx / USEC_IN_SEC
1026 before = time.time()
1027 e = self.vapi.wait_for_event(
1028 2 * detection_time, "bfd_udp_session_event")
1030 self.assert_in_range(after - before,
1031 0.9 * detection_time,
1032 1.1 * detection_time,
1033 "time before bfd session goes down")
1034 verify_event(self, e, expected_state=BFDState.down)
1036 def test_modify_detect_mult(self):
1037 """ modify detect multiplier """
1038 bfd_session_up(self)
1039 p = wait_for_bfd_packet(self)
1040 self.vpp_session.modify_parameters(detect_mult=1)
1041 p = wait_for_bfd_packet(
1042 self, pcap_time_min=time.time() - self.vpp_clock_offset)
1043 self.assert_equal(self.vpp_session.detect_mult,
1046 # poll bit must not be set
1047 self.assertNotIn("P", p.sprintf("%BFD.flags%"),
1048 "Poll bit not set in BFD packet")
1049 self.vpp_session.modify_parameters(detect_mult=10)
1050 p = wait_for_bfd_packet(
1051 self, pcap_time_min=time.time() - self.vpp_clock_offset)
1052 self.assert_equal(self.vpp_session.detect_mult,
1055 # poll bit must not be set
1056 self.assertNotIn("P", p.sprintf("%BFD.flags%"),
1057 "Poll bit not set in BFD packet")
1059 def test_queued_poll(self):
1060 """ test poll sequence queueing """
1061 bfd_session_up(self)
1062 p = wait_for_bfd_packet(self)
1063 self.vpp_session.modify_parameters(
1064 required_min_rx=2 * self.vpp_session.required_min_rx)
1065 p = wait_for_bfd_packet(self)
1066 poll_sequence_start = time.time()
1067 poll_sequence_length_min = 0.5
1068 send_final_after = time.time() + poll_sequence_length_min
1069 # poll bit needs to be set
1070 self.assertIn("P", p.sprintf("%BFD.flags%"),
1071 "Poll bit not set in BFD packet")
1072 self.assert_equal(p[BFD].required_min_rx_interval,
1073 self.vpp_session.required_min_rx,
1074 "BFD required min rx interval")
1075 self.vpp_session.modify_parameters(
1076 required_min_rx=2 * self.vpp_session.required_min_rx)
1077 # 2nd poll sequence should be queued now
1078 # don't send the reply back yet, wait for some time to emulate
1079 # longer round-trip time
1081 while time.time() < send_final_after:
1082 self.test_session.send_packet()
1083 p = wait_for_bfd_packet(self)
1084 self.assert_equal(len(self.vapi.collect_events()), 0,
1085 "number of bfd events")
1086 self.assert_equal(p[BFD].required_min_rx_interval,
1087 self.vpp_session.required_min_rx,
1088 "BFD required min rx interval")
1090 # poll bit must be set
1091 self.assertIn("P", p.sprintf("%BFD.flags%"),
1092 "Poll bit not set in BFD packet")
1093 final = self.test_session.create_packet()
1094 final[BFD].flags = "F"
1095 self.test_session.send_packet(final)
1096 # finish 1st with final
1097 poll_sequence_length = time.time() - poll_sequence_start
1098 # vpp must wait for some time before starting new poll sequence
1099 poll_no_2_started = False
1100 for dummy in range(2 * packet_count):
1101 p = wait_for_bfd_packet(self)
1102 self.assert_equal(len(self.vapi.collect_events()), 0,
1103 "number of bfd events")
1104 if "P" in p.sprintf("%BFD.flags%"):
1105 poll_no_2_started = True
1106 if time.time() < poll_sequence_start + poll_sequence_length:
1107 raise Exception("VPP started 2nd poll sequence too soon")
1108 final = self.test_session.create_packet()
1109 final[BFD].flags = "F"
1110 self.test_session.send_packet(final)
1113 self.test_session.send_packet()
1114 self.assertTrue(poll_no_2_started, "2nd poll sequence not performed")
1115 # finish 2nd with final
1116 final = self.test_session.create_packet()
1117 final[BFD].flags = "F"
1118 self.test_session.send_packet(final)
1119 p = wait_for_bfd_packet(self)
1120 # poll bit must not be set
1121 self.assertNotIn("P", p.sprintf("%BFD.flags%"),
1122 "Poll bit set in BFD packet")
1124 # returning inconsistent results requiring retries in per-patch tests
1125 @unittest.skipUnless(running_extended_tests, "part of extended tests")
1126 def test_poll_response(self):
1127 """ test correct response to control frame with poll bit set """
1128 bfd_session_up(self)
1129 poll = self.test_session.create_packet()
1130 poll[BFD].flags = "P"
1131 self.test_session.send_packet(poll)
1132 final = wait_for_bfd_packet(
1133 self, pcap_time_min=time.time() - self.vpp_clock_offset)
1134 self.assertIn("F", final.sprintf("%BFD.flags%"))
1136 def test_no_periodic_if_remote_demand(self):
1137 """ no periodic frames outside poll sequence if remote demand set """
1138 bfd_session_up(self)
1139 demand = self.test_session.create_packet()
1140 demand[BFD].flags = "D"
1141 self.test_session.send_packet(demand)
1142 transmit_time = 0.9 \
1143 * max(self.vpp_session.required_min_rx,
1144 self.test_session.desired_min_tx) \
1147 for dummy in range(self.test_session.detect_mult * 2):
1148 self.sleep(transmit_time)
1149 self.test_session.send_packet(demand)
1151 p = wait_for_bfd_packet(self, timeout=0)
1152 self.logger.error(ppp("Received unexpected packet:", p))
1154 except CaptureTimeoutError:
1156 events = self.vapi.collect_events()
1158 self.logger.error("Received unexpected event: %s", e)
1159 self.assert_equal(count, 0, "number of packets received")
1160 self.assert_equal(len(events), 0, "number of events received")
1162 def test_echo_looped_back(self):
1163 """ echo packets looped back """
1164 bfd_session_up(self)
1165 stats_before = bfd_grab_stats_snapshot(self)
1166 self.pg0.enable_capture()
1167 echo_packet_count = 10
1168 # random source port low enough to increment a few times..
1169 udp_sport_tx = randint(1, 50000)
1170 udp_sport_rx = udp_sport_tx
1171 echo_packet = (Ether(src=self.pg0.remote_mac,
1172 dst=self.pg0.local_mac) /
1173 IP(src=self.pg0.remote_ip4,
1174 dst=self.pg0.remote_ip4) /
1175 UDP(dport=BFD.udp_dport_echo) /
1176 Raw("this should be looped back"))
1177 for dummy in range(echo_packet_count):
1178 self.sleep(.01, "delay between echo packets")
1179 echo_packet[UDP].sport = udp_sport_tx
1181 self.logger.debug(ppp("Sending packet:", echo_packet))
1182 self.pg0.add_stream(echo_packet)
1184 self.logger.debug(self.vapi.ppcli("show trace"))
1186 bfd_control_packets_rx = 0
1187 while counter < echo_packet_count:
1188 p = self.pg0.wait_for_packet(1)
1189 self.logger.debug(ppp("Got packet:", p))
1191 self.assert_equal(self.pg0.remote_mac,
1192 ether.dst, "Destination MAC")
1193 self.assert_equal(self.pg0.local_mac, ether.src, "Source MAC")
1195 self.assert_equal(self.pg0.remote_ip4, ip.dst, "Destination IP")
1197 if udp.dport == BFD.udp_dport:
1198 bfd_control_packets_rx += 1
1200 self.assert_equal(self.pg0.remote_ip4, ip.src, "Source IP")
1201 self.assert_equal(udp.dport, BFD.udp_dport_echo,
1202 "UDP destination port")
1203 self.assert_equal(udp.sport, udp_sport_rx, "UDP source port")
1205 # need to compare the hex payload here, otherwise BFD_vpp_echo
1207 self.assertEqual(scapy.compat.raw(p[UDP].payload),
1208 scapy.compat.raw(echo_packet[UDP].payload),
1209 "Received packet is not the echo packet sent")
1211 self.assert_equal(udp_sport_tx, udp_sport_rx, "UDP source port (== "
1212 "ECHO packet identifier for test purposes)")
1213 stats_after = bfd_grab_stats_snapshot(self)
1214 diff = bfd_stats_diff(stats_before, stats_after)
1216 0, diff.rx, "RX counter bumped but no BFD packets sent")
1218 bfd_control_packets_rx, diff.tx, "TX counter incorrect")
1219 self.assertEqual(0, diff.rx_echo,
1220 "RX echo counter bumped but no BFD session exists")
1221 self.assertEqual(0, diff.tx_echo,
1222 "TX echo counter bumped but no BFD session exists")
1224 def test_echo(self):
1225 """ echo function """
1226 stats_before = bfd_grab_stats_snapshot(self)
1227 bfd_session_up(self)
1228 self.test_session.update(required_min_echo_rx=150000)
1229 self.test_session.send_packet()
1230 detection_time = self.test_session.detect_mult *\
1231 self.vpp_session.required_min_rx / USEC_IN_SEC
1232 # echo shouldn't work without echo source set
1233 for dummy in range(10):
1234 sleep = self.vpp_session.required_min_rx / USEC_IN_SEC
1235 self.sleep(sleep, "delay before sending bfd packet")
1236 self.test_session.send_packet()
1237 p = wait_for_bfd_packet(
1238 self, pcap_time_min=time.time() - self.vpp_clock_offset)
1239 self.assert_equal(p[BFD].required_min_rx_interval,
1240 self.vpp_session.required_min_rx,
1241 "BFD required min rx interval")
1242 self.test_session.send_packet()
1243 self.vapi.bfd_udp_set_echo_source(
1244 sw_if_index=self.loopback0.sw_if_index)
1246 # should be turned on - loopback echo packets
1247 for dummy in range(3):
1248 loop_until = time.time() + 0.75 * detection_time
1249 while time.time() < loop_until:
1250 p = self.pg0.wait_for_packet(1)
1251 self.logger.debug(ppp("Got packet:", p))
1252 if p[UDP].dport == BFD.udp_dport_echo:
1254 p[IP].dst, self.pg0.local_ip4, "BFD ECHO dst IP")
1255 self.assertNotEqual(p[IP].src, self.loopback0.local_ip4,
1256 "BFD ECHO src IP equal to loopback IP")
1257 self.logger.debug(ppp("Looping back packet:", p))
1258 self.assert_equal(p[Ether].dst, self.pg0.remote_mac,
1259 "ECHO packet destination MAC address")
1260 p[Ether].dst = self.pg0.local_mac
1261 self.pg0.add_stream(p)
1262 self.test_session.rx_packets_echo += 1
1263 self.test_session.tx_packets_echo += 1
1266 elif p.haslayer(BFD):
1267 self.test_session.rx_packets += 1
1269 self.assertGreaterEqual(
1270 p[BFD].required_min_rx_interval,
1272 if "P" in p.sprintf("%BFD.flags%"):
1273 final = self.test_session.create_packet()
1274 final[BFD].flags = "F"
1275 self.test_session.send_packet(final)
1277 raise Exception(ppp("Received unknown packet:", p))
1279 self.assert_equal(len(self.vapi.collect_events()), 0,
1280 "number of bfd events")
1281 self.test_session.send_packet()
1282 self.assertTrue(echo_seen, "No echo packets received")
1284 stats_after = bfd_grab_stats_snapshot(self)
1285 diff = bfd_stats_diff(stats_before, stats_after)
1286 # our rx is vpp tx and vice versa, also tolerate one packet off
1287 self.assert_in_range(self.test_session.tx_packets,
1288 diff.rx - 1, diff.rx + 1, "RX counter")
1289 self.assert_in_range(self.test_session.rx_packets,
1290 diff.tx - 1, diff.tx + 1, "TX counter")
1291 self.assert_in_range(self.test_session.tx_packets_echo,
1292 diff.rx_echo - 1, diff.rx_echo + 1,
1294 self.assert_in_range(self.test_session.rx_packets_echo,
1295 diff.tx_echo - 1, diff.tx_echo + 1,
1298 def test_echo_fail(self):
1299 """ session goes down if echo function fails """
1300 bfd_session_up(self)
1301 self.test_session.update(required_min_echo_rx=150000)
1302 self.test_session.send_packet()
1303 detection_time = self.test_session.detect_mult *\
1304 self.vpp_session.required_min_rx / USEC_IN_SEC
1305 self.vapi.bfd_udp_set_echo_source(
1306 sw_if_index=self.loopback0.sw_if_index)
1307 # echo function should be used now, but we will drop the echo packets
1308 verified_diag = False
1309 for dummy in range(3):
1310 loop_until = time.time() + 0.75 * detection_time
1311 while time.time() < loop_until:
1312 p = self.pg0.wait_for_packet(1)
1313 self.logger.debug(ppp("Got packet:", p))
1314 if p[UDP].dport == BFD.udp_dport_echo:
1317 elif p.haslayer(BFD):
1318 if "P" in p.sprintf("%BFD.flags%"):
1319 self.assertGreaterEqual(
1320 p[BFD].required_min_rx_interval,
1322 final = self.test_session.create_packet()
1323 final[BFD].flags = "F"
1324 self.test_session.send_packet(final)
1325 if p[BFD].state == BFDState.down:
1326 self.assert_equal(p[BFD].diag,
1327 BFDDiagCode.echo_function_failed,
1329 verified_diag = True
1331 raise Exception(ppp("Received unknown packet:", p))
1332 self.test_session.send_packet()
1333 events = self.vapi.collect_events()
1334 self.assert_equal(len(events), 1, "number of bfd events")
1335 self.assert_equal(events[0].state, BFDState.down, BFDState)
1336 self.assertTrue(verified_diag, "Incorrect diagnostics code received")
1338 def test_echo_stop(self):
1339 """ echo function stops if peer sets required min echo rx zero """
1340 bfd_session_up(self)
1341 self.test_session.update(required_min_echo_rx=150000)
1342 self.test_session.send_packet()
1343 self.vapi.bfd_udp_set_echo_source(
1344 sw_if_index=self.loopback0.sw_if_index)
1345 # wait for first echo packet
1347 p = self.pg0.wait_for_packet(1)
1348 self.logger.debug(ppp("Got packet:", p))
1349 if p[UDP].dport == BFD.udp_dport_echo:
1350 self.logger.debug(ppp("Looping back packet:", p))
1351 p[Ether].dst = self.pg0.local_mac
1352 self.pg0.add_stream(p)
1355 elif p.haslayer(BFD):
1359 raise Exception(ppp("Received unknown packet:", p))
1360 self.test_session.update(required_min_echo_rx=0)
1361 self.test_session.send_packet()
1362 # echo packets shouldn't arrive anymore
1363 for dummy in range(5):
1364 wait_for_bfd_packet(
1365 self, pcap_time_min=time.time() - self.vpp_clock_offset)
1366 self.test_session.send_packet()
1367 events = self.vapi.collect_events()
1368 self.assert_equal(len(events), 0, "number of bfd events")
1370 def test_echo_source_removed(self):
1371 """ echo function stops if echo source is removed """
1372 bfd_session_up(self)
1373 self.test_session.update(required_min_echo_rx=150000)
1374 self.test_session.send_packet()
1375 self.vapi.bfd_udp_set_echo_source(
1376 sw_if_index=self.loopback0.sw_if_index)
1377 # wait for first echo packet
1379 p = self.pg0.wait_for_packet(1)
1380 self.logger.debug(ppp("Got packet:", p))
1381 if p[UDP].dport == BFD.udp_dport_echo:
1382 self.logger.debug(ppp("Looping back packet:", p))
1383 p[Ether].dst = self.pg0.local_mac
1384 self.pg0.add_stream(p)
1387 elif p.haslayer(BFD):
1391 raise Exception(ppp("Received unknown packet:", p))
1392 self.vapi.bfd_udp_del_echo_source()
1393 self.test_session.send_packet()
1394 # echo packets shouldn't arrive anymore
1395 for dummy in range(5):
1396 wait_for_bfd_packet(
1397 self, pcap_time_min=time.time() - self.vpp_clock_offset)
1398 self.test_session.send_packet()
1399 events = self.vapi.collect_events()
1400 self.assert_equal(len(events), 0, "number of bfd events")
1402 def test_stale_echo(self):
1403 """ stale echo packets don't keep a session up """
1404 bfd_session_up(self)
1405 self.test_session.update(required_min_echo_rx=150000)
1406 self.vapi.bfd_udp_set_echo_source(
1407 sw_if_index=self.loopback0.sw_if_index)
1408 self.test_session.send_packet()
1409 # should be turned on - loopback echo packets
1413 for dummy in range(10 * self.vpp_session.detect_mult):
1414 p = self.pg0.wait_for_packet(1)
1415 if p[UDP].dport == BFD.udp_dport_echo:
1416 if echo_packet is None:
1417 self.logger.debug(ppp("Got first echo packet:", p))
1419 timeout_at = time.time() + self.vpp_session.detect_mult * \
1420 self.test_session.required_min_echo_rx / USEC_IN_SEC
1422 self.logger.debug(ppp("Got followup echo packet:", p))
1423 self.logger.debug(ppp("Looping back first echo packet:", p))
1424 echo_packet[Ether].dst = self.pg0.local_mac
1425 self.pg0.add_stream(echo_packet)
1427 elif p.haslayer(BFD):
1428 self.logger.debug(ppp("Got packet:", p))
1429 if "P" in p.sprintf("%BFD.flags%"):
1430 final = self.test_session.create_packet()
1431 final[BFD].flags = "F"
1432 self.test_session.send_packet(final)
1433 if p[BFD].state == BFDState.down:
1434 self.assertIsNotNone(
1436 "Session went down before first echo packet received")
1438 self.assertGreaterEqual(
1440 "Session timeout at %s, but is expected at %s" %
1442 self.assert_equal(p[BFD].diag,
1443 BFDDiagCode.echo_function_failed,
1445 events = self.vapi.collect_events()
1446 self.assert_equal(len(events), 1, "number of bfd events")
1447 self.assert_equal(events[0].state, BFDState.down, BFDState)
1451 raise Exception(ppp("Received unknown packet:", p))
1452 self.test_session.send_packet()
1453 self.assertTrue(timeout_ok, "Expected timeout event didn't occur")
1455 def test_invalid_echo_checksum(self):
1456 """ echo packets with invalid checksum don't keep a session up """
1457 bfd_session_up(self)
1458 self.test_session.update(required_min_echo_rx=150000)
1459 self.vapi.bfd_udp_set_echo_source(
1460 sw_if_index=self.loopback0.sw_if_index)
1461 self.test_session.send_packet()
1462 # should be turned on - loopback echo packets
1465 for dummy in range(10 * self.vpp_session.detect_mult):
1466 p = self.pg0.wait_for_packet(1)
1467 if p[UDP].dport == BFD.udp_dport_echo:
1468 self.logger.debug(ppp("Got echo packet:", p))
1469 if timeout_at is None:
1470 timeout_at = time.time() + self.vpp_session.detect_mult * \
1471 self.test_session.required_min_echo_rx / USEC_IN_SEC
1472 p[BFD_vpp_echo].checksum = getrandbits(64)
1473 p[Ether].dst = self.pg0.local_mac
1474 self.logger.debug(ppp("Looping back modified echo packet:", p))
1475 self.pg0.add_stream(p)
1477 elif p.haslayer(BFD):
1478 self.logger.debug(ppp("Got packet:", p))
1479 if "P" in p.sprintf("%BFD.flags%"):
1480 final = self.test_session.create_packet()
1481 final[BFD].flags = "F"
1482 self.test_session.send_packet(final)
1483 if p[BFD].state == BFDState.down:
1484 self.assertIsNotNone(
1486 "Session went down before first echo packet received")
1488 self.assertGreaterEqual(
1490 "Session timeout at %s, but is expected at %s" %
1492 self.assert_equal(p[BFD].diag,
1493 BFDDiagCode.echo_function_failed,
1495 events = self.vapi.collect_events()
1496 self.assert_equal(len(events), 1, "number of bfd events")
1497 self.assert_equal(events[0].state, BFDState.down, BFDState)
1501 raise Exception(ppp("Received unknown packet:", p))
1502 self.test_session.send_packet()
1503 self.assertTrue(timeout_ok, "Expected timeout event didn't occur")
1505 def test_admin_up_down(self):
1506 """ put session admin-up and admin-down """
1507 bfd_session_up(self)
1508 self.vpp_session.admin_down()
1509 self.pg0.enable_capture()
1510 e = self.vapi.wait_for_event(1, "bfd_udp_session_event")
1511 verify_event(self, e, expected_state=BFDState.admin_down)
1512 for dummy in range(2):
1513 p = wait_for_bfd_packet(self)
1514 self.assert_equal(p[BFD].state, BFDState.admin_down, BFDState)
1515 # try to bring session up - shouldn't be possible
1516 self.test_session.update(state=BFDState.init)
1517 self.test_session.send_packet()
1518 for dummy in range(2):
1519 p = wait_for_bfd_packet(self)
1520 self.assert_equal(p[BFD].state, BFDState.admin_down, BFDState)
1521 self.vpp_session.admin_up()
1522 self.test_session.update(state=BFDState.down)
1523 e = self.vapi.wait_for_event(1, "bfd_udp_session_event")
1524 verify_event(self, e, expected_state=BFDState.down)
1525 p = wait_for_bfd_packet(
1526 self, pcap_time_min=time.time() - self.vpp_clock_offset)
1527 self.assert_equal(p[BFD].state, BFDState.down, BFDState)
1528 self.test_session.send_packet()
1529 p = wait_for_bfd_packet(
1530 self, pcap_time_min=time.time() - self.vpp_clock_offset)
1531 self.assert_equal(p[BFD].state, BFDState.init, BFDState)
1532 e = self.vapi.wait_for_event(1, "bfd_udp_session_event")
1533 verify_event(self, e, expected_state=BFDState.init)
1534 self.test_session.update(state=BFDState.up)
1535 self.test_session.send_packet()
1536 p = wait_for_bfd_packet(
1537 self, pcap_time_min=time.time() - self.vpp_clock_offset)
1538 self.assert_equal(p[BFD].state, BFDState.up, BFDState)
1539 e = self.vapi.wait_for_event(1, "bfd_udp_session_event")
1540 verify_event(self, e, expected_state=BFDState.up)
1542 def test_config_change_remote_demand(self):
1543 """ configuration change while peer in demand mode """
1544 bfd_session_up(self)
1545 demand = self.test_session.create_packet()
1546 demand[BFD].flags = "D"
1547 self.test_session.send_packet(demand)
1548 self.vpp_session.modify_parameters(
1549 required_min_rx=2 * self.vpp_session.required_min_rx)
1550 p = wait_for_bfd_packet(
1551 self, pcap_time_min=time.time() - self.vpp_clock_offset)
1552 # poll bit must be set
1553 self.assertIn("P", p.sprintf("%BFD.flags%"), "Poll bit not set")
1554 # terminate poll sequence
1555 final = self.test_session.create_packet()
1556 final[BFD].flags = "D+F"
1557 self.test_session.send_packet(final)
1558 # vpp should be quiet now again
1559 transmit_time = 0.9 \
1560 * max(self.vpp_session.required_min_rx,
1561 self.test_session.desired_min_tx) \
1564 for dummy in range(self.test_session.detect_mult * 2):
1565 self.sleep(transmit_time)
1566 self.test_session.send_packet(demand)
1568 p = wait_for_bfd_packet(self, timeout=0)
1569 self.logger.error(ppp("Received unexpected packet:", p))
1571 except CaptureTimeoutError:
1573 events = self.vapi.collect_events()
1575 self.logger.error("Received unexpected event: %s", e)
1576 self.assert_equal(count, 0, "number of packets received")
1577 self.assert_equal(len(events), 0, "number of events received")
1579 def test_intf_deleted(self):
1580 """ interface with bfd session deleted """
1581 intf = VppLoInterface(self)
1584 sw_if_index = intf.sw_if_index
1585 vpp_session = VppBFDUDPSession(self, intf, intf.remote_ip4)
1586 vpp_session.add_vpp_config()
1587 vpp_session.admin_up()
1588 intf.remove_vpp_config()
1589 e = self.vapi.wait_for_event(1, "bfd_udp_session_event")
1590 self.assert_equal(e.sw_if_index, sw_if_index, "sw_if_index")
1591 self.assertFalse(vpp_session.query_vpp_config())
1595 @tag_fixme_vpp_workers
1596 class BFD6TestCase(VppTestCase):
1597 """Bidirectional Forwarding Detection (BFD) (IPv6) """
1600 vpp_clock_offset = None
1605 def setUpClass(cls):
1606 super(BFD6TestCase, cls).setUpClass()
1607 cls.vapi.cli("set log class bfd level debug")
1609 cls.create_pg_interfaces([0])
1610 cls.pg0.config_ip6()
1611 cls.pg0.configure_ipv6_neighbors()
1613 cls.pg0.resolve_ndp()
1614 cls.create_loopback_interfaces(1)
1615 cls.loopback0 = cls.lo_interfaces[0]
1616 cls.loopback0.config_ip6()
1617 cls.loopback0.admin_up()
1620 super(BFD6TestCase, cls).tearDownClass()
1624 def tearDownClass(cls):
1625 super(BFD6TestCase, cls).tearDownClass()
1628 super(BFD6TestCase, self).setUp()
1629 self.factory = AuthKeyFactory()
1630 self.vapi.want_bfd_events()
1631 self.pg0.enable_capture()
1633 self.bfd_udp4_sessions = self.statistics['/bfd/udp4/sessions']
1634 self.bfd_udp6_sessions = self.statistics['/bfd/udp6/sessions']
1635 self.vpp_session = VppBFDUDPSession(self, self.pg0,
1636 self.pg0.remote_ip6,
1638 self.vpp_session.add_vpp_config()
1639 self.vpp_session.admin_up()
1640 self.test_session = BFDTestSession(self, self.pg0, AF_INET6)
1641 self.logger.debug(self.vapi.cli("show adj nbr"))
1642 except BaseException:
1643 self.vapi.want_bfd_events(enable_disable=0)
1647 if not self.vpp_dead:
1648 self.vapi.want_bfd_events(enable_disable=0)
1649 self.vapi.collect_events() # clear the event queue
1650 super(BFD6TestCase, self).tearDown()
1652 def test_session_up(self):
1653 """ bring BFD session up """
1654 bfd_session_up(self)
1655 bfd_udp4_sessions = self.statistics['/bfd/udp4/sessions']
1656 bfd_udp6_sessions = self.statistics['/bfd/udp6/sessions']
1657 self.assert_equal(bfd_udp4_sessions, self.bfd_udp4_sessions)
1658 self.assert_equal(bfd_udp6_sessions - self.bfd_udp6_sessions, 1)
1660 def test_session_up_by_ip(self):
1661 """ bring BFD session up - first frame looked up by address pair """
1662 self.logger.info("BFD: Sending Slow control frame")
1663 self.test_session.update(my_discriminator=randint(0, 40000000))
1664 self.test_session.send_packet()
1665 self.pg0.enable_capture()
1666 p = self.pg0.wait_for_packet(1)
1667 self.assert_equal(p[BFD].your_discriminator,
1668 self.test_session.my_discriminator,
1669 "BFD - your discriminator")
1670 self.assert_equal(p[BFD].state, BFDState.init, BFDState)
1671 self.test_session.update(your_discriminator=p[BFD].my_discriminator,
1673 self.logger.info("BFD: Waiting for event")
1674 e = self.vapi.wait_for_event(1, "bfd_udp_session_event")
1675 verify_event(self, e, expected_state=BFDState.init)
1676 self.logger.info("BFD: Sending Up")
1677 self.test_session.send_packet()
1678 self.logger.info("BFD: Waiting for event")
1679 e = self.vapi.wait_for_event(1, "bfd_udp_session_event")
1680 verify_event(self, e, expected_state=BFDState.up)
1681 self.logger.info("BFD: Session is Up")
1682 self.test_session.update(state=BFDState.up)
1683 self.test_session.send_packet()
1684 self.assert_equal(self.vpp_session.state, BFDState.up, BFDState)
1686 def test_hold_up(self):
1687 """ hold BFD session up """
1688 bfd_session_up(self)
1689 for dummy in range(self.test_session.detect_mult * 2):
1690 wait_for_bfd_packet(self)
1691 self.test_session.send_packet()
1692 self.assert_equal(len(self.vapi.collect_events()), 0,
1693 "number of bfd events")
1694 self.assert_equal(self.vpp_session.state, BFDState.up, BFDState)
1696 def test_echo_looped_back(self):
1697 """ echo packets looped back """
1698 bfd_session_up(self)
1699 stats_before = bfd_grab_stats_snapshot(self)
1700 self.pg0.enable_capture()
1701 echo_packet_count = 10
1702 # random source port low enough to increment a few times..
1703 udp_sport_tx = randint(1, 50000)
1704 udp_sport_rx = udp_sport_tx
1705 echo_packet = (Ether(src=self.pg0.remote_mac,
1706 dst=self.pg0.local_mac) /
1707 IPv6(src=self.pg0.remote_ip6,
1708 dst=self.pg0.remote_ip6) /
1709 UDP(dport=BFD.udp_dport_echo) /
1710 Raw("this should be looped back"))
1711 for dummy in range(echo_packet_count):
1712 self.sleep(.01, "delay between echo packets")
1713 echo_packet[UDP].sport = udp_sport_tx
1715 self.logger.debug(ppp("Sending packet:", echo_packet))
1716 self.pg0.add_stream(echo_packet)
1719 bfd_control_packets_rx = 0
1720 while counter < echo_packet_count:
1721 p = self.pg0.wait_for_packet(1)
1722 self.logger.debug(ppp("Got packet:", p))
1724 self.assert_equal(self.pg0.remote_mac,
1725 ether.dst, "Destination MAC")
1726 self.assert_equal(self.pg0.local_mac, ether.src, "Source MAC")
1728 self.assert_equal(self.pg0.remote_ip6, ip.dst, "Destination IP")
1730 if udp.dport == BFD.udp_dport:
1731 bfd_control_packets_rx += 1
1733 self.assert_equal(self.pg0.remote_ip6, ip.src, "Source IP")
1734 self.assert_equal(udp.dport, BFD.udp_dport_echo,
1735 "UDP destination port")
1736 self.assert_equal(udp.sport, udp_sport_rx, "UDP source port")
1738 # need to compare the hex payload here, otherwise BFD_vpp_echo
1740 self.assertEqual(scapy.compat.raw(p[UDP].payload),
1741 scapy.compat.raw(echo_packet[UDP].payload),
1742 "Received packet is not the echo packet sent")
1744 self.assert_equal(udp_sport_tx, udp_sport_rx, "UDP source port (== "
1745 "ECHO packet identifier for test purposes)")
1746 stats_after = bfd_grab_stats_snapshot(self)
1747 diff = bfd_stats_diff(stats_before, stats_after)
1749 0, diff.rx, "RX counter bumped but no BFD packets sent")
1750 self.assertEqual(bfd_control_packets_rx,
1751 diff.tx, "TX counter incorrect")
1752 self.assertEqual(0, diff.rx_echo,
1753 "RX echo counter bumped but no BFD session exists")
1754 self.assertEqual(0, diff.tx_echo,
1755 "TX echo counter bumped but no BFD session exists")
1757 def test_echo(self):
1758 """ echo function """
1759 stats_before = bfd_grab_stats_snapshot(self)
1760 bfd_session_up(self)
1761 self.test_session.update(required_min_echo_rx=150000)
1762 self.test_session.send_packet()
1763 detection_time = self.test_session.detect_mult *\
1764 self.vpp_session.required_min_rx / USEC_IN_SEC
1765 # echo shouldn't work without echo source set
1766 for dummy in range(10):
1767 sleep = self.vpp_session.required_min_rx / USEC_IN_SEC
1768 self.sleep(sleep, "delay before sending bfd packet")
1769 self.test_session.send_packet()
1770 p = wait_for_bfd_packet(
1771 self, pcap_time_min=time.time() - self.vpp_clock_offset)
1772 self.assert_equal(p[BFD].required_min_rx_interval,
1773 self.vpp_session.required_min_rx,
1774 "BFD required min rx interval")
1775 self.test_session.send_packet()
1776 self.vapi.bfd_udp_set_echo_source(
1777 sw_if_index=self.loopback0.sw_if_index)
1779 # should be turned on - loopback echo packets
1780 for dummy in range(3):
1781 loop_until = time.time() + 0.75 * detection_time
1782 while time.time() < loop_until:
1783 p = self.pg0.wait_for_packet(1)
1784 self.logger.debug(ppp("Got packet:", p))
1785 if p[UDP].dport == BFD.udp_dport_echo:
1787 p[IPv6].dst, self.pg0.local_ip6, "BFD ECHO dst IP")
1788 self.assertNotEqual(p[IPv6].src, self.loopback0.local_ip6,
1789 "BFD ECHO src IP equal to loopback IP")
1790 self.logger.debug(ppp("Looping back packet:", p))
1791 self.assert_equal(p[Ether].dst, self.pg0.remote_mac,
1792 "ECHO packet destination MAC address")
1793 self.test_session.rx_packets_echo += 1
1794 self.test_session.tx_packets_echo += 1
1795 p[Ether].dst = self.pg0.local_mac
1796 self.pg0.add_stream(p)
1799 elif p.haslayer(BFD):
1800 self.test_session.rx_packets += 1
1802 self.assertGreaterEqual(
1803 p[BFD].required_min_rx_interval,
1805 if "P" in p.sprintf("%BFD.flags%"):
1806 final = self.test_session.create_packet()
1807 final[BFD].flags = "F"
1808 self.test_session.send_packet(final)
1810 raise Exception(ppp("Received unknown packet:", p))
1812 self.assert_equal(len(self.vapi.collect_events()), 0,
1813 "number of bfd events")
1814 self.test_session.send_packet()
1815 self.assertTrue(echo_seen, "No echo packets received")
1817 stats_after = bfd_grab_stats_snapshot(self)
1818 diff = bfd_stats_diff(stats_before, stats_after)
1819 # our rx is vpp tx and vice versa, also tolerate one packet off
1820 self.assert_in_range(self.test_session.tx_packets,
1821 diff.rx - 1, diff.rx + 1, "RX counter")
1822 self.assert_in_range(self.test_session.rx_packets,
1823 diff.tx - 1, diff.tx + 1, "TX counter")
1824 self.assert_in_range(self.test_session.tx_packets_echo,
1825 diff.rx_echo - 1, diff.rx_echo + 1,
1827 self.assert_in_range(self.test_session.rx_packets_echo,
1828 diff.tx_echo - 1, diff.tx_echo + 1,
1831 def test_intf_deleted(self):
1832 """ interface with bfd session deleted """
1833 intf = VppLoInterface(self)
1836 sw_if_index = intf.sw_if_index
1837 vpp_session = VppBFDUDPSession(
1838 self, intf, intf.remote_ip6, af=AF_INET6)
1839 vpp_session.add_vpp_config()
1840 vpp_session.admin_up()
1841 intf.remove_vpp_config()
1842 e = self.vapi.wait_for_event(1, "bfd_udp_session_event")
1843 self.assert_equal(e.sw_if_index, sw_if_index, "sw_if_index")
1844 self.assertFalse(vpp_session.query_vpp_config())
1848 class BFDFIBTestCase(VppTestCase):
1849 """ BFD-FIB interactions (IPv6) """
1855 def setUpClass(cls):
1856 super(BFDFIBTestCase, cls).setUpClass()
1859 def tearDownClass(cls):
1860 super(BFDFIBTestCase, cls).tearDownClass()
1863 super(BFDFIBTestCase, self).setUp()
1864 self.create_pg_interfaces(range(1))
1866 self.vapi.want_bfd_events()
1867 self.pg0.enable_capture()
1869 for i in self.pg_interfaces:
1872 i.configure_ipv6_neighbors()
1875 if not self.vpp_dead:
1876 self.vapi.want_bfd_events(enable_disable=False)
1878 super(BFDFIBTestCase, self).tearDown()
1881 def pkt_is_not_data_traffic(p):
1882 """ not data traffic implies BFD or the usual IPv6 ND/RA"""
1883 if p.haslayer(BFD) or is_ipv6_misc(p):
1887 def test_session_with_fib(self):
1888 """ BFD-FIB interactions """
1890 # packets to match against both of the routes
1891 p = [(Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
1892 IPv6(src="3001::1", dst="2001::1") /
1893 UDP(sport=1234, dport=1234) /
1894 Raw(b'\xa5' * 100)),
1895 (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
1896 IPv6(src="3001::1", dst="2002::1") /
1897 UDP(sport=1234, dport=1234) /
1898 Raw(b'\xa5' * 100))]
1900 # A recursive and a non-recursive route via a next-hop that
1901 # will have a BFD session
1902 ip_2001_s_64 = VppIpRoute(self, "2001::", 64,
1903 [VppRoutePath(self.pg0.remote_ip6,
1904 self.pg0.sw_if_index)])
1905 ip_2002_s_64 = VppIpRoute(self, "2002::", 64,
1906 [VppRoutePath(self.pg0.remote_ip6,
1908 ip_2001_s_64.add_vpp_config()
1909 ip_2002_s_64.add_vpp_config()
1911 # bring the session up now the routes are present
1912 self.vpp_session = VppBFDUDPSession(self,
1914 self.pg0.remote_ip6,
1916 self.vpp_session.add_vpp_config()
1917 self.vpp_session.admin_up()
1918 self.test_session = BFDTestSession(self, self.pg0, AF_INET6)
1920 # session is up - traffic passes
1921 bfd_session_up(self)
1923 self.pg0.add_stream(p)
1926 captured = self.pg0.wait_for_packet(
1928 filter_out_fn=self.pkt_is_not_data_traffic)
1929 self.assertEqual(captured[IPv6].dst,
1932 # session is up - traffic is dropped
1933 bfd_session_down(self)
1935 self.pg0.add_stream(p)
1937 with self.assertRaises(CaptureTimeoutError):
1938 self.pg0.wait_for_packet(1, self.pkt_is_not_data_traffic)
1940 # session is up - traffic passes
1941 bfd_session_up(self)
1943 self.pg0.add_stream(p)
1946 captured = self.pg0.wait_for_packet(
1948 filter_out_fn=self.pkt_is_not_data_traffic)
1949 self.assertEqual(captured[IPv6].dst,
1953 @unittest.skipUnless(running_extended_tests, "part of extended tests")
1954 class BFDTunTestCase(VppTestCase):
1955 """ BFD over GRE tunnel """
1961 def setUpClass(cls):
1962 super(BFDTunTestCase, cls).setUpClass()
1965 def tearDownClass(cls):
1966 super(BFDTunTestCase, cls).tearDownClass()
1969 super(BFDTunTestCase, self).setUp()
1970 self.create_pg_interfaces(range(1))
1972 self.vapi.want_bfd_events()
1973 self.pg0.enable_capture()
1975 for i in self.pg_interfaces:
1981 if not self.vpp_dead:
1982 self.vapi.want_bfd_events(enable_disable=0)
1984 super(BFDTunTestCase, self).tearDown()
1987 def pkt_is_not_data_traffic(p):
1988 """ not data traffic implies BFD or the usual IPv6 ND/RA"""
1989 if p.haslayer(BFD) or is_ipv6_misc(p):
1993 def test_bfd_o_gre(self):
1996 # A GRE interface over which to run a BFD session
1997 gre_if = VppGreInterface(self,
1999 self.pg0.remote_ip4)
2000 gre_if.add_vpp_config()
2004 # bring the session up now the routes are present
2005 self.vpp_session = VppBFDUDPSession(self,
2009 self.vpp_session.add_vpp_config()
2010 self.vpp_session.admin_up()
2012 self.test_session = BFDTestSession(
2013 self, gre_if, AF_INET,
2014 tunnel_header=(IP(src=self.pg0.remote_ip4,
2015 dst=self.pg0.local_ip4) /
2017 phy_interface=self.pg0)
2019 # packets to match against both of the routes
2020 p = [(Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
2021 IP(src=self.pg0.remote_ip4, dst=gre_if.remote_ip4) /
2022 UDP(sport=1234, dport=1234) /
2023 Raw(b'\xa5' * 100))]
2025 # session is up - traffic passes
2026 bfd_session_up(self)
2028 self.send_and_expect(self.pg0, p, self.pg0)
2030 # bring session down
2031 bfd_session_down(self)
2035 class BFDSHA1TestCase(VppTestCase):
2036 """Bidirectional Forwarding Detection (BFD) (SHA1 auth) """
2039 vpp_clock_offset = None
2044 def setUpClass(cls):
2045 super(BFDSHA1TestCase, cls).setUpClass()
2046 cls.vapi.cli("set log class bfd level debug")
2048 cls.create_pg_interfaces([0])
2049 cls.pg0.config_ip4()
2051 cls.pg0.resolve_arp()
2054 super(BFDSHA1TestCase, cls).tearDownClass()
2058 def tearDownClass(cls):
2059 super(BFDSHA1TestCase, cls).tearDownClass()
2062 super(BFDSHA1TestCase, self).setUp()
2063 self.factory = AuthKeyFactory()
2064 self.vapi.want_bfd_events()
2065 self.pg0.enable_capture()
2068 if not self.vpp_dead:
2069 self.vapi.want_bfd_events(enable_disable=False)
2070 self.vapi.collect_events() # clear the event queue
2071 super(BFDSHA1TestCase, self).tearDown()
2073 def test_session_up(self):
2074 """ bring BFD session up """
2075 key = self.factory.create_random_key(self)
2076 key.add_vpp_config()
2077 self.vpp_session = VppBFDUDPSession(self, self.pg0,
2078 self.pg0.remote_ip4,
2080 self.vpp_session.add_vpp_config()
2081 self.vpp_session.admin_up()
2082 self.test_session = BFDTestSession(
2083 self, self.pg0, AF_INET, sha1_key=key,
2084 bfd_key_id=self.vpp_session.bfd_key_id)
2085 bfd_session_up(self)
2087 def test_hold_up(self):
2088 """ hold BFD session up """
2089 key = self.factory.create_random_key(self)
2090 key.add_vpp_config()
2091 self.vpp_session = VppBFDUDPSession(self, self.pg0,
2092 self.pg0.remote_ip4,
2094 self.vpp_session.add_vpp_config()
2095 self.vpp_session.admin_up()
2096 self.test_session = BFDTestSession(
2097 self, self.pg0, AF_INET, sha1_key=key,
2098 bfd_key_id=self.vpp_session.bfd_key_id)
2099 bfd_session_up(self)
2100 for dummy in range(self.test_session.detect_mult * 2):
2101 wait_for_bfd_packet(self)
2102 self.test_session.send_packet()
2103 self.assert_equal(self.vpp_session.state, BFDState.up, BFDState)
2105 def test_hold_up_meticulous(self):
2106 """ hold BFD session up - meticulous auth """
2107 key = self.factory.create_random_key(
2108 self, BFDAuthType.meticulous_keyed_sha1)
2109 key.add_vpp_config()
2110 self.vpp_session = VppBFDUDPSession(self, self.pg0,
2111 self.pg0.remote_ip4, sha1_key=key)
2112 self.vpp_session.add_vpp_config()
2113 self.vpp_session.admin_up()
2114 # specify sequence number so that it wraps
2115 self.test_session = BFDTestSession(
2116 self, self.pg0, AF_INET, sha1_key=key,
2117 bfd_key_id=self.vpp_session.bfd_key_id,
2118 our_seq_number=0xFFFFFFFF - 4)
2119 bfd_session_up(self)
2120 for dummy in range(30):
2121 wait_for_bfd_packet(self)
2122 self.test_session.inc_seq_num()
2123 self.test_session.send_packet()
2124 self.assert_equal(self.vpp_session.state, BFDState.up, BFDState)
2126 def test_send_bad_seq_number(self):
2127 """ session is not kept alive by msgs with bad sequence numbers"""
2128 key = self.factory.create_random_key(
2129 self, BFDAuthType.meticulous_keyed_sha1)
2130 key.add_vpp_config()
2131 self.vpp_session = VppBFDUDPSession(self, self.pg0,
2132 self.pg0.remote_ip4, sha1_key=key)
2133 self.vpp_session.add_vpp_config()
2134 self.test_session = BFDTestSession(
2135 self, self.pg0, AF_INET, sha1_key=key,
2136 bfd_key_id=self.vpp_session.bfd_key_id)
2137 bfd_session_up(self)
2138 detection_time = self.test_session.detect_mult *\
2139 self.vpp_session.required_min_rx / USEC_IN_SEC
2140 send_until = time.time() + 2 * detection_time
2141 while time.time() < send_until:
2142 self.test_session.send_packet()
2143 self.sleep(0.7 * self.vpp_session.required_min_rx / USEC_IN_SEC,
2144 "time between bfd packets")
2145 e = self.vapi.collect_events()
2146 # session should be down now, because the sequence numbers weren't
2148 self.assert_equal(len(e), 1, "number of bfd events")
2149 verify_event(self, e[0], expected_state=BFDState.down)
2151 def execute_rogue_session_scenario(self, vpp_bfd_udp_session,
2152 legitimate_test_session,
2154 rogue_bfd_values=None):
2155 """ execute a rogue session interaction scenario
2157 1. create vpp session, add config
2158 2. bring the legitimate session up
2159 3. copy the bfd values from legitimate session to rogue session
2160 4. apply rogue_bfd_values to rogue session
2161 5. set rogue session state to down
2162 6. send message to take the session down from the rogue session
2163 7. assert that the legitimate session is unaffected
2166 self.vpp_session = vpp_bfd_udp_session
2167 self.vpp_session.add_vpp_config()
2168 self.test_session = legitimate_test_session
2169 # bring vpp session up
2170 bfd_session_up(self)
2171 # send packet from rogue session
2172 rogue_test_session.update(
2173 my_discriminator=self.test_session.my_discriminator,
2174 your_discriminator=self.test_session.your_discriminator,
2175 desired_min_tx=self.test_session.desired_min_tx,
2176 required_min_rx=self.test_session.required_min_rx,
2177 detect_mult=self.test_session.detect_mult,
2178 diag=self.test_session.diag,
2179 state=self.test_session.state,
2180 auth_type=self.test_session.auth_type)
2181 if rogue_bfd_values:
2182 rogue_test_session.update(**rogue_bfd_values)
2183 rogue_test_session.update(state=BFDState.down)
2184 rogue_test_session.send_packet()
2185 wait_for_bfd_packet(self)
2186 self.assert_equal(self.vpp_session.state, BFDState.up, BFDState)
2188 def test_mismatch_auth(self):
2189 """ session is not brought down by unauthenticated msg """
2190 key = self.factory.create_random_key(self)
2191 key.add_vpp_config()
2192 vpp_session = VppBFDUDPSession(
2193 self, self.pg0, self.pg0.remote_ip4, sha1_key=key)
2194 legitimate_test_session = BFDTestSession(
2195 self, self.pg0, AF_INET, sha1_key=key,
2196 bfd_key_id=vpp_session.bfd_key_id)
2197 rogue_test_session = BFDTestSession(self, self.pg0, AF_INET)
2198 self.execute_rogue_session_scenario(vpp_session,
2199 legitimate_test_session,
2202 def test_mismatch_bfd_key_id(self):
2203 """ session is not brought down by msg with non-existent key-id """
2204 key = self.factory.create_random_key(self)
2205 key.add_vpp_config()
2206 vpp_session = VppBFDUDPSession(
2207 self, self.pg0, self.pg0.remote_ip4, sha1_key=key)
2208 # pick a different random bfd key id
2210 while x == vpp_session.bfd_key_id:
2212 legitimate_test_session = BFDTestSession(
2213 self, self.pg0, AF_INET, sha1_key=key,
2214 bfd_key_id=vpp_session.bfd_key_id)
2215 rogue_test_session = BFDTestSession(
2216 self, self.pg0, AF_INET, sha1_key=key, bfd_key_id=x)
2217 self.execute_rogue_session_scenario(vpp_session,
2218 legitimate_test_session,
2221 def test_mismatched_auth_type(self):
2222 """ session is not brought down by msg with wrong auth type """
2223 key = self.factory.create_random_key(self)
2224 key.add_vpp_config()
2225 vpp_session = VppBFDUDPSession(
2226 self, self.pg0, self.pg0.remote_ip4, sha1_key=key)
2227 legitimate_test_session = BFDTestSession(
2228 self, self.pg0, AF_INET, sha1_key=key,
2229 bfd_key_id=vpp_session.bfd_key_id)
2230 rogue_test_session = BFDTestSession(
2231 self, self.pg0, AF_INET, sha1_key=key,
2232 bfd_key_id=vpp_session.bfd_key_id)
2233 self.execute_rogue_session_scenario(
2234 vpp_session, legitimate_test_session, rogue_test_session,
2235 {'auth_type': BFDAuthType.keyed_md5})
2237 def test_restart(self):
2238 """ simulate remote peer restart and resynchronization """
2239 key = self.factory.create_random_key(
2240 self, BFDAuthType.meticulous_keyed_sha1)
2241 key.add_vpp_config()
2242 self.vpp_session = VppBFDUDPSession(self, self.pg0,
2243 self.pg0.remote_ip4, sha1_key=key)
2244 self.vpp_session.add_vpp_config()
2245 self.test_session = BFDTestSession(
2246 self, self.pg0, AF_INET, sha1_key=key,
2247 bfd_key_id=self.vpp_session.bfd_key_id, our_seq_number=0)
2248 bfd_session_up(self)
2249 # don't send any packets for 2*detection_time
2250 detection_time = self.test_session.detect_mult *\
2251 self.vpp_session.required_min_rx / USEC_IN_SEC
2252 self.sleep(2 * detection_time, "simulating peer restart")
2253 events = self.vapi.collect_events()
2254 self.assert_equal(len(events), 1, "number of bfd events")
2255 verify_event(self, events[0], expected_state=BFDState.down)
2256 self.test_session.update(state=BFDState.down)
2257 # reset sequence number
2258 self.test_session.our_seq_number = 0
2259 self.test_session.vpp_seq_number = None
2260 # now throw away any pending packets
2261 self.pg0.enable_capture()
2262 self.test_session.my_discriminator = 0
2263 bfd_session_up(self)
2267 class BFDAuthOnOffTestCase(VppTestCase):
2268 """Bidirectional Forwarding Detection (BFD) (changing auth) """
2275 def setUpClass(cls):
2276 super(BFDAuthOnOffTestCase, cls).setUpClass()
2277 cls.vapi.cli("set log class bfd level debug")
2279 cls.create_pg_interfaces([0])
2280 cls.pg0.config_ip4()
2282 cls.pg0.resolve_arp()
2285 super(BFDAuthOnOffTestCase, cls).tearDownClass()
2289 def tearDownClass(cls):
2290 super(BFDAuthOnOffTestCase, cls).tearDownClass()
2293 super(BFDAuthOnOffTestCase, self).setUp()
2294 self.factory = AuthKeyFactory()
2295 self.vapi.want_bfd_events()
2296 self.pg0.enable_capture()
2299 if not self.vpp_dead:
2300 self.vapi.want_bfd_events(enable_disable=False)
2301 self.vapi.collect_events() # clear the event queue
2302 super(BFDAuthOnOffTestCase, self).tearDown()
2304 def test_auth_on_immediate(self):
2305 """ turn auth on without disturbing session state (immediate) """
2306 key = self.factory.create_random_key(self)
2307 key.add_vpp_config()
2308 self.vpp_session = VppBFDUDPSession(self, self.pg0,
2309 self.pg0.remote_ip4)
2310 self.vpp_session.add_vpp_config()
2311 self.test_session = BFDTestSession(self, self.pg0, AF_INET)
2312 bfd_session_up(self)
2313 for dummy in range(self.test_session.detect_mult * 2):
2314 p = wait_for_bfd_packet(self)
2315 self.assert_equal(p[BFD].state, BFDState.up, BFDState)
2316 self.test_session.send_packet()
2317 self.vpp_session.activate_auth(key)
2318 self.test_session.bfd_key_id = self.vpp_session.bfd_key_id
2319 self.test_session.sha1_key = key
2320 for dummy in range(self.test_session.detect_mult * 2):
2321 p = wait_for_bfd_packet(self)
2322 self.assert_equal(p[BFD].state, BFDState.up, BFDState)
2323 self.test_session.send_packet()
2324 self.assert_equal(self.vpp_session.state, BFDState.up, BFDState)
2325 self.assert_equal(len(self.vapi.collect_events()), 0,
2326 "number of bfd events")
2328 def test_auth_off_immediate(self):
2329 """ turn auth off without disturbing session state (immediate) """
2330 key = self.factory.create_random_key(self)
2331 key.add_vpp_config()
2332 self.vpp_session = VppBFDUDPSession(self, self.pg0,
2333 self.pg0.remote_ip4, sha1_key=key)
2334 self.vpp_session.add_vpp_config()
2335 self.test_session = BFDTestSession(
2336 self, self.pg0, AF_INET, sha1_key=key,
2337 bfd_key_id=self.vpp_session.bfd_key_id)
2338 bfd_session_up(self)
2339 # self.vapi.want_bfd_events(enable_disable=0)
2340 for dummy in range(self.test_session.detect_mult * 2):
2341 p = wait_for_bfd_packet(self)
2342 self.assert_equal(p[BFD].state, BFDState.up, BFDState)
2343 self.test_session.inc_seq_num()
2344 self.test_session.send_packet()
2345 self.vpp_session.deactivate_auth()
2346 self.test_session.bfd_key_id = None
2347 self.test_session.sha1_key = None
2348 for dummy in range(self.test_session.detect_mult * 2):
2349 p = wait_for_bfd_packet(self)
2350 self.assert_equal(p[BFD].state, BFDState.up, BFDState)
2351 self.test_session.inc_seq_num()
2352 self.test_session.send_packet()
2353 self.assert_equal(self.vpp_session.state, BFDState.up, BFDState)
2354 self.assert_equal(len(self.vapi.collect_events()), 0,
2355 "number of bfd events")
2357 def test_auth_change_key_immediate(self):
2358 """ change auth key without disturbing session state (immediate) """
2359 key1 = self.factory.create_random_key(self)
2360 key1.add_vpp_config()
2361 key2 = self.factory.create_random_key(self)
2362 key2.add_vpp_config()
2363 self.vpp_session = VppBFDUDPSession(self, self.pg0,
2364 self.pg0.remote_ip4, sha1_key=key1)
2365 self.vpp_session.add_vpp_config()
2366 self.test_session = BFDTestSession(
2367 self, self.pg0, AF_INET, sha1_key=key1,
2368 bfd_key_id=self.vpp_session.bfd_key_id)
2369 bfd_session_up(self)
2370 for dummy in range(self.test_session.detect_mult * 2):
2371 p = wait_for_bfd_packet(self)
2372 self.assert_equal(p[BFD].state, BFDState.up, BFDState)
2373 self.test_session.send_packet()
2374 self.vpp_session.activate_auth(key2)
2375 self.test_session.bfd_key_id = self.vpp_session.bfd_key_id
2376 self.test_session.sha1_key = key2
2377 for dummy in range(self.test_session.detect_mult * 2):
2378 p = wait_for_bfd_packet(self)
2379 self.assert_equal(p[BFD].state, BFDState.up, BFDState)
2380 self.test_session.send_packet()
2381 self.assert_equal(self.vpp_session.state, BFDState.up, BFDState)
2382 self.assert_equal(len(self.vapi.collect_events()), 0,
2383 "number of bfd events")
2385 def test_auth_on_delayed(self):
2386 """ turn auth on without disturbing session state (delayed) """
2387 key = self.factory.create_random_key(self)
2388 key.add_vpp_config()
2389 self.vpp_session = VppBFDUDPSession(self, self.pg0,
2390 self.pg0.remote_ip4)
2391 self.vpp_session.add_vpp_config()
2392 self.test_session = BFDTestSession(self, self.pg0, AF_INET)
2393 bfd_session_up(self)
2394 for dummy in range(self.test_session.detect_mult * 2):
2395 wait_for_bfd_packet(self)
2396 self.test_session.send_packet()
2397 self.vpp_session.activate_auth(key, delayed=True)
2398 for dummy in range(self.test_session.detect_mult * 2):
2399 p = wait_for_bfd_packet(self)
2400 self.assert_equal(p[BFD].state, BFDState.up, BFDState)
2401 self.test_session.send_packet()
2402 self.test_session.bfd_key_id = self.vpp_session.bfd_key_id
2403 self.test_session.sha1_key = key
2404 self.test_session.send_packet()
2405 for dummy in range(self.test_session.detect_mult * 2):
2406 p = wait_for_bfd_packet(self)
2407 self.assert_equal(p[BFD].state, BFDState.up, BFDState)
2408 self.test_session.send_packet()
2409 self.assert_equal(self.vpp_session.state, BFDState.up, BFDState)
2410 self.assert_equal(len(self.vapi.collect_events()), 0,
2411 "number of bfd events")
2413 def test_auth_off_delayed(self):
2414 """ turn auth off without disturbing session state (delayed) """
2415 key = self.factory.create_random_key(self)
2416 key.add_vpp_config()
2417 self.vpp_session = VppBFDUDPSession(self, self.pg0,
2418 self.pg0.remote_ip4, sha1_key=key)
2419 self.vpp_session.add_vpp_config()
2420 self.test_session = BFDTestSession(
2421 self, self.pg0, AF_INET, sha1_key=key,
2422 bfd_key_id=self.vpp_session.bfd_key_id)
2423 bfd_session_up(self)
2424 for dummy in range(self.test_session.detect_mult * 2):
2425 p = wait_for_bfd_packet(self)
2426 self.assert_equal(p[BFD].state, BFDState.up, BFDState)
2427 self.test_session.send_packet()
2428 self.vpp_session.deactivate_auth(delayed=True)
2429 for dummy in range(self.test_session.detect_mult * 2):
2430 p = wait_for_bfd_packet(self)
2431 self.assert_equal(p[BFD].state, BFDState.up, BFDState)
2432 self.test_session.send_packet()
2433 self.test_session.bfd_key_id = None
2434 self.test_session.sha1_key = None
2435 self.test_session.send_packet()
2436 for dummy in range(self.test_session.detect_mult * 2):
2437 p = wait_for_bfd_packet(self)
2438 self.assert_equal(p[BFD].state, BFDState.up, BFDState)
2439 self.test_session.send_packet()
2440 self.assert_equal(self.vpp_session.state, BFDState.up, BFDState)
2441 self.assert_equal(len(self.vapi.collect_events()), 0,
2442 "number of bfd events")
2444 def test_auth_change_key_delayed(self):
2445 """ change auth key without disturbing session state (delayed) """
2446 key1 = self.factory.create_random_key(self)
2447 key1.add_vpp_config()
2448 key2 = self.factory.create_random_key(self)
2449 key2.add_vpp_config()
2450 self.vpp_session = VppBFDUDPSession(self, self.pg0,
2451 self.pg0.remote_ip4, sha1_key=key1)
2452 self.vpp_session.add_vpp_config()
2453 self.vpp_session.admin_up()
2454 self.test_session = BFDTestSession(
2455 self, self.pg0, AF_INET, sha1_key=key1,
2456 bfd_key_id=self.vpp_session.bfd_key_id)
2457 bfd_session_up(self)
2458 for dummy in range(self.test_session.detect_mult * 2):
2459 p = wait_for_bfd_packet(self)
2460 self.assert_equal(p[BFD].state, BFDState.up, BFDState)
2461 self.test_session.send_packet()
2462 self.vpp_session.activate_auth(key2, delayed=True)
2463 for dummy in range(self.test_session.detect_mult * 2):
2464 p = wait_for_bfd_packet(self)
2465 self.assert_equal(p[BFD].state, BFDState.up, BFDState)
2466 self.test_session.send_packet()
2467 self.test_session.bfd_key_id = self.vpp_session.bfd_key_id
2468 self.test_session.sha1_key = key2
2469 self.test_session.send_packet()
2470 for dummy in range(self.test_session.detect_mult * 2):
2471 p = wait_for_bfd_packet(self)
2472 self.assert_equal(p[BFD].state, BFDState.up, BFDState)
2473 self.test_session.send_packet()
2474 self.assert_equal(self.vpp_session.state, BFDState.up, BFDState)
2475 self.assert_equal(len(self.vapi.collect_events()), 0,
2476 "number of bfd events")
2480 class BFDCLITestCase(VppTestCase):
2481 """Bidirectional Forwarding Detection (BFD) (CLI) """
2485 def setUpClass(cls):
2486 super(BFDCLITestCase, cls).setUpClass()
2487 cls.vapi.cli("set log class bfd level debug")
2489 cls.create_pg_interfaces((0,))
2490 cls.pg0.config_ip4()
2491 cls.pg0.config_ip6()
2492 cls.pg0.resolve_arp()
2493 cls.pg0.resolve_ndp()
2496 super(BFDCLITestCase, cls).tearDownClass()
2500 def tearDownClass(cls):
2501 super(BFDCLITestCase, cls).tearDownClass()
2504 super(BFDCLITestCase, self).setUp()
2505 self.factory = AuthKeyFactory()
2506 self.pg0.enable_capture()
2510 self.vapi.want_bfd_events(enable_disable=False)
2511 except UnexpectedApiReturnValueError:
2512 # some tests aren't subscribed, so this is not an issue
2514 self.vapi.collect_events() # clear the event queue
2515 super(BFDCLITestCase, self).tearDown()
2517 def cli_verify_no_response(self, cli):
2518 """ execute a CLI, asserting that the response is empty """
2519 self.assert_equal(self.vapi.cli(cli),
2521 "CLI command response")
2523 def cli_verify_response(self, cli, expected):
2524 """ execute a CLI, asserting that the response matches expectation """
2526 reply = self.vapi.cli(cli)
2527 except CliFailedCommandError as cli_error:
2528 reply = str(cli_error)
2529 self.assert_equal(reply.strip(),
2531 "CLI command response")
2533 def test_show(self):
2534 """ show commands """
2535 k1 = self.factory.create_random_key(self)
2537 k2 = self.factory.create_random_key(
2538 self, auth_type=BFDAuthType.meticulous_keyed_sha1)
2540 s1 = VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip4)
2542 s2 = VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip6, af=AF_INET6,
2545 self.logger.info(self.vapi.ppcli("show bfd keys"))
2546 self.logger.info(self.vapi.ppcli("show bfd sessions"))
2547 self.logger.info(self.vapi.ppcli("show bfd"))
2549 def test_set_del_sha1_key(self):
2550 """ set/delete SHA1 auth key """
2551 k = self.factory.create_random_key(self)
2552 self.registry.register(k, self.logger)
2553 self.cli_verify_no_response(
2554 "bfd key set conf-key-id %s type keyed-sha1 secret %s" %
2556 "".join("{:02x}".format(scapy.compat.orb(c)) for c in k.key)))
2557 self.assertTrue(k.query_vpp_config())
2558 self.vpp_session = VppBFDUDPSession(
2559 self, self.pg0, self.pg0.remote_ip4, sha1_key=k)
2560 self.vpp_session.add_vpp_config()
2561 self.test_session = \
2562 BFDTestSession(self, self.pg0, AF_INET, sha1_key=k,
2563 bfd_key_id=self.vpp_session.bfd_key_id)
2564 self.vapi.want_bfd_events()
2565 bfd_session_up(self)
2566 bfd_session_down(self)
2567 # try to replace the secret for the key - should fail because the key
2569 k2 = self.factory.create_random_key(self)
2570 self.cli_verify_response(
2571 "bfd key set conf-key-id %s type keyed-sha1 secret %s" %
2573 "".join("{:02x}".format(scapy.compat.orb(c)) for c in k2.key)),
2574 "bfd key set: `bfd_auth_set_key' API call failed, "
2575 "rv=-103:BFD object in use")
2576 # manipulating the session using old secret should still work
2577 bfd_session_up(self)
2578 bfd_session_down(self)
2579 self.vpp_session.remove_vpp_config()
2580 self.cli_verify_no_response(
2581 "bfd key del conf-key-id %s" % k.conf_key_id)
2582 self.assertFalse(k.query_vpp_config())
2584 def test_set_del_meticulous_sha1_key(self):
2585 """ set/delete meticulous SHA1 auth key """
2586 k = self.factory.create_random_key(
2587 self, auth_type=BFDAuthType.meticulous_keyed_sha1)
2588 self.registry.register(k, self.logger)
2589 self.cli_verify_no_response(
2590 "bfd key set conf-key-id %s type meticulous-keyed-sha1 secret %s" %
2592 "".join("{:02x}".format(scapy.compat.orb(c)) for c in k.key)))
2593 self.assertTrue(k.query_vpp_config())
2594 self.vpp_session = VppBFDUDPSession(self, self.pg0,
2595 self.pg0.remote_ip6, af=AF_INET6,
2597 self.vpp_session.add_vpp_config()
2598 self.vpp_session.admin_up()
2599 self.test_session = \
2600 BFDTestSession(self, self.pg0, AF_INET6, sha1_key=k,
2601 bfd_key_id=self.vpp_session.bfd_key_id)
2602 self.vapi.want_bfd_events()
2603 bfd_session_up(self)
2604 bfd_session_down(self)
2605 # try to replace the secret for the key - should fail because the key
2607 k2 = self.factory.create_random_key(self)
2608 self.cli_verify_response(
2609 "bfd key set conf-key-id %s type keyed-sha1 secret %s" %
2611 "".join("{:02x}".format(scapy.compat.orb(c)) for c in k2.key)),
2612 "bfd key set: `bfd_auth_set_key' API call failed, "
2613 "rv=-103:BFD object in use")
2614 # manipulating the session using old secret should still work
2615 bfd_session_up(self)
2616 bfd_session_down(self)
2617 self.vpp_session.remove_vpp_config()
2618 self.cli_verify_no_response(
2619 "bfd key del conf-key-id %s" % k.conf_key_id)
2620 self.assertFalse(k.query_vpp_config())
2622 def test_add_mod_del_bfd_udp(self):
2623 """ create/modify/delete IPv4 BFD UDP session """
2624 vpp_session = VppBFDUDPSession(
2625 self, self.pg0, self.pg0.remote_ip4)
2626 self.registry.register(vpp_session, self.logger)
2627 cli_add_cmd = "bfd udp session add interface %s local-addr %s " \
2628 "peer-addr %s desired-min-tx %s required-min-rx %s "\
2629 "detect-mult %s" % (self.pg0.name, self.pg0.local_ip4,
2630 self.pg0.remote_ip4,
2631 vpp_session.desired_min_tx,
2632 vpp_session.required_min_rx,
2633 vpp_session.detect_mult)
2634 self.cli_verify_no_response(cli_add_cmd)
2635 # 2nd add should fail
2636 self.cli_verify_response(
2638 "bfd udp session add: `bfd_add_add_session' API call"
2639 " failed, rv=-101:Duplicate BFD object")
2640 verify_bfd_session_config(self, vpp_session)
2641 mod_session = VppBFDUDPSession(
2642 self, self.pg0, self.pg0.remote_ip4,
2643 required_min_rx=2 * vpp_session.required_min_rx,
2644 desired_min_tx=3 * vpp_session.desired_min_tx,
2645 detect_mult=4 * vpp_session.detect_mult)
2646 self.cli_verify_no_response(
2647 "bfd udp session mod interface %s local-addr %s peer-addr %s "
2648 "desired-min-tx %s required-min-rx %s detect-mult %s" %
2649 (self.pg0.name, self.pg0.local_ip4, self.pg0.remote_ip4,
2650 mod_session.desired_min_tx, mod_session.required_min_rx,
2651 mod_session.detect_mult))
2652 verify_bfd_session_config(self, mod_session)
2653 cli_del_cmd = "bfd udp session del interface %s local-addr %s "\
2654 "peer-addr %s" % (self.pg0.name,
2655 self.pg0.local_ip4, self.pg0.remote_ip4)
2656 self.cli_verify_no_response(cli_del_cmd)
2657 # 2nd del is expected to fail
2658 self.cli_verify_response(
2659 cli_del_cmd, "bfd udp session del: `bfd_udp_del_session' API call"
2660 " failed, rv=-102:No such BFD object")
2661 self.assertFalse(vpp_session.query_vpp_config())
2663 def test_add_mod_del_bfd_udp6(self):
2664 """ create/modify/delete IPv6 BFD UDP session """
2665 vpp_session = VppBFDUDPSession(
2666 self, self.pg0, self.pg0.remote_ip6, af=AF_INET6)
2667 self.registry.register(vpp_session, self.logger)
2668 cli_add_cmd = "bfd udp session add interface %s local-addr %s " \
2669 "peer-addr %s desired-min-tx %s required-min-rx %s "\
2670 "detect-mult %s" % (self.pg0.name, self.pg0.local_ip6,
2671 self.pg0.remote_ip6,
2672 vpp_session.desired_min_tx,
2673 vpp_session.required_min_rx,
2674 vpp_session.detect_mult)
2675 self.cli_verify_no_response(cli_add_cmd)
2676 # 2nd add should fail
2677 self.cli_verify_response(
2679 "bfd udp session add: `bfd_add_add_session' API call"
2680 " failed, rv=-101:Duplicate BFD object")
2681 verify_bfd_session_config(self, vpp_session)
2682 mod_session = VppBFDUDPSession(
2683 self, self.pg0, self.pg0.remote_ip6, af=AF_INET6,
2684 required_min_rx=2 * vpp_session.required_min_rx,
2685 desired_min_tx=3 * vpp_session.desired_min_tx,
2686 detect_mult=4 * vpp_session.detect_mult)
2687 self.cli_verify_no_response(
2688 "bfd udp session mod interface %s local-addr %s peer-addr %s "
2689 "desired-min-tx %s required-min-rx %s detect-mult %s" %
2690 (self.pg0.name, self.pg0.local_ip6, self.pg0.remote_ip6,
2691 mod_session.desired_min_tx,
2692 mod_session.required_min_rx, mod_session.detect_mult))
2693 verify_bfd_session_config(self, mod_session)
2694 cli_del_cmd = "bfd udp session del interface %s local-addr %s "\
2695 "peer-addr %s" % (self.pg0.name,
2696 self.pg0.local_ip6, self.pg0.remote_ip6)
2697 self.cli_verify_no_response(cli_del_cmd)
2698 # 2nd del is expected to fail
2699 self.cli_verify_response(
2701 "bfd udp session del: `bfd_udp_del_session' API call"
2702 " failed, rv=-102:No such BFD object")
2703 self.assertFalse(vpp_session.query_vpp_config())
2705 def test_add_mod_del_bfd_udp_auth(self):
2706 """ create/modify/delete IPv4 BFD UDP session (authenticated) """
2707 key = self.factory.create_random_key(self)
2708 key.add_vpp_config()
2709 vpp_session = VppBFDUDPSession(
2710 self, self.pg0, self.pg0.remote_ip4, sha1_key=key)
2711 self.registry.register(vpp_session, self.logger)
2712 cli_add_cmd = "bfd udp session add interface %s local-addr %s " \
2713 "peer-addr %s desired-min-tx %s required-min-rx %s "\
2714 "detect-mult %s conf-key-id %s bfd-key-id %s"\
2715 % (self.pg0.name, self.pg0.local_ip4, self.pg0.remote_ip4,
2716 vpp_session.desired_min_tx, vpp_session.required_min_rx,
2717 vpp_session.detect_mult, key.conf_key_id,
2718 vpp_session.bfd_key_id)
2719 self.cli_verify_no_response(cli_add_cmd)
2720 # 2nd add should fail
2721 self.cli_verify_response(
2723 "bfd udp session add: `bfd_add_add_session' API call"
2724 " failed, rv=-101:Duplicate BFD object")
2725 verify_bfd_session_config(self, vpp_session)
2726 mod_session = VppBFDUDPSession(
2727 self, self.pg0, self.pg0.remote_ip4, sha1_key=key,
2728 bfd_key_id=vpp_session.bfd_key_id,
2729 required_min_rx=2 * vpp_session.required_min_rx,
2730 desired_min_tx=3 * vpp_session.desired_min_tx,
2731 detect_mult=4 * vpp_session.detect_mult)
2732 self.cli_verify_no_response(
2733 "bfd udp session mod interface %s local-addr %s peer-addr %s "
2734 "desired-min-tx %s required-min-rx %s detect-mult %s" %
2735 (self.pg0.name, self.pg0.local_ip4, self.pg0.remote_ip4,
2736 mod_session.desired_min_tx,
2737 mod_session.required_min_rx, mod_session.detect_mult))
2738 verify_bfd_session_config(self, mod_session)
2739 cli_del_cmd = "bfd udp session del interface %s local-addr %s "\
2740 "peer-addr %s" % (self.pg0.name,
2741 self.pg0.local_ip4, self.pg0.remote_ip4)
2742 self.cli_verify_no_response(cli_del_cmd)
2743 # 2nd del is expected to fail
2744 self.cli_verify_response(
2746 "bfd udp session del: `bfd_udp_del_session' API call"
2747 " failed, rv=-102:No such BFD object")
2748 self.assertFalse(vpp_session.query_vpp_config())
2750 def test_add_mod_del_bfd_udp6_auth(self):
2751 """ create/modify/delete IPv6 BFD UDP session (authenticated) """
2752 key = self.factory.create_random_key(
2753 self, auth_type=BFDAuthType.meticulous_keyed_sha1)
2754 key.add_vpp_config()
2755 vpp_session = VppBFDUDPSession(
2756 self, self.pg0, self.pg0.remote_ip6, af=AF_INET6, sha1_key=key)
2757 self.registry.register(vpp_session, self.logger)
2758 cli_add_cmd = "bfd udp session add interface %s local-addr %s " \
2759 "peer-addr %s desired-min-tx %s required-min-rx %s "\
2760 "detect-mult %s conf-key-id %s bfd-key-id %s" \
2761 % (self.pg0.name, self.pg0.local_ip6, self.pg0.remote_ip6,
2762 vpp_session.desired_min_tx, vpp_session.required_min_rx,
2763 vpp_session.detect_mult, key.conf_key_id,
2764 vpp_session.bfd_key_id)
2765 self.cli_verify_no_response(cli_add_cmd)
2766 # 2nd add should fail
2767 self.cli_verify_response(
2769 "bfd udp session add: `bfd_add_add_session' API call"
2770 " failed, rv=-101:Duplicate BFD object")
2771 verify_bfd_session_config(self, vpp_session)
2772 mod_session = VppBFDUDPSession(
2773 self, self.pg0, self.pg0.remote_ip6, af=AF_INET6, sha1_key=key,
2774 bfd_key_id=vpp_session.bfd_key_id,
2775 required_min_rx=2 * vpp_session.required_min_rx,
2776 desired_min_tx=3 * vpp_session.desired_min_tx,
2777 detect_mult=4 * vpp_session.detect_mult)
2778 self.cli_verify_no_response(
2779 "bfd udp session mod interface %s local-addr %s peer-addr %s "
2780 "desired-min-tx %s required-min-rx %s detect-mult %s" %
2781 (self.pg0.name, self.pg0.local_ip6, self.pg0.remote_ip6,
2782 mod_session.desired_min_tx,
2783 mod_session.required_min_rx, mod_session.detect_mult))
2784 verify_bfd_session_config(self, mod_session)
2785 cli_del_cmd = "bfd udp session del interface %s local-addr %s "\
2786 "peer-addr %s" % (self.pg0.name,
2787 self.pg0.local_ip6, self.pg0.remote_ip6)
2788 self.cli_verify_no_response(cli_del_cmd)
2789 # 2nd del is expected to fail
2790 self.cli_verify_response(
2792 "bfd udp session del: `bfd_udp_del_session' API call"
2793 " failed, rv=-102:No such BFD object")
2794 self.assertFalse(vpp_session.query_vpp_config())
2796 def test_auth_on_off(self):
2797 """ turn authentication on and off """
2798 key = self.factory.create_random_key(
2799 self, auth_type=BFDAuthType.meticulous_keyed_sha1)
2800 key.add_vpp_config()
2801 session = VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip4)
2802 auth_session = VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip4,
2804 session.add_vpp_config()
2806 "bfd udp session auth activate interface %s local-addr %s "\
2807 "peer-addr %s conf-key-id %s bfd-key-id %s"\
2808 % (self.pg0.name, self.pg0.local_ip4, self.pg0.remote_ip4,
2809 key.conf_key_id, auth_session.bfd_key_id)
2810 self.cli_verify_no_response(cli_activate)
2811 verify_bfd_session_config(self, auth_session)
2812 self.cli_verify_no_response(cli_activate)
2813 verify_bfd_session_config(self, auth_session)
2815 "bfd udp session auth deactivate interface %s local-addr %s "\
2817 % (self.pg0.name, self.pg0.local_ip4, self.pg0.remote_ip4)
2818 self.cli_verify_no_response(cli_deactivate)
2819 verify_bfd_session_config(self, session)
2820 self.cli_verify_no_response(cli_deactivate)
2821 verify_bfd_session_config(self, session)
2823 def test_auth_on_off_delayed(self):
2824 """ turn authentication on and off (delayed) """
2825 key = self.factory.create_random_key(
2826 self, auth_type=BFDAuthType.meticulous_keyed_sha1)
2827 key.add_vpp_config()
2828 session = VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip4)
2829 auth_session = VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip4,
2831 session.add_vpp_config()
2833 "bfd udp session auth activate interface %s local-addr %s "\
2834 "peer-addr %s conf-key-id %s bfd-key-id %s delayed yes"\
2835 % (self.pg0.name, self.pg0.local_ip4, self.pg0.remote_ip4,
2836 key.conf_key_id, auth_session.bfd_key_id)
2837 self.cli_verify_no_response(cli_activate)
2838 verify_bfd_session_config(self, auth_session)
2839 self.cli_verify_no_response(cli_activate)
2840 verify_bfd_session_config(self, auth_session)
2842 "bfd udp session auth deactivate interface %s local-addr %s "\
2843 "peer-addr %s delayed yes"\
2844 % (self.pg0.name, self.pg0.local_ip4, self.pg0.remote_ip4)
2845 self.cli_verify_no_response(cli_deactivate)
2846 verify_bfd_session_config(self, session)
2847 self.cli_verify_no_response(cli_deactivate)
2848 verify_bfd_session_config(self, session)
2850 def test_admin_up_down(self):
2851 """ put session admin-up and admin-down """
2852 session = VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip4)
2853 session.add_vpp_config()
2855 "bfd udp session set-flags admin down interface %s local-addr %s "\
2857 % (self.pg0.name, self.pg0.local_ip4, self.pg0.remote_ip4)
2859 "bfd udp session set-flags admin up interface %s local-addr %s "\
2861 % (self.pg0.name, self.pg0.local_ip4, self.pg0.remote_ip4)
2862 self.cli_verify_no_response(cli_down)
2863 verify_bfd_session_config(self, session, state=BFDState.admin_down)
2864 self.cli_verify_no_response(cli_up)
2865 verify_bfd_session_config(self, session, state=BFDState.down)
2867 def test_set_del_udp_echo_source(self):
2868 """ set/del udp echo source """
2869 self.create_loopback_interfaces(1)
2870 self.loopback0 = self.lo_interfaces[0]
2871 self.loopback0.admin_up()
2872 self.cli_verify_response("show bfd echo-source",
2873 "UDP echo source is not set.")
2874 cli_set = "bfd udp echo-source set interface %s" % self.loopback0.name
2875 self.cli_verify_no_response(cli_set)
2876 self.cli_verify_response("show bfd echo-source",
2877 "UDP echo source is: %s\n"
2878 "IPv4 address usable as echo source: none\n"
2879 "IPv6 address usable as echo source: none" %
2880 self.loopback0.name)
2881 self.loopback0.config_ip4()
2882 echo_ip4 = str(ipaddress.IPv4Address(int(ipaddress.IPv4Address(
2883 self.loopback0.local_ip4)) ^ 1))
2884 self.cli_verify_response("show bfd echo-source",
2885 "UDP echo source is: %s\n"
2886 "IPv4 address usable as echo source: %s\n"
2887 "IPv6 address usable as echo source: none" %
2888 (self.loopback0.name, echo_ip4))
2889 echo_ip6 = str(ipaddress.IPv6Address(int(ipaddress.IPv6Address(
2890 self.loopback0.local_ip6)) ^ 1))
2891 self.loopback0.config_ip6()
2892 self.cli_verify_response("show bfd echo-source",
2893 "UDP echo source is: %s\n"
2894 "IPv4 address usable as echo source: %s\n"
2895 "IPv6 address usable as echo source: %s" %
2896 (self.loopback0.name, echo_ip4, echo_ip6))
2897 cli_del = "bfd udp echo-source del"
2898 self.cli_verify_no_response(cli_del)
2899 self.cli_verify_response("show bfd echo-source",
2900 "UDP echo source is not set.")
2903 if __name__ == '__main__':
2904 unittest.main(testRunner=VppTestRunner)