4 from __future__ import division
9 from random import randint, shuffle, getrandbits
10 from socket import AF_INET, AF_INET6
11 from scapy.packet import Raw
12 from scapy.layers.l2 import Ether
13 from scapy.layers.inet import UDP, IP
14 from scapy.layers.inet6 import IPv6
15 from bfd import VppBFDAuthKey, BFD, BFDAuthType, VppBFDUDPSession, \
16 BFDDiagCode, BFDState, BFD_vpp_echo
17 from framework import VppTestCase, VppTestRunner
18 from vpp_pg_interface import CaptureTimeoutError
24 class AuthKeyFactory(object):
25 """Factory class for creating auth keys with unique conf key ID"""
28 self._conf_key_ids = {}
30 def create_random_key(self, test, auth_type=BFDAuthType.keyed_sha1):
31 """ create a random key with unique conf key id """
32 conf_key_id = randint(0, 0xFFFFFFFF)
33 while conf_key_id in self._conf_key_ids:
34 conf_key_id = randint(0, 0xFFFFFFFF)
35 self._conf_key_ids[conf_key_id] = 1
36 key = str(bytearray([randint(0, 255) for _ in range(randint(1, 20))]))
37 return VppBFDAuthKey(test=test, auth_type=auth_type,
38 conf_key_id=conf_key_id, key=key)
41 class BFDAPITestCase(VppTestCase):
42 """Bidirectional Forwarding Detection (BFD) - API"""
49 super(BFDAPITestCase, cls).setUpClass()
52 cls.create_pg_interfaces(range(2))
53 for i in cls.pg_interfaces:
59 super(BFDAPITestCase, cls).tearDownClass()
63 super(BFDAPITestCase, self).setUp()
64 self.factory = AuthKeyFactory()
66 def test_add_bfd(self):
67 """ create a BFD session """
68 session = VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip4)
69 session.add_vpp_config()
70 self.logger.debug("Session state is %s", session.state)
71 session.remove_vpp_config()
72 session.add_vpp_config()
73 self.logger.debug("Session state is %s", session.state)
74 session.remove_vpp_config()
76 def test_double_add(self):
77 """ create the same BFD session twice (negative case) """
78 session = VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip4)
79 session.add_vpp_config()
81 with self.vapi.expect_negative_api_retval():
82 session.add_vpp_config()
84 session.remove_vpp_config()
86 def test_add_bfd6(self):
87 """ create IPv6 BFD session """
88 session = VppBFDUDPSession(
89 self, self.pg0, self.pg0.remote_ip6, af=AF_INET6)
90 session.add_vpp_config()
91 self.logger.debug("Session state is %s", session.state)
92 session.remove_vpp_config()
93 session.add_vpp_config()
94 self.logger.debug("Session state is %s", session.state)
95 session.remove_vpp_config()
97 def test_mod_bfd(self):
98 """ modify BFD session parameters """
99 session = VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip4,
100 desired_min_tx=50000,
101 required_min_rx=10000,
103 session.add_vpp_config()
104 s = session.get_bfd_udp_session_dump_entry()
105 self.assert_equal(session.desired_min_tx,
107 "desired min transmit interval")
108 self.assert_equal(session.required_min_rx,
110 "required min receive interval")
111 self.assert_equal(session.detect_mult, s.detect_mult, "detect mult")
112 session.modify_parameters(desired_min_tx=session.desired_min_tx * 2,
113 required_min_rx=session.required_min_rx * 2,
114 detect_mult=session.detect_mult * 2)
115 s = session.get_bfd_udp_session_dump_entry()
116 self.assert_equal(session.desired_min_tx,
118 "desired min transmit interval")
119 self.assert_equal(session.required_min_rx,
121 "required min receive interval")
122 self.assert_equal(session.detect_mult, s.detect_mult, "detect mult")
124 def test_add_sha1_keys(self):
125 """ add SHA1 keys """
127 keys = [self.factory.create_random_key(
128 self) for i in range(0, key_count)]
130 self.assertFalse(key.query_vpp_config())
134 self.assertTrue(key.query_vpp_config())
136 indexes = range(key_count)
141 key.remove_vpp_config()
143 for j in range(key_count):
146 self.assertFalse(key.query_vpp_config())
148 self.assertTrue(key.query_vpp_config())
149 # should be removed now
151 self.assertFalse(key.query_vpp_config())
152 # add back and remove again
156 self.assertTrue(key.query_vpp_config())
158 key.remove_vpp_config()
160 self.assertFalse(key.query_vpp_config())
162 def test_add_bfd_sha1(self):
163 """ create a BFD session (SHA1) """
164 key = self.factory.create_random_key(self)
166 session = VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip4,
168 session.add_vpp_config()
169 self.logger.debug("Session state is %s", session.state)
170 session.remove_vpp_config()
171 session.add_vpp_config()
172 self.logger.debug("Session state is %s", session.state)
173 session.remove_vpp_config()
175 def test_double_add_sha1(self):
176 """ create the same BFD session twice (negative case) (SHA1) """
177 key = self.factory.create_random_key(self)
179 session = VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip4,
181 session.add_vpp_config()
182 with self.assertRaises(Exception):
183 session.add_vpp_config()
185 def test_add_auth_nonexistent_key(self):
186 """ create BFD session using non-existent SHA1 (negative case) """
187 session = VppBFDUDPSession(
188 self, self.pg0, self.pg0.remote_ip4,
189 sha1_key=self.factory.create_random_key(self))
190 with self.assertRaises(Exception):
191 session.add_vpp_config()
193 def test_shared_sha1_key(self):
194 """ share single SHA1 key between multiple BFD sessions """
195 key = self.factory.create_random_key(self)
198 VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip4,
200 VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip6,
201 sha1_key=key, af=AF_INET6),
202 VppBFDUDPSession(self, self.pg1, self.pg1.remote_ip4,
204 VppBFDUDPSession(self, self.pg1, self.pg1.remote_ip6,
205 sha1_key=key, af=AF_INET6)]
210 e = key.get_bfd_auth_keys_dump_entry()
211 self.assert_equal(e.use_count, len(sessions) - removed,
212 "Use count for shared key")
213 s.remove_vpp_config()
215 e = key.get_bfd_auth_keys_dump_entry()
216 self.assert_equal(e.use_count, len(sessions) - removed,
217 "Use count for shared key")
219 def test_activate_auth(self):
220 """ activate SHA1 authentication """
221 key = self.factory.create_random_key(self)
223 session = VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip4)
224 session.add_vpp_config()
225 session.activate_auth(key)
227 def test_deactivate_auth(self):
228 """ deactivate SHA1 authentication """
229 key = self.factory.create_random_key(self)
231 session = VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip4)
232 session.add_vpp_config()
233 session.activate_auth(key)
234 session.deactivate_auth()
236 def test_change_key(self):
237 """ change SHA1 key """
238 key1 = self.factory.create_random_key(self)
239 key2 = self.factory.create_random_key(self)
240 while key2.conf_key_id == key1.conf_key_id:
241 key2 = self.factory.create_random_key(self)
242 key1.add_vpp_config()
243 key2.add_vpp_config()
244 session = VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip4,
246 session.add_vpp_config()
247 session.activate_auth(key2)
250 class BFDTestSession(object):
251 """ BFD session as seen from test framework side """
253 def __init__(self, test, interface, af, detect_mult=3, sha1_key=None,
254 bfd_key_id=None, our_seq_number=None):
257 self.sha1_key = sha1_key
258 self.bfd_key_id = bfd_key_id
259 self.interface = interface
260 self.udp_sport = randint(49152, 65535)
261 if our_seq_number is None:
262 self.our_seq_number = randint(0, 40000000)
264 self.our_seq_number = our_seq_number
265 self.vpp_seq_number = None
266 self.my_discriminator = 0
267 self.desired_min_tx = 100000
268 self.required_min_rx = 100000
269 self.required_min_echo_rx = None
270 self.detect_mult = detect_mult
271 self.diag = BFDDiagCode.no_diagnostic
272 self.your_discriminator = None
273 self.state = BFDState.down
274 self.auth_type = BFDAuthType.no_auth
276 def inc_seq_num(self):
277 """ increment sequence number, wrapping if needed """
278 if self.our_seq_number == 0xFFFFFFFF:
279 self.our_seq_number = 0
281 self.our_seq_number += 1
283 def update(self, my_discriminator=None, your_discriminator=None,
284 desired_min_tx=None, required_min_rx=None,
285 required_min_echo_rx=None, detect_mult=None,
286 diag=None, state=None, auth_type=None):
287 """ update BFD parameters associated with session """
288 if my_discriminator is not None:
289 self.my_discriminator = my_discriminator
290 if your_discriminator is not None:
291 self.your_discriminator = your_discriminator
292 if required_min_rx is not None:
293 self.required_min_rx = required_min_rx
294 if required_min_echo_rx is not None:
295 self.required_min_echo_rx = required_min_echo_rx
296 if desired_min_tx is not None:
297 self.desired_min_tx = desired_min_tx
298 if detect_mult is not None:
299 self.detect_mult = detect_mult
302 if state is not None:
304 if auth_type is not None:
305 self.auth_type = auth_type
307 def fill_packet_fields(self, packet):
308 """ set packet fields with known values in packet """
310 if self.my_discriminator:
311 self.test.logger.debug("BFD: setting packet.my_discriminator=%s",
312 self.my_discriminator)
313 bfd.my_discriminator = self.my_discriminator
314 if self.your_discriminator:
315 self.test.logger.debug("BFD: setting packet.your_discriminator=%s",
316 self.your_discriminator)
317 bfd.your_discriminator = self.your_discriminator
318 if self.required_min_rx:
319 self.test.logger.debug(
320 "BFD: setting packet.required_min_rx_interval=%s",
321 self.required_min_rx)
322 bfd.required_min_rx_interval = self.required_min_rx
323 if self.required_min_echo_rx:
324 self.test.logger.debug(
325 "BFD: setting packet.required_min_echo_rx=%s",
326 self.required_min_echo_rx)
327 bfd.required_min_echo_rx_interval = self.required_min_echo_rx
328 if self.desired_min_tx:
329 self.test.logger.debug(
330 "BFD: setting packet.desired_min_tx_interval=%s",
332 bfd.desired_min_tx_interval = self.desired_min_tx
334 self.test.logger.debug(
335 "BFD: setting packet.detect_mult=%s", self.detect_mult)
336 bfd.detect_mult = self.detect_mult
338 self.test.logger.debug("BFD: setting packet.diag=%s", self.diag)
341 self.test.logger.debug("BFD: setting packet.state=%s", self.state)
342 bfd.state = self.state
344 # this is used by a negative test-case
345 self.test.logger.debug("BFD: setting packet.auth_type=%s",
347 bfd.auth_type = self.auth_type
349 def create_packet(self):
350 """ create a BFD packet, reflecting the current state of session """
353 bfd.auth_type = self.sha1_key.auth_type
354 bfd.auth_len = BFD.sha1_auth_len
355 bfd.auth_key_id = self.bfd_key_id
356 bfd.auth_seq_num = self.our_seq_number
357 bfd.length = BFD.sha1_auth_len + BFD.bfd_pkt_len
360 if self.af == AF_INET6:
361 packet = (Ether(src=self.interface.remote_mac,
362 dst=self.interface.local_mac) /
363 IPv6(src=self.interface.remote_ip6,
364 dst=self.interface.local_ip6,
366 UDP(sport=self.udp_sport, dport=BFD.udp_dport) /
369 packet = (Ether(src=self.interface.remote_mac,
370 dst=self.interface.local_mac) /
371 IP(src=self.interface.remote_ip4,
372 dst=self.interface.local_ip4,
374 UDP(sport=self.udp_sport, dport=BFD.udp_dport) /
376 self.test.logger.debug("BFD: Creating packet")
377 self.fill_packet_fields(packet)
379 hash_material = str(packet[BFD])[:32] + self.sha1_key.key + \
380 "\0" * (20 - len(self.sha1_key.key))
381 self.test.logger.debug("BFD: Calculated SHA1 hash: %s" %
382 hashlib.sha1(hash_material).hexdigest())
383 packet[BFD].auth_key_hash = hashlib.sha1(hash_material).digest()
386 def send_packet(self, packet=None, interface=None):
387 """ send packet on interface, creating the packet if needed """
389 packet = self.create_packet()
390 if interface is None:
391 interface = self.test.pg0
392 self.test.logger.debug(ppp("Sending packet:", packet))
393 interface.add_stream(packet)
396 def verify_sha1_auth(self, packet):
397 """ Verify correctness of authentication in BFD layer. """
399 self.test.assert_equal(bfd.auth_len, 28, "Auth section length")
400 self.test.assert_equal(bfd.auth_type, self.sha1_key.auth_type,
402 self.test.assert_equal(bfd.auth_key_id, self.bfd_key_id, "Key ID")
403 self.test.assert_equal(bfd.auth_reserved, 0, "Reserved")
404 if self.vpp_seq_number is None:
405 self.vpp_seq_number = bfd.auth_seq_num
406 self.test.logger.debug("Received initial sequence number: %s" %
409 recvd_seq_num = bfd.auth_seq_num
410 self.test.logger.debug("Received followup sequence number: %s" %
412 if self.vpp_seq_number < 0xffffffff:
413 if self.sha1_key.auth_type == \
414 BFDAuthType.meticulous_keyed_sha1:
415 self.test.assert_equal(recvd_seq_num,
416 self.vpp_seq_number + 1,
417 "BFD sequence number")
419 self.test.assert_in_range(recvd_seq_num,
421 self.vpp_seq_number + 1,
422 "BFD sequence number")
424 if self.sha1_key.auth_type == \
425 BFDAuthType.meticulous_keyed_sha1:
426 self.test.assert_equal(recvd_seq_num, 0,
427 "BFD sequence number")
429 self.test.assertIn(recvd_seq_num, (self.vpp_seq_number, 0),
430 "BFD sequence number not one of "
431 "(%s, 0)" % self.vpp_seq_number)
432 self.vpp_seq_number = recvd_seq_num
433 # last 20 bytes represent the hash - so replace them with the key,
434 # pad the result with zeros and hash the result
435 hash_material = bfd.original[:-20] + self.sha1_key.key + \
436 "\0" * (20 - len(self.sha1_key.key))
437 expected_hash = hashlib.sha1(hash_material).hexdigest()
438 self.test.assert_equal(binascii.hexlify(bfd.auth_key_hash),
439 expected_hash, "Auth key hash")
441 def verify_bfd(self, packet):
442 """ Verify correctness of BFD layer. """
444 self.test.assert_equal(bfd.version, 1, "BFD version")
445 self.test.assert_equal(bfd.your_discriminator,
446 self.my_discriminator,
447 "BFD - your discriminator")
449 self.verify_sha1_auth(packet)
452 def bfd_session_up(test):
453 """ Bring BFD session up """
454 test.logger.info("BFD: Waiting for slow hello")
455 p = wait_for_bfd_packet(test, 2)
457 if hasattr(test, 'vpp_clock_offset'):
458 old_offset = test.vpp_clock_offset
459 test.vpp_clock_offset = time.time() - p.time
460 test.logger.debug("BFD: Calculated vpp clock offset: %s",
461 test.vpp_clock_offset)
463 test.assertAlmostEqual(
464 old_offset, test.vpp_clock_offset, delta=0.1,
465 msg="vpp clock offset not stable (new: %s, old: %s)" %
466 (test.vpp_clock_offset, old_offset))
467 test.logger.info("BFD: Sending Init")
468 test.test_session.update(my_discriminator=randint(0, 40000000),
469 your_discriminator=p[BFD].my_discriminator,
471 test.test_session.send_packet()
472 test.logger.info("BFD: Waiting for event")
473 e = test.vapi.wait_for_event(1, "bfd_udp_session_details")
474 verify_event(test, e, expected_state=BFDState.up)
475 test.logger.info("BFD: Session is Up")
476 test.test_session.update(state=BFDState.up)
477 test.test_session.send_packet()
478 test.assert_equal(test.vpp_session.state, BFDState.up, BFDState)
481 def bfd_session_down(test):
482 """ Bring BFD session down """
483 test.assert_equal(test.vpp_session.state, BFDState.up, BFDState)
484 test.test_session.update(state=BFDState.down)
485 test.test_session.send_packet()
486 test.logger.info("BFD: Waiting for event")
487 e = test.vapi.wait_for_event(1, "bfd_udp_session_details")
488 verify_event(test, e, expected_state=BFDState.down)
489 test.logger.info("BFD: Session is Down")
490 test.assert_equal(test.vpp_session.state, BFDState.down, BFDState)
493 def verify_ip(test, packet):
494 """ Verify correctness of IP layer. """
495 if test.vpp_session.af == AF_INET6:
497 local_ip = test.pg0.local_ip6
498 remote_ip = test.pg0.remote_ip6
499 test.assert_equal(ip.hlim, 255, "IPv6 hop limit")
502 local_ip = test.pg0.local_ip4
503 remote_ip = test.pg0.remote_ip4
504 test.assert_equal(ip.ttl, 255, "IPv4 TTL")
505 test.assert_equal(ip.src, local_ip, "IP source address")
506 test.assert_equal(ip.dst, remote_ip, "IP destination address")
509 def verify_udp(test, packet):
510 """ Verify correctness of UDP layer. """
512 test.assert_equal(udp.dport, BFD.udp_dport, "UDP destination port")
513 test.assert_in_range(udp.sport, BFD.udp_sport_min, BFD.udp_sport_max,
517 def verify_event(test, event, expected_state):
518 """ Verify correctness of event values. """
520 test.logger.debug("BFD: Event: %s" % repr(e))
521 test.assert_equal(e.sw_if_index,
522 test.vpp_session.interface.sw_if_index,
523 "BFD interface index")
525 if test.vpp_session.af == AF_INET6:
527 test.assert_equal(e.is_ipv6, is_ipv6, "is_ipv6")
528 if test.vpp_session.af == AF_INET:
529 test.assert_equal(e.local_addr[:4], test.vpp_session.local_addr_n,
530 "Local IPv4 address")
531 test.assert_equal(e.peer_addr[:4], test.vpp_session.peer_addr_n,
534 test.assert_equal(e.local_addr, test.vpp_session.local_addr_n,
535 "Local IPv6 address")
536 test.assert_equal(e.peer_addr, test.vpp_session.peer_addr_n,
538 test.assert_equal(e.state, expected_state, BFDState)
541 def wait_for_bfd_packet(test, timeout=1, pcap_time_min=None):
542 """ wait for BFD packet and verify its correctness
544 :param timeout: how long to wait
545 :param pcap_time_min: ignore packets with pcap timestamp lower than this
547 :returns: tuple (packet, time spent waiting for packet)
549 test.logger.info("BFD: Waiting for BFD packet")
550 deadline = time.time() + timeout
555 test.assert_in_range(counter, 0, 100, "number of packets ignored")
556 time_left = deadline - time.time()
558 raise CaptureTimeoutError("Packet did not arrive within timeout")
559 p = test.pg0.wait_for_packet(timeout=time_left)
560 test.logger.debug(ppp("BFD: Got packet:", p))
561 if pcap_time_min is not None and p.time < pcap_time_min:
562 test.logger.debug(ppp("BFD: ignoring packet (pcap time %s < "
563 "pcap time min %s):" %
564 (p.time, pcap_time_min), p))
569 raise Exception(ppp("Unexpected or invalid BFD packet:", p))
571 raise Exception(ppp("Unexpected payload in BFD packet:", bfd))
574 test.test_session.verify_bfd(p)
578 class BFD4TestCase(VppTestCase):
579 """Bidirectional Forwarding Detection (BFD)"""
582 vpp_clock_offset = None
588 super(BFD4TestCase, cls).setUpClass()
590 cls.create_pg_interfaces([0])
591 cls.create_loopback_interfaces([0])
592 cls.loopback0 = cls.lo_interfaces[0]
593 cls.loopback0.config_ip4()
594 cls.loopback0.admin_up()
596 cls.pg0.configure_ipv4_neighbors()
598 cls.pg0.resolve_arp()
601 super(BFD4TestCase, cls).tearDownClass()
605 super(BFD4TestCase, self).setUp()
606 self.factory = AuthKeyFactory()
607 self.vapi.want_bfd_events()
608 self.pg0.enable_capture()
610 self.vpp_session = VppBFDUDPSession(self, self.pg0,
612 self.vpp_session.add_vpp_config()
613 self.vpp_session.admin_up()
614 self.test_session = BFDTestSession(self, self.pg0, AF_INET)
616 self.vapi.want_bfd_events(enable_disable=0)
620 if not self.vpp_dead:
621 self.vapi.want_bfd_events(enable_disable=0)
622 self.vapi.collect_events() # clear the event queue
623 super(BFD4TestCase, self).tearDown()
625 def test_session_up(self):
626 """ bring BFD session up """
629 def test_session_down(self):
630 """ bring BFD session down """
632 bfd_session_down(self)
634 def test_hold_up(self):
635 """ hold BFD session up """
637 for dummy in range(self.test_session.detect_mult * 2):
638 wait_for_bfd_packet(self)
639 self.test_session.send_packet()
640 self.assert_equal(len(self.vapi.collect_events()), 0,
641 "number of bfd events")
643 def test_slow_timer(self):
644 """ verify slow periodic control frames while session down """
646 self.logger.info("BFD: Waiting for %d BFD packets", packet_count)
647 prev_packet = wait_for_bfd_packet(self, 2)
648 for dummy in range(packet_count):
649 next_packet = wait_for_bfd_packet(self, 2)
650 time_diff = next_packet.time - prev_packet.time
651 # spec says the range should be <0.75, 1>, allow extra 0.05 margin
652 # to work around timing issues
653 self.assert_in_range(
654 time_diff, 0.70, 1.05, "time between slow packets")
655 prev_packet = next_packet
657 def test_zero_remote_min_rx(self):
658 """ no packets when zero remote required min rx interval """
660 self.test_session.update(required_min_rx=0)
661 self.test_session.send_packet()
662 for dummy in range(self.test_session.detect_mult):
663 self.sleep(self.vpp_session.required_min_rx / USEC_IN_SEC,
664 "sleep before transmitting bfd packet")
665 self.test_session.send_packet()
667 p = wait_for_bfd_packet(self, timeout=0)
668 self.logger.error(ppp("Received unexpected packet:", p))
669 except CaptureTimeoutError:
672 len(self.vapi.collect_events()), 0, "number of bfd events")
673 self.test_session.update(required_min_rx=100000)
674 for dummy in range(3):
675 self.test_session.send_packet()
677 self, timeout=self.test_session.required_min_rx / USEC_IN_SEC)
679 len(self.vapi.collect_events()), 0, "number of bfd events")
681 def test_conn_down(self):
682 """ verify session goes down after inactivity """
684 detection_time = self.test_session.detect_mult *\
685 self.vpp_session.required_min_rx / USEC_IN_SEC
686 self.sleep(detection_time, "waiting for BFD session time-out")
687 e = self.vapi.wait_for_event(1, "bfd_udp_session_details")
688 verify_event(self, e, expected_state=BFDState.down)
690 def test_large_required_min_rx(self):
691 """ large remote required min rx interval """
693 p = wait_for_bfd_packet(self)
695 self.test_session.update(required_min_rx=interval)
696 self.test_session.send_packet()
697 time_mark = time.time()
699 # busy wait here, trying to collect a packet or event, vpp is not
700 # allowed to send packets and the session will timeout first - so the
701 # Up->Down event must arrive before any packets do
702 while time.time() < time_mark + interval / USEC_IN_SEC:
704 p = wait_for_bfd_packet(self, timeout=0)
705 # if vpp managed to send a packet before we did the session
706 # session update, then that's fine, ignore it
707 if p.time < time_mark - self.vpp_clock_offset:
709 self.logger.error(ppp("Received unexpected packet:", p))
711 except CaptureTimeoutError:
713 events = self.vapi.collect_events()
715 verify_event(self, events[0], BFDState.down)
717 self.assert_equal(count, 0, "number of packets received")
719 def test_immediate_remote_min_rx_reduction(self):
720 """ immediately honor remote required min rx reduction """
721 self.vpp_session.remove_vpp_config()
722 self.vpp_session = VppBFDUDPSession(
723 self, self.pg0, self.pg0.remote_ip4, desired_min_tx=10000)
724 self.pg0.enable_capture()
725 self.vpp_session.add_vpp_config()
726 self.test_session.update(desired_min_tx=1000000,
727 required_min_rx=1000000)
729 reference_packet = wait_for_bfd_packet(self)
730 time_mark = time.time()
732 self.test_session.update(required_min_rx=interval)
733 self.test_session.send_packet()
734 extra_time = time.time() - time_mark
735 p = wait_for_bfd_packet(self)
736 # first packet is allowed to be late by time we spent doing the update
737 # calculated in extra_time
738 self.assert_in_range(p.time - reference_packet.time,
739 .95 * 0.75 * interval / USEC_IN_SEC,
740 1.05 * interval / USEC_IN_SEC + extra_time,
741 "time between BFD packets")
743 for dummy in range(3):
744 p = wait_for_bfd_packet(self)
745 diff = p.time - reference_packet.time
746 self.assert_in_range(diff, .95 * .75 * interval / USEC_IN_SEC,
747 1.05 * interval / USEC_IN_SEC,
748 "time between BFD packets")
751 def test_modify_req_min_rx_double(self):
752 """ modify session - double required min rx """
754 p = wait_for_bfd_packet(self)
755 self.test_session.update(desired_min_tx=10000,
756 required_min_rx=10000)
757 self.test_session.send_packet()
758 # double required min rx
759 self.vpp_session.modify_parameters(
760 required_min_rx=2 * self.vpp_session.required_min_rx)
761 p = wait_for_bfd_packet(
762 self, pcap_time_min=time.time() - self.vpp_clock_offset)
763 # poll bit needs to be set
764 self.assertIn("P", p.sprintf("%BFD.flags%"),
765 "Poll bit not set in BFD packet")
766 # finish poll sequence with final packet
767 final = self.test_session.create_packet()
768 final[BFD].flags = "F"
769 timeout = self.test_session.detect_mult * \
770 max(self.test_session.desired_min_tx,
771 self.vpp_session.required_min_rx) / USEC_IN_SEC
772 self.test_session.send_packet(final)
773 time_mark = time.time()
774 e = self.vapi.wait_for_event(2 * timeout, "bfd_udp_session_details")
775 verify_event(self, e, expected_state=BFDState.down)
776 time_to_event = time.time() - time_mark
777 self.assert_in_range(time_to_event, .9 * timeout,
778 1.1 * timeout, "session timeout")
780 def test_modify_req_min_rx_halve(self):
781 """ modify session - halve required min rx """
782 self.vpp_session.modify_parameters(
783 required_min_rx=2 * self.vpp_session.required_min_rx)
785 p = wait_for_bfd_packet(self)
786 self.test_session.update(desired_min_tx=10000,
787 required_min_rx=10000)
788 self.test_session.send_packet()
789 p = wait_for_bfd_packet(
790 self, pcap_time_min=time.time() - self.vpp_clock_offset)
791 # halve required min rx
792 old_required_min_rx = self.vpp_session.required_min_rx
793 self.vpp_session.modify_parameters(
794 required_min_rx=0.5 * self.vpp_session.required_min_rx)
795 # now we wait 0.8*3*old-req-min-rx and the session should still be up
796 self.sleep(0.8 * self.vpp_session.detect_mult *
797 old_required_min_rx / USEC_IN_SEC)
798 self.assert_equal(len(self.vapi.collect_events()), 0,
799 "number of bfd events")
800 p = wait_for_bfd_packet(self)
801 # poll bit needs to be set
802 self.assertIn("P", p.sprintf("%BFD.flags%"),
803 "Poll bit not set in BFD packet")
804 # finish poll sequence with final packet
805 final = self.test_session.create_packet()
806 final[BFD].flags = "F"
807 self.test_session.send_packet(final)
808 # now the session should time out under new conditions
810 e = self.vapi.wait_for_event(1, "bfd_udp_session_details")
812 detection_time = self.test_session.detect_mult *\
813 self.vpp_session.required_min_rx / USEC_IN_SEC
814 self.assert_in_range(after - before,
815 0.9 * detection_time,
816 1.1 * detection_time,
817 "time before bfd session goes down")
818 verify_event(self, e, expected_state=BFDState.down)
820 def test_modify_detect_mult(self):
821 """ modify detect multiplier """
823 p = wait_for_bfd_packet(self)
824 self.vpp_session.modify_parameters(detect_mult=1)
825 p = wait_for_bfd_packet(
826 self, pcap_time_min=time.time() - self.vpp_clock_offset)
827 self.assert_equal(self.vpp_session.detect_mult,
830 # poll bit must not be set
831 self.assertNotIn("P", p.sprintf("%BFD.flags%"),
832 "Poll bit not set in BFD packet")
833 self.vpp_session.modify_parameters(detect_mult=10)
834 p = wait_for_bfd_packet(
835 self, pcap_time_min=time.time() - self.vpp_clock_offset)
836 self.assert_equal(self.vpp_session.detect_mult,
839 # poll bit must not be set
840 self.assertNotIn("P", p.sprintf("%BFD.flags%"),
841 "Poll bit not set in BFD packet")
843 def test_queued_poll(self):
844 """ test poll sequence queueing """
846 p = wait_for_bfd_packet(self)
847 self.vpp_session.modify_parameters(
848 required_min_rx=2 * self.vpp_session.required_min_rx)
849 p = wait_for_bfd_packet(self)
850 poll_sequence_start = time.time()
851 poll_sequence_length_min = 0.5
852 send_final_after = time.time() + poll_sequence_length_min
853 # poll bit needs to be set
854 self.assertIn("P", p.sprintf("%BFD.flags%"),
855 "Poll bit not set in BFD packet")
856 self.assert_equal(p[BFD].required_min_rx_interval,
857 self.vpp_session.required_min_rx,
858 "BFD required min rx interval")
859 self.vpp_session.modify_parameters(
860 required_min_rx=2 * self.vpp_session.required_min_rx)
861 # 2nd poll sequence should be queued now
862 # don't send the reply back yet, wait for some time to emulate
863 # longer round-trip time
865 while time.time() < send_final_after:
866 self.test_session.send_packet()
867 p = wait_for_bfd_packet(self)
868 self.assert_equal(len(self.vapi.collect_events()), 0,
869 "number of bfd events")
870 self.assert_equal(p[BFD].required_min_rx_interval,
871 self.vpp_session.required_min_rx,
872 "BFD required min rx interval")
874 # poll bit must be set
875 self.assertIn("P", p.sprintf("%BFD.flags%"),
876 "Poll bit not set in BFD packet")
877 final = self.test_session.create_packet()
878 final[BFD].flags = "F"
879 self.test_session.send_packet(final)
880 # finish 1st with final
881 poll_sequence_length = time.time() - poll_sequence_start
882 # vpp must wait for some time before starting new poll sequence
883 poll_no_2_started = False
884 for dummy in range(2 * packet_count):
885 p = wait_for_bfd_packet(self)
886 self.assert_equal(len(self.vapi.collect_events()), 0,
887 "number of bfd events")
888 if "P" in p.sprintf("%BFD.flags%"):
889 poll_no_2_started = True
890 if time.time() < poll_sequence_start + poll_sequence_length:
891 raise Exception("VPP started 2nd poll sequence too soon")
892 final = self.test_session.create_packet()
893 final[BFD].flags = "F"
894 self.test_session.send_packet(final)
897 self.test_session.send_packet()
898 self.assertTrue(poll_no_2_started, "2nd poll sequence not performed")
899 # finish 2nd with final
900 final = self.test_session.create_packet()
901 final[BFD].flags = "F"
902 self.test_session.send_packet(final)
903 p = wait_for_bfd_packet(self)
904 # poll bit must not be set
905 self.assertNotIn("P", p.sprintf("%BFD.flags%"),
906 "Poll bit set in BFD packet")
908 def test_no_periodic_if_remote_demand(self):
909 """ no periodic frames outside poll sequence if remote demand set """
911 demand = self.test_session.create_packet()
912 demand[BFD].flags = "D"
913 self.test_session.send_packet(demand)
914 transmit_time = 0.9 \
915 * max(self.vpp_session.required_min_rx,
916 self.test_session.desired_min_tx) \
919 for dummy in range(self.test_session.detect_mult * 2):
920 time.sleep(transmit_time)
921 self.test_session.send_packet(demand)
923 p = wait_for_bfd_packet(self, timeout=0)
924 self.logger.error(ppp("Received unexpected packet:", p))
926 except CaptureTimeoutError:
928 events = self.vapi.collect_events()
930 self.logger.error("Received unexpected event: %s", e)
931 self.assert_equal(count, 0, "number of packets received")
932 self.assert_equal(len(events), 0, "number of events received")
934 def test_echo_looped_back(self):
935 """ echo packets looped back """
936 # don't need a session in this case..
937 self.vpp_session.remove_vpp_config()
938 self.pg0.enable_capture()
939 echo_packet_count = 10
940 # random source port low enough to increment a few times..
941 udp_sport_tx = randint(1, 50000)
942 udp_sport_rx = udp_sport_tx
943 echo_packet = (Ether(src=self.pg0.remote_mac,
944 dst=self.pg0.local_mac) /
945 IP(src=self.pg0.remote_ip4,
946 dst=self.pg0.remote_ip4) /
947 UDP(dport=BFD.udp_dport_echo) /
948 Raw("this should be looped back"))
949 for dummy in range(echo_packet_count):
950 self.sleep(.01, "delay between echo packets")
951 echo_packet[UDP].sport = udp_sport_tx
953 self.logger.debug(ppp("Sending packet:", echo_packet))
954 self.pg0.add_stream(echo_packet)
956 for dummy in range(echo_packet_count):
957 p = self.pg0.wait_for_packet(1)
958 self.logger.debug(ppp("Got packet:", p))
960 self.assert_equal(self.pg0.remote_mac,
961 ether.dst, "Destination MAC")
962 self.assert_equal(self.pg0.local_mac, ether.src, "Source MAC")
964 self.assert_equal(self.pg0.remote_ip4, ip.dst, "Destination IP")
965 self.assert_equal(self.pg0.remote_ip4, ip.src, "Destination IP")
967 self.assert_equal(udp.dport, BFD.udp_dport_echo,
968 "UDP destination port")
969 self.assert_equal(udp.sport, udp_sport_rx, "UDP source port")
971 # need to compare the hex payload here, otherwise BFD_vpp_echo
973 self.assertEqual(str(p[UDP].payload),
974 str(echo_packet[UDP].payload),
975 "Received packet is not the echo packet sent")
976 self.assert_equal(udp_sport_tx, udp_sport_rx, "UDP source port (== "
977 "ECHO packet identifier for test purposes)")
980 """ echo function """
982 self.test_session.update(required_min_echo_rx=50000)
983 self.test_session.send_packet()
984 detection_time = self.test_session.detect_mult *\
985 self.vpp_session.required_min_rx / USEC_IN_SEC
986 # echo shouldn't work without echo source set
987 for dummy in range(3):
988 sleep = 0.75 * detection_time
989 self.sleep(sleep, "delay before sending bfd packet")
990 self.test_session.send_packet()
991 p = wait_for_bfd_packet(
992 self, pcap_time_min=time.time() - self.vpp_clock_offset)
993 self.assert_equal(p[BFD].required_min_rx_interval,
994 self.vpp_session.required_min_rx,
995 "BFD required min rx interval")
996 self.vapi.bfd_udp_set_echo_source(self.loopback0.sw_if_index)
997 # should be turned on - loopback echo packets
998 for dummy in range(3):
999 loop_until = time.time() + 0.75 * detection_time
1000 while time.time() < loop_until:
1001 p = self.pg0.wait_for_packet(1)
1002 self.logger.debug(ppp("Got packet:", p))
1003 if p[UDP].dport == BFD.udp_dport_echo:
1005 p[IP].dst, self.pg0.local_ip4, "BFD ECHO dst IP")
1006 self.assertNotEqual(p[IP].src, self.loopback0.local_ip4,
1007 "BFD ECHO src IP equal to loopback IP")
1008 self.logger.debug(ppp("Looping back packet:", p))
1009 self.pg0.add_stream(p)
1011 elif p.haslayer(BFD):
1012 self.assertGreaterEqual(p[BFD].required_min_rx_interval,
1014 if "P" in p.sprintf("%BFD.flags%"):
1015 final = self.test_session.create_packet()
1016 final[BFD].flags = "F"
1017 self.test_session.send_packet(final)
1019 raise Exception(ppp("Received unknown packet:", p))
1021 self.assert_equal(len(self.vapi.collect_events()), 0,
1022 "number of bfd events")
1023 self.test_session.send_packet()
1025 def test_echo_fail(self):
1026 """ session goes down if echo function fails """
1027 bfd_session_up(self)
1028 self.test_session.update(required_min_echo_rx=50000)
1029 self.test_session.send_packet()
1030 detection_time = self.test_session.detect_mult *\
1031 self.vpp_session.required_min_rx / USEC_IN_SEC
1032 self.vapi.bfd_udp_set_echo_source(self.loopback0.sw_if_index)
1033 # echo function should be used now, but we will drop the echo packets
1034 verified_diag = False
1035 for dummy in range(3):
1036 loop_until = time.time() + 0.75 * detection_time
1037 while time.time() < loop_until:
1038 p = self.pg0.wait_for_packet(1)
1039 self.logger.debug(ppp("Got packet:", p))
1040 if p[UDP].dport == BFD.udp_dport_echo:
1043 elif p.haslayer(BFD):
1044 if "P" in p.sprintf("%BFD.flags%"):
1045 self.assertGreaterEqual(
1046 p[BFD].required_min_rx_interval,
1048 final = self.test_session.create_packet()
1049 final[BFD].flags = "F"
1050 self.test_session.send_packet(final)
1051 if p[BFD].state == BFDState.down:
1052 self.assert_equal(p[BFD].diag,
1053 BFDDiagCode.echo_function_failed,
1055 verified_diag = True
1057 raise Exception(ppp("Received unknown packet:", p))
1058 self.test_session.send_packet()
1059 events = self.vapi.collect_events()
1060 self.assert_equal(len(events), 1, "number of bfd events")
1061 self.assert_equal(events[0].state, BFDState.down, BFDState)
1062 self.assertTrue(verified_diag, "Incorrect diagnostics code received")
1064 def test_echo_stop(self):
1065 """ echo function stops if peer sets required min echo rx zero """
1066 bfd_session_up(self)
1067 self.test_session.update(required_min_echo_rx=50000)
1068 self.test_session.send_packet()
1069 self.vapi.bfd_udp_set_echo_source(self.loopback0.sw_if_index)
1070 # wait for first echo packet
1072 p = self.pg0.wait_for_packet(1)
1073 self.logger.debug(ppp("Got packet:", p))
1074 if p[UDP].dport == BFD.udp_dport_echo:
1075 self.logger.debug(ppp("Looping back packet:", p))
1076 self.pg0.add_stream(p)
1079 elif p.haslayer(BFD):
1083 raise Exception(ppp("Received unknown packet:", p))
1084 self.test_session.update(required_min_echo_rx=0)
1085 self.test_session.send_packet()
1086 # echo packets shouldn't arrive anymore
1087 for dummy in range(5):
1088 wait_for_bfd_packet(
1089 self, pcap_time_min=time.time() - self.vpp_clock_offset)
1090 self.test_session.send_packet()
1091 events = self.vapi.collect_events()
1092 self.assert_equal(len(events), 0, "number of bfd events")
1094 def test_stale_echo(self):
1095 """ stale echo packets don't keep a session up """
1096 bfd_session_up(self)
1097 self.test_session.update(required_min_echo_rx=50000)
1098 self.vapi.bfd_udp_set_echo_source(self.loopback0.sw_if_index)
1099 self.test_session.send_packet()
1100 # should be turned on - loopback echo packets
1104 for dummy in range(10 * self.vpp_session.detect_mult):
1105 p = self.pg0.wait_for_packet(1)
1106 if p[UDP].dport == BFD.udp_dport_echo:
1107 if echo_packet is None:
1108 self.logger.debug(ppp("Got first echo packet:", p))
1110 timeout_at = time.time() + self.vpp_session.detect_mult * \
1111 self.test_session.required_min_echo_rx / USEC_IN_SEC
1113 self.logger.debug(ppp("Got followup echo packet:", p))
1114 self.logger.debug(ppp("Looping back first echo packet:", p))
1115 self.pg0.add_stream(echo_packet)
1117 elif p.haslayer(BFD):
1118 self.logger.debug(ppp("Got packet:", p))
1119 if "P" in p.sprintf("%BFD.flags%"):
1120 final = self.test_session.create_packet()
1121 final[BFD].flags = "F"
1122 self.test_session.send_packet(final)
1123 if p[BFD].state == BFDState.down:
1124 self.assertIsNotNone(
1126 "Session went down before first echo packet received")
1128 self.assertGreaterEqual(
1130 "Session timeout at %s, but is expected at %s" %
1132 self.assert_equal(p[BFD].diag,
1133 BFDDiagCode.echo_function_failed,
1135 events = self.vapi.collect_events()
1136 self.assert_equal(len(events), 1, "number of bfd events")
1137 self.assert_equal(events[0].state, BFDState.down, BFDState)
1141 raise Exception(ppp("Received unknown packet:", p))
1142 self.test_session.send_packet()
1143 self.assertTrue(timeout_ok, "Expected timeout event didn't occur")
1145 def test_invalid_echo_checksum(self):
1146 """ echo packets with invalid checksum don't keep a session up """
1147 bfd_session_up(self)
1148 self.test_session.update(required_min_echo_rx=50000)
1149 self.vapi.bfd_udp_set_echo_source(self.loopback0.sw_if_index)
1150 self.test_session.send_packet()
1151 # should be turned on - loopback echo packets
1154 for dummy in range(10 * self.vpp_session.detect_mult):
1155 p = self.pg0.wait_for_packet(1)
1156 if p[UDP].dport == BFD.udp_dport_echo:
1157 self.logger.debug(ppp("Got echo packet:", p))
1158 if timeout_at is None:
1159 timeout_at = time.time() + self.vpp_session.detect_mult * \
1160 self.test_session.required_min_echo_rx / USEC_IN_SEC
1161 p[BFD_vpp_echo].checksum = getrandbits(64)
1162 self.logger.debug(ppp("Looping back modified echo packet:", p))
1163 self.pg0.add_stream(p)
1165 elif p.haslayer(BFD):
1166 self.logger.debug(ppp("Got packet:", p))
1167 if "P" in p.sprintf("%BFD.flags%"):
1168 final = self.test_session.create_packet()
1169 final[BFD].flags = "F"
1170 self.test_session.send_packet(final)
1171 if p[BFD].state == BFDState.down:
1172 self.assertIsNotNone(
1174 "Session went down before first echo packet received")
1176 self.assertGreaterEqual(
1178 "Session timeout at %s, but is expected at %s" %
1180 self.assert_equal(p[BFD].diag,
1181 BFDDiagCode.echo_function_failed,
1183 events = self.vapi.collect_events()
1184 self.assert_equal(len(events), 1, "number of bfd events")
1185 self.assert_equal(events[0].state, BFDState.down, BFDState)
1189 raise Exception(ppp("Received unknown packet:", p))
1190 self.test_session.send_packet()
1191 self.assertTrue(timeout_ok, "Expected timeout event didn't occur")
1193 def test_admin_up_down(self):
1194 """ put session admin-up and admin-down """
1195 bfd_session_up(self)
1196 self.vpp_session.admin_down()
1197 self.pg0.enable_capture()
1198 e = self.vapi.wait_for_event(1, "bfd_udp_session_details")
1199 verify_event(self, e, expected_state=BFDState.admin_down)
1200 for dummy in range(2):
1201 p = wait_for_bfd_packet(self)
1202 self.assert_equal(BFDState.admin_down, p[BFD].state, BFDState)
1203 # try to bring session up - shouldn't be possible
1204 self.test_session.update(state=BFDState.init)
1205 self.test_session.send_packet()
1206 for dummy in range(2):
1207 p = wait_for_bfd_packet(self)
1208 self.assert_equal(BFDState.admin_down, p[BFD].state, BFDState)
1209 self.vpp_session.admin_up()
1210 self.test_session.update(state=BFDState.down)
1211 e = self.vapi.wait_for_event(1, "bfd_udp_session_details")
1212 verify_event(self, e, expected_state=BFDState.down)
1213 p = wait_for_bfd_packet(self)
1214 self.assert_equal(BFDState.down, p[BFD].state, BFDState)
1215 self.test_session.send_packet()
1216 p = wait_for_bfd_packet(self)
1217 self.assert_equal(BFDState.init, p[BFD].state, BFDState)
1218 e = self.vapi.wait_for_event(1, "bfd_udp_session_details")
1219 verify_event(self, e, expected_state=BFDState.init)
1220 self.test_session.update(state=BFDState.up)
1221 self.test_session.send_packet()
1222 p = wait_for_bfd_packet(self)
1223 self.assert_equal(BFDState.up, p[BFD].state, BFDState)
1224 e = self.vapi.wait_for_event(1, "bfd_udp_session_details")
1225 verify_event(self, e, expected_state=BFDState.up)
1227 def test_config_change_remote_demand(self):
1228 """ configuration change while peer in demand mode """
1229 bfd_session_up(self)
1230 demand = self.test_session.create_packet()
1231 demand[BFD].flags = "D"
1232 self.test_session.send_packet(demand)
1233 self.vpp_session.modify_parameters(
1234 required_min_rx=2 * self.vpp_session.required_min_rx)
1235 p = wait_for_bfd_packet(self)
1236 # poll bit must be set
1237 self.assertIn("P", p.sprintf("%BFD.flags%"), "Poll bit not set")
1238 # terminate poll sequence
1239 final = self.test_session.create_packet()
1240 final[BFD].flags = "D+F"
1241 self.test_session.send_packet(final)
1242 # vpp should be quiet now again
1243 transmit_time = 0.9 \
1244 * max(self.vpp_session.required_min_rx,
1245 self.test_session.desired_min_tx) \
1248 for dummy in range(self.test_session.detect_mult * 2):
1249 time.sleep(transmit_time)
1250 self.test_session.send_packet(demand)
1252 p = wait_for_bfd_packet(self, timeout=0)
1253 self.logger.error(ppp("Received unexpected packet:", p))
1255 except CaptureTimeoutError:
1257 events = self.vapi.collect_events()
1259 self.logger.error("Received unexpected event: %s", e)
1260 self.assert_equal(count, 0, "number of packets received")
1261 self.assert_equal(len(events), 0, "number of events received")
1264 class BFD6TestCase(VppTestCase):
1265 """Bidirectional Forwarding Detection (BFD) (IPv6) """
1268 vpp_clock_offset = None
1273 def setUpClass(cls):
1274 super(BFD6TestCase, cls).setUpClass()
1276 cls.create_pg_interfaces([0])
1277 cls.pg0.config_ip6()
1278 cls.pg0.configure_ipv6_neighbors()
1280 cls.pg0.resolve_ndp()
1281 cls.create_loopback_interfaces([0])
1282 cls.loopback0 = cls.lo_interfaces[0]
1283 cls.loopback0.config_ip6()
1284 cls.loopback0.admin_up()
1287 super(BFD6TestCase, cls).tearDownClass()
1291 super(BFD6TestCase, self).setUp()
1292 self.factory = AuthKeyFactory()
1293 self.vapi.want_bfd_events()
1294 self.pg0.enable_capture()
1296 self.vpp_session = VppBFDUDPSession(self, self.pg0,
1297 self.pg0.remote_ip6,
1299 self.vpp_session.add_vpp_config()
1300 self.vpp_session.admin_up()
1301 self.test_session = BFDTestSession(self, self.pg0, AF_INET6)
1302 self.logger.debug(self.vapi.cli("show adj nbr"))
1304 self.vapi.want_bfd_events(enable_disable=0)
1308 if not self.vpp_dead:
1309 self.vapi.want_bfd_events(enable_disable=0)
1310 self.vapi.collect_events() # clear the event queue
1311 super(BFD6TestCase, self).tearDown()
1313 def test_session_up(self):
1314 """ bring BFD session up """
1315 bfd_session_up(self)
1317 def test_hold_up(self):
1318 """ hold BFD session up """
1319 bfd_session_up(self)
1320 for dummy in range(self.test_session.detect_mult * 2):
1321 wait_for_bfd_packet(self)
1322 self.test_session.send_packet()
1323 self.assert_equal(len(self.vapi.collect_events()), 0,
1324 "number of bfd events")
1325 self.assert_equal(self.vpp_session.state, BFDState.up, BFDState)
1327 def test_echo_looped_back(self):
1328 """ echo packets looped back """
1329 # don't need a session in this case..
1330 self.vpp_session.remove_vpp_config()
1331 self.pg0.enable_capture()
1332 echo_packet_count = 10
1333 # random source port low enough to increment a few times..
1334 udp_sport_tx = randint(1, 50000)
1335 udp_sport_rx = udp_sport_tx
1336 echo_packet = (Ether(src=self.pg0.remote_mac,
1337 dst=self.pg0.local_mac) /
1338 IPv6(src=self.pg0.remote_ip6,
1339 dst=self.pg0.remote_ip6) /
1340 UDP(dport=BFD.udp_dport_echo) /
1341 Raw("this should be looped back"))
1342 for dummy in range(echo_packet_count):
1343 self.sleep(.01, "delay between echo packets")
1344 echo_packet[UDP].sport = udp_sport_tx
1346 self.logger.debug(ppp("Sending packet:", echo_packet))
1347 self.pg0.add_stream(echo_packet)
1349 for dummy in range(echo_packet_count):
1350 p = self.pg0.wait_for_packet(1)
1351 self.logger.debug(ppp("Got packet:", p))
1353 self.assert_equal(self.pg0.remote_mac,
1354 ether.dst, "Destination MAC")
1355 self.assert_equal(self.pg0.local_mac, ether.src, "Source MAC")
1357 self.assert_equal(self.pg0.remote_ip6, ip.dst, "Destination IP")
1358 self.assert_equal(self.pg0.remote_ip6, ip.src, "Destination IP")
1360 self.assert_equal(udp.dport, BFD.udp_dport_echo,
1361 "UDP destination port")
1362 self.assert_equal(udp.sport, udp_sport_rx, "UDP source port")
1364 # need to compare the hex payload here, otherwise BFD_vpp_echo
1366 self.assertEqual(str(p[UDP].payload),
1367 str(echo_packet[UDP].payload),
1368 "Received packet is not the echo packet sent")
1369 self.assert_equal(udp_sport_tx, udp_sport_rx, "UDP source port (== "
1370 "ECHO packet identifier for test purposes)")
1371 self.assert_equal(udp_sport_tx, udp_sport_rx, "UDP source port (== "
1372 "ECHO packet identifier for test purposes)")
1374 def test_echo(self):
1375 """ echo function used """
1376 bfd_session_up(self)
1377 self.test_session.update(required_min_echo_rx=50000)
1378 self.test_session.send_packet()
1379 detection_time = self.test_session.detect_mult *\
1380 self.vpp_session.required_min_rx / USEC_IN_SEC
1381 # echo shouldn't work without echo source set
1382 for dummy in range(3):
1383 sleep = 0.75 * detection_time
1384 self.sleep(sleep, "delay before sending bfd packet")
1385 self.test_session.send_packet()
1386 p = wait_for_bfd_packet(
1387 self, pcap_time_min=time.time() - self.vpp_clock_offset)
1388 self.assert_equal(p[BFD].required_min_rx_interval,
1389 self.vpp_session.required_min_rx,
1390 "BFD required min rx interval")
1391 self.vapi.bfd_udp_set_echo_source(self.loopback0.sw_if_index)
1392 # should be turned on - loopback echo packets
1393 for dummy in range(3):
1394 loop_until = time.time() + 0.75 * detection_time
1395 while time.time() < loop_until:
1396 p = self.pg0.wait_for_packet(1)
1397 self.logger.debug(ppp("Got packet:", p))
1398 if p[UDP].dport == BFD.udp_dport_echo:
1400 p[IPv6].dst, self.pg0.local_ip6, "BFD ECHO dst IP")
1401 self.assertNotEqual(p[IPv6].src, self.loopback0.local_ip6,
1402 "BFD ECHO src IP equal to loopback IP")
1403 self.logger.debug(ppp("Looping back packet:", p))
1404 self.pg0.add_stream(p)
1406 elif p.haslayer(BFD):
1407 self.assertGreaterEqual(p[BFD].required_min_rx_interval,
1409 if "P" in p.sprintf("%BFD.flags%"):
1410 final = self.test_session.create_packet()
1411 final[BFD].flags = "F"
1412 self.test_session.send_packet(final)
1414 raise Exception(ppp("Received unknown packet:", p))
1416 self.assert_equal(len(self.vapi.collect_events()), 0,
1417 "number of bfd events")
1418 self.test_session.send_packet()
1421 class BFDSHA1TestCase(VppTestCase):
1422 """Bidirectional Forwarding Detection (BFD) (SHA1 auth) """
1425 vpp_clock_offset = None
1430 def setUpClass(cls):
1431 super(BFDSHA1TestCase, cls).setUpClass()
1433 cls.create_pg_interfaces([0])
1434 cls.pg0.config_ip4()
1436 cls.pg0.resolve_arp()
1439 super(BFDSHA1TestCase, cls).tearDownClass()
1443 super(BFDSHA1TestCase, self).setUp()
1444 self.factory = AuthKeyFactory()
1445 self.vapi.want_bfd_events()
1446 self.pg0.enable_capture()
1449 if not self.vpp_dead:
1450 self.vapi.want_bfd_events(enable_disable=0)
1451 self.vapi.collect_events() # clear the event queue
1452 super(BFDSHA1TestCase, self).tearDown()
1454 def test_session_up(self):
1455 """ bring BFD session up """
1456 key = self.factory.create_random_key(self)
1457 key.add_vpp_config()
1458 self.vpp_session = VppBFDUDPSession(self, self.pg0,
1459 self.pg0.remote_ip4,
1461 self.vpp_session.add_vpp_config()
1462 self.vpp_session.admin_up()
1463 self.test_session = BFDTestSession(
1464 self, self.pg0, AF_INET, sha1_key=key,
1465 bfd_key_id=self.vpp_session.bfd_key_id)
1466 bfd_session_up(self)
1468 def test_hold_up(self):
1469 """ hold BFD session up """
1470 key = self.factory.create_random_key(self)
1471 key.add_vpp_config()
1472 self.vpp_session = VppBFDUDPSession(self, self.pg0,
1473 self.pg0.remote_ip4,
1475 self.vpp_session.add_vpp_config()
1476 self.vpp_session.admin_up()
1477 self.test_session = BFDTestSession(
1478 self, self.pg0, AF_INET, sha1_key=key,
1479 bfd_key_id=self.vpp_session.bfd_key_id)
1480 bfd_session_up(self)
1481 for dummy in range(self.test_session.detect_mult * 2):
1482 wait_for_bfd_packet(self)
1483 self.test_session.send_packet()
1484 self.assert_equal(self.vpp_session.state, BFDState.up, BFDState)
1486 def test_hold_up_meticulous(self):
1487 """ hold BFD session up - meticulous auth """
1488 key = self.factory.create_random_key(
1489 self, BFDAuthType.meticulous_keyed_sha1)
1490 key.add_vpp_config()
1491 self.vpp_session = VppBFDUDPSession(self, self.pg0,
1492 self.pg0.remote_ip4, sha1_key=key)
1493 self.vpp_session.add_vpp_config()
1494 self.vpp_session.admin_up()
1495 # specify sequence number so that it wraps
1496 self.test_session = BFDTestSession(
1497 self, self.pg0, AF_INET, sha1_key=key,
1498 bfd_key_id=self.vpp_session.bfd_key_id,
1499 our_seq_number=0xFFFFFFFF - 4)
1500 bfd_session_up(self)
1501 for dummy in range(30):
1502 wait_for_bfd_packet(self)
1503 self.test_session.inc_seq_num()
1504 self.test_session.send_packet()
1505 self.assert_equal(self.vpp_session.state, BFDState.up, BFDState)
1507 def test_send_bad_seq_number(self):
1508 """ session is not kept alive by msgs with bad sequence numbers"""
1509 key = self.factory.create_random_key(
1510 self, BFDAuthType.meticulous_keyed_sha1)
1511 key.add_vpp_config()
1512 self.vpp_session = VppBFDUDPSession(self, self.pg0,
1513 self.pg0.remote_ip4, sha1_key=key)
1514 self.vpp_session.add_vpp_config()
1515 self.vpp_session.admin_up()
1516 self.test_session = BFDTestSession(
1517 self, self.pg0, AF_INET, sha1_key=key,
1518 bfd_key_id=self.vpp_session.bfd_key_id)
1519 bfd_session_up(self)
1520 detection_time = self.test_session.detect_mult *\
1521 self.vpp_session.required_min_rx / USEC_IN_SEC
1522 send_until = time.time() + 2 * detection_time
1523 while time.time() < send_until:
1524 self.test_session.send_packet()
1525 self.sleep(0.7 * self.vpp_session.required_min_rx / USEC_IN_SEC,
1526 "time between bfd packets")
1527 e = self.vapi.collect_events()
1528 # session should be down now, because the sequence numbers weren't
1530 self.assert_equal(len(e), 1, "number of bfd events")
1531 verify_event(self, e[0], expected_state=BFDState.down)
1533 def execute_rogue_session_scenario(self, vpp_bfd_udp_session,
1534 legitimate_test_session,
1536 rogue_bfd_values=None):
1537 """ execute a rogue session interaction scenario
1539 1. create vpp session, add config
1540 2. bring the legitimate session up
1541 3. copy the bfd values from legitimate session to rogue session
1542 4. apply rogue_bfd_values to rogue session
1543 5. set rogue session state to down
1544 6. send message to take the session down from the rogue session
1545 7. assert that the legitimate session is unaffected
1548 self.vpp_session = vpp_bfd_udp_session
1549 self.vpp_session.add_vpp_config()
1550 self.vpp_session.admin_up()
1551 self.test_session = legitimate_test_session
1552 # bring vpp session up
1553 bfd_session_up(self)
1554 # send packet from rogue session
1555 rogue_test_session.update(
1556 my_discriminator=self.test_session.my_discriminator,
1557 your_discriminator=self.test_session.your_discriminator,
1558 desired_min_tx=self.test_session.desired_min_tx,
1559 required_min_rx=self.test_session.required_min_rx,
1560 detect_mult=self.test_session.detect_mult,
1561 diag=self.test_session.diag,
1562 state=self.test_session.state,
1563 auth_type=self.test_session.auth_type)
1564 if rogue_bfd_values:
1565 rogue_test_session.update(**rogue_bfd_values)
1566 rogue_test_session.update(state=BFDState.down)
1567 rogue_test_session.send_packet()
1568 wait_for_bfd_packet(self)
1569 self.assert_equal(self.vpp_session.state, BFDState.up, BFDState)
1571 def test_mismatch_auth(self):
1572 """ session is not brought down by unauthenticated msg """
1573 key = self.factory.create_random_key(self)
1574 key.add_vpp_config()
1575 vpp_session = VppBFDUDPSession(
1576 self, self.pg0, self.pg0.remote_ip4, sha1_key=key)
1577 legitimate_test_session = BFDTestSession(
1578 self, self.pg0, AF_INET, sha1_key=key,
1579 bfd_key_id=vpp_session.bfd_key_id)
1580 rogue_test_session = BFDTestSession(self, self.pg0, AF_INET)
1581 self.execute_rogue_session_scenario(vpp_session,
1582 legitimate_test_session,
1585 def test_mismatch_bfd_key_id(self):
1586 """ session is not brought down by msg with non-existent key-id """
1587 key = self.factory.create_random_key(self)
1588 key.add_vpp_config()
1589 vpp_session = VppBFDUDPSession(
1590 self, self.pg0, self.pg0.remote_ip4, sha1_key=key)
1591 # pick a different random bfd key id
1593 while x == vpp_session.bfd_key_id:
1595 legitimate_test_session = BFDTestSession(
1596 self, self.pg0, AF_INET, sha1_key=key,
1597 bfd_key_id=vpp_session.bfd_key_id)
1598 rogue_test_session = BFDTestSession(
1599 self, self.pg0, AF_INET, sha1_key=key, bfd_key_id=x)
1600 self.execute_rogue_session_scenario(vpp_session,
1601 legitimate_test_session,
1604 def test_mismatched_auth_type(self):
1605 """ session is not brought down by msg with wrong auth type """
1606 key = self.factory.create_random_key(self)
1607 key.add_vpp_config()
1608 vpp_session = VppBFDUDPSession(
1609 self, self.pg0, self.pg0.remote_ip4, sha1_key=key)
1610 legitimate_test_session = BFDTestSession(
1611 self, self.pg0, AF_INET, sha1_key=key,
1612 bfd_key_id=vpp_session.bfd_key_id)
1613 rogue_test_session = BFDTestSession(
1614 self, self.pg0, AF_INET, sha1_key=key,
1615 bfd_key_id=vpp_session.bfd_key_id)
1616 self.execute_rogue_session_scenario(
1617 vpp_session, legitimate_test_session, rogue_test_session,
1618 {'auth_type': BFDAuthType.keyed_md5})
1620 def test_restart(self):
1621 """ simulate remote peer restart and resynchronization """
1622 key = self.factory.create_random_key(
1623 self, BFDAuthType.meticulous_keyed_sha1)
1624 key.add_vpp_config()
1625 self.vpp_session = VppBFDUDPSession(self, self.pg0,
1626 self.pg0.remote_ip4, sha1_key=key)
1627 self.vpp_session.add_vpp_config()
1628 self.vpp_session.admin_up()
1629 self.test_session = BFDTestSession(
1630 self, self.pg0, AF_INET, sha1_key=key,
1631 bfd_key_id=self.vpp_session.bfd_key_id, our_seq_number=0)
1632 bfd_session_up(self)
1633 # don't send any packets for 2*detection_time
1634 detection_time = self.test_session.detect_mult *\
1635 self.vpp_session.required_min_rx / USEC_IN_SEC
1636 self.sleep(detection_time, "simulating peer restart")
1637 events = self.vapi.collect_events()
1638 self.assert_equal(len(events), 1, "number of bfd events")
1639 verify_event(self, events[0], expected_state=BFDState.down)
1640 self.test_session.update(state=BFDState.down)
1641 # reset sequence number
1642 self.test_session.our_seq_number = 0
1643 self.test_session.vpp_seq_number = None
1644 # now throw away any pending packets
1645 self.pg0.enable_capture()
1646 bfd_session_up(self)
1649 class BFDAuthOnOffTestCase(VppTestCase):
1650 """Bidirectional Forwarding Detection (BFD) (changing auth) """
1657 def setUpClass(cls):
1658 super(BFDAuthOnOffTestCase, cls).setUpClass()
1660 cls.create_pg_interfaces([0])
1661 cls.pg0.config_ip4()
1663 cls.pg0.resolve_arp()
1666 super(BFDAuthOnOffTestCase, cls).tearDownClass()
1670 super(BFDAuthOnOffTestCase, self).setUp()
1671 self.factory = AuthKeyFactory()
1672 self.vapi.want_bfd_events()
1673 self.pg0.enable_capture()
1676 if not self.vpp_dead:
1677 self.vapi.want_bfd_events(enable_disable=0)
1678 self.vapi.collect_events() # clear the event queue
1679 super(BFDAuthOnOffTestCase, self).tearDown()
1681 def test_auth_on_immediate(self):
1682 """ turn auth on without disturbing session state (immediate) """
1683 key = self.factory.create_random_key(self)
1684 key.add_vpp_config()
1685 self.vpp_session = VppBFDUDPSession(self, self.pg0,
1686 self.pg0.remote_ip4)
1687 self.vpp_session.add_vpp_config()
1688 self.vpp_session.admin_up()
1689 self.test_session = BFDTestSession(self, self.pg0, AF_INET)
1690 bfd_session_up(self)
1691 for dummy in range(self.test_session.detect_mult * 2):
1692 p = wait_for_bfd_packet(self)
1693 self.assert_equal(p[BFD].state, BFDState.up, BFDState)
1694 self.test_session.send_packet()
1695 self.vpp_session.activate_auth(key)
1696 self.test_session.bfd_key_id = self.vpp_session.bfd_key_id
1697 self.test_session.sha1_key = key
1698 for dummy in range(self.test_session.detect_mult * 2):
1699 p = wait_for_bfd_packet(self)
1700 self.assert_equal(p[BFD].state, BFDState.up, BFDState)
1701 self.test_session.send_packet()
1702 self.assert_equal(self.vpp_session.state, BFDState.up, BFDState)
1703 self.assert_equal(len(self.vapi.collect_events()), 0,
1704 "number of bfd events")
1706 def test_auth_off_immediate(self):
1707 """ turn auth off without disturbing session state (immediate) """
1708 key = self.factory.create_random_key(self)
1709 key.add_vpp_config()
1710 self.vpp_session = VppBFDUDPSession(self, self.pg0,
1711 self.pg0.remote_ip4, sha1_key=key)
1712 self.vpp_session.add_vpp_config()
1713 self.vpp_session.admin_up()
1714 self.test_session = BFDTestSession(
1715 self, self.pg0, AF_INET, sha1_key=key,
1716 bfd_key_id=self.vpp_session.bfd_key_id)
1717 bfd_session_up(self)
1718 # self.vapi.want_bfd_events(enable_disable=0)
1719 for dummy in range(self.test_session.detect_mult * 2):
1720 p = wait_for_bfd_packet(self)
1721 self.assert_equal(p[BFD].state, BFDState.up, BFDState)
1722 self.test_session.inc_seq_num()
1723 self.test_session.send_packet()
1724 self.vpp_session.deactivate_auth()
1725 self.test_session.bfd_key_id = None
1726 self.test_session.sha1_key = None
1727 for dummy in range(self.test_session.detect_mult * 2):
1728 p = wait_for_bfd_packet(self)
1729 self.assert_equal(p[BFD].state, BFDState.up, BFDState)
1730 self.test_session.inc_seq_num()
1731 self.test_session.send_packet()
1732 self.assert_equal(self.vpp_session.state, BFDState.up, BFDState)
1733 self.assert_equal(len(self.vapi.collect_events()), 0,
1734 "number of bfd events")
1736 def test_auth_change_key_immediate(self):
1737 """ change auth key without disturbing session state (immediate) """
1738 key1 = self.factory.create_random_key(self)
1739 key1.add_vpp_config()
1740 key2 = self.factory.create_random_key(self)
1741 key2.add_vpp_config()
1742 self.vpp_session = VppBFDUDPSession(self, self.pg0,
1743 self.pg0.remote_ip4, sha1_key=key1)
1744 self.vpp_session.add_vpp_config()
1745 self.vpp_session.admin_up()
1746 self.test_session = BFDTestSession(
1747 self, self.pg0, AF_INET, sha1_key=key1,
1748 bfd_key_id=self.vpp_session.bfd_key_id)
1749 bfd_session_up(self)
1750 for dummy in range(self.test_session.detect_mult * 2):
1751 p = wait_for_bfd_packet(self)
1752 self.assert_equal(p[BFD].state, BFDState.up, BFDState)
1753 self.test_session.send_packet()
1754 self.vpp_session.activate_auth(key2)
1755 self.test_session.bfd_key_id = self.vpp_session.bfd_key_id
1756 self.test_session.sha1_key = key2
1757 for dummy in range(self.test_session.detect_mult * 2):
1758 p = wait_for_bfd_packet(self)
1759 self.assert_equal(p[BFD].state, BFDState.up, BFDState)
1760 self.test_session.send_packet()
1761 self.assert_equal(self.vpp_session.state, BFDState.up, BFDState)
1762 self.assert_equal(len(self.vapi.collect_events()), 0,
1763 "number of bfd events")
1765 def test_auth_on_delayed(self):
1766 """ turn auth on without disturbing session state (delayed) """
1767 key = self.factory.create_random_key(self)
1768 key.add_vpp_config()
1769 self.vpp_session = VppBFDUDPSession(self, self.pg0,
1770 self.pg0.remote_ip4)
1771 self.vpp_session.add_vpp_config()
1772 self.vpp_session.admin_up()
1773 self.test_session = BFDTestSession(self, self.pg0, AF_INET)
1774 bfd_session_up(self)
1775 for dummy in range(self.test_session.detect_mult * 2):
1776 wait_for_bfd_packet(self)
1777 self.test_session.send_packet()
1778 self.vpp_session.activate_auth(key, delayed=True)
1779 for dummy in range(self.test_session.detect_mult * 2):
1780 p = wait_for_bfd_packet(self)
1781 self.assert_equal(p[BFD].state, BFDState.up, BFDState)
1782 self.test_session.send_packet()
1783 self.test_session.bfd_key_id = self.vpp_session.bfd_key_id
1784 self.test_session.sha1_key = key
1785 self.test_session.send_packet()
1786 for dummy in range(self.test_session.detect_mult * 2):
1787 p = wait_for_bfd_packet(self)
1788 self.assert_equal(p[BFD].state, BFDState.up, BFDState)
1789 self.test_session.send_packet()
1790 self.assert_equal(self.vpp_session.state, BFDState.up, BFDState)
1791 self.assert_equal(len(self.vapi.collect_events()), 0,
1792 "number of bfd events")
1794 def test_auth_off_delayed(self):
1795 """ turn auth off without disturbing session state (delayed) """
1796 key = self.factory.create_random_key(self)
1797 key.add_vpp_config()
1798 self.vpp_session = VppBFDUDPSession(self, self.pg0,
1799 self.pg0.remote_ip4, sha1_key=key)
1800 self.vpp_session.add_vpp_config()
1801 self.vpp_session.admin_up()
1802 self.test_session = BFDTestSession(
1803 self, self.pg0, AF_INET, sha1_key=key,
1804 bfd_key_id=self.vpp_session.bfd_key_id)
1805 bfd_session_up(self)
1806 for dummy in range(self.test_session.detect_mult * 2):
1807 p = wait_for_bfd_packet(self)
1808 self.assert_equal(p[BFD].state, BFDState.up, BFDState)
1809 self.test_session.send_packet()
1810 self.vpp_session.deactivate_auth(delayed=True)
1811 for dummy in range(self.test_session.detect_mult * 2):
1812 p = wait_for_bfd_packet(self)
1813 self.assert_equal(p[BFD].state, BFDState.up, BFDState)
1814 self.test_session.send_packet()
1815 self.test_session.bfd_key_id = None
1816 self.test_session.sha1_key = None
1817 self.test_session.send_packet()
1818 for dummy in range(self.test_session.detect_mult * 2):
1819 p = wait_for_bfd_packet(self)
1820 self.assert_equal(p[BFD].state, BFDState.up, BFDState)
1821 self.test_session.send_packet()
1822 self.assert_equal(self.vpp_session.state, BFDState.up, BFDState)
1823 self.assert_equal(len(self.vapi.collect_events()), 0,
1824 "number of bfd events")
1826 def test_auth_change_key_delayed(self):
1827 """ change auth key without disturbing session state (delayed) """
1828 key1 = self.factory.create_random_key(self)
1829 key1.add_vpp_config()
1830 key2 = self.factory.create_random_key(self)
1831 key2.add_vpp_config()
1832 self.vpp_session = VppBFDUDPSession(self, self.pg0,
1833 self.pg0.remote_ip4, sha1_key=key1)
1834 self.vpp_session.add_vpp_config()
1835 self.vpp_session.admin_up()
1836 self.test_session = BFDTestSession(
1837 self, self.pg0, AF_INET, sha1_key=key1,
1838 bfd_key_id=self.vpp_session.bfd_key_id)
1839 bfd_session_up(self)
1840 for dummy in range(self.test_session.detect_mult * 2):
1841 p = wait_for_bfd_packet(self)
1842 self.assert_equal(p[BFD].state, BFDState.up, BFDState)
1843 self.test_session.send_packet()
1844 self.vpp_session.activate_auth(key2, delayed=True)
1845 for dummy in range(self.test_session.detect_mult * 2):
1846 p = wait_for_bfd_packet(self)
1847 self.assert_equal(p[BFD].state, BFDState.up, BFDState)
1848 self.test_session.send_packet()
1849 self.test_session.bfd_key_id = self.vpp_session.bfd_key_id
1850 self.test_session.sha1_key = key2
1851 self.test_session.send_packet()
1852 for dummy in range(self.test_session.detect_mult * 2):
1853 p = wait_for_bfd_packet(self)
1854 self.assert_equal(p[BFD].state, BFDState.up, BFDState)
1855 self.test_session.send_packet()
1856 self.assert_equal(self.vpp_session.state, BFDState.up, BFDState)
1857 self.assert_equal(len(self.vapi.collect_events()), 0,
1858 "number of bfd events")
1860 if __name__ == '__main__':
1861 unittest.main(testRunner=VppTestRunner)