bfd: add per session counters
[vpp.git] / test / test_bfd.py
1 #!/usr/bin/env python3
2 """ BFD tests """
3
4 from __future__ import division
5
6 import binascii
7 from collections import namedtuple
8 import hashlib
9 import ipaddress
10 import reprlib
11 import time
12 import unittest
13 from random import randint, shuffle, getrandbits
14 from socket import AF_INET, AF_INET6, inet_ntop
15 from struct import pack, unpack
16
17 import scapy.compat
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
22
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
28 from util import ppp
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, \
33     CliFailedCommandError
34 from vpp_pg_interface import CaptureTimeoutError, is_ipv6_misc
35 from vpp_gre_interface import VppGreInterface
36 from vpp_papi import VppEnum
37
38 USEC_IN_SEC = 1000000
39
40
41 class AuthKeyFactory(object):
42     """Factory class for creating auth keys with unique conf key ID"""
43
44     def __init__(self):
45         self._conf_key_ids = {}
46
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)
57
58
59 class BFDAPITestCase(VppTestCase):
60     """Bidirectional Forwarding Detection (BFD) - API"""
61
62     pg0 = None
63     pg1 = None
64
65     @classmethod
66     def setUpClass(cls):
67         super(BFDAPITestCase, cls).setUpClass()
68         cls.vapi.cli("set log class bfd level debug")
69         try:
70             cls.create_pg_interfaces(range(2))
71             for i in cls.pg_interfaces:
72                 i.config_ip4()
73                 i.config_ip6()
74                 i.resolve_arp()
75
76         except Exception:
77             super(BFDAPITestCase, cls).tearDownClass()
78             raise
79
80     @classmethod
81     def tearDownClass(cls):
82         super(BFDAPITestCase, cls).tearDownClass()
83
84     def setUp(self):
85         super(BFDAPITestCase, self).setUp()
86         self.factory = AuthKeyFactory()
87
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()
97
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()
102
103         with self.vapi.assert_negative_api_retval():
104             session.add_vpp_config()
105
106         session.remove_vpp_config()
107
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()
118
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,
124                                    detect_mult=1)
125         session.add_vpp_config()
126         s = session.get_bfd_udp_session_dump_entry()
127         self.assert_equal(session.desired_min_tx,
128                           s.desired_min_tx,
129                           "desired min transmit interval")
130         self.assert_equal(session.required_min_rx,
131                           s.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,
139                           s.desired_min_tx,
140                           "desired min transmit interval")
141         self.assert_equal(session.required_min_rx,
142                           s.required_min_rx,
143                           "required min receive interval")
144         self.assert_equal(session.detect_mult, s.detect_mult, "detect mult")
145
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,
151                                    detect_mult=1)
152         session.upd_vpp_config()
153         s = session.get_bfd_udp_session_dump_entry()
154         self.assert_equal(session.desired_min_tx,
155                           s.desired_min_tx,
156                           "desired min transmit interval")
157         self.assert_equal(session.required_min_rx,
158                           s.required_min_rx,
159                           "required min receive interval")
160
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,
167                           s.desired_min_tx,
168                           "desired min transmit interval")
169         self.assert_equal(session.required_min_rx,
170                           s.required_min_rx,
171                           "required min receive interval")
172         self.assert_equal(session.detect_mult, s.detect_mult, "detect mult")
173
174     def test_add_sha1_keys(self):
175         """ add SHA1 keys """
176         key_count = 10
177         keys = [self.factory.create_random_key(
178             self) for i in range(0, key_count)]
179         for key in keys:
180             self.assertFalse(key.query_vpp_config())
181         for key in keys:
182             key.add_vpp_config()
183         for key in keys:
184             self.assertTrue(key.query_vpp_config())
185         # remove randomly
186         indexes = list(range(key_count))
187         shuffle(indexes)
188         removed = []
189         for i in indexes:
190             key = keys[i]
191             key.remove_vpp_config()
192             removed.append(i)
193             for j in range(key_count):
194                 key = keys[j]
195                 if j in removed:
196                     self.assertFalse(key.query_vpp_config())
197                 else:
198                     self.assertTrue(key.query_vpp_config())
199         # should be removed now
200         for key in keys:
201             self.assertFalse(key.query_vpp_config())
202         # add back and remove again
203         for key in keys:
204             key.add_vpp_config()
205         for key in keys:
206             self.assertTrue(key.query_vpp_config())
207         for key in keys:
208             key.remove_vpp_config()
209         for key in keys:
210             self.assertFalse(key.query_vpp_config())
211
212     def test_add_bfd_sha1(self):
213         """ create a BFD session (SHA1) """
214         key = self.factory.create_random_key(self)
215         key.add_vpp_config()
216         session = VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip4,
217                                    sha1_key=key)
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()
224
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)
228         key.add_vpp_config()
229         session = VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip4,
230                                    sha1_key=key)
231         session.add_vpp_config()
232         with self.assertRaises(Exception):
233             session.add_vpp_config()
234
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()
242
243     def test_shared_sha1_key(self):
244         """ single SHA1 key shared by multiple BFD sessions """
245         key = self.factory.create_random_key(self)
246         key.add_vpp_config()
247         sessions = [
248             VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip4,
249                              sha1_key=key),
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,
253                              sha1_key=key),
254             VppBFDUDPSession(self, self.pg1, self.pg1.remote_ip6,
255                              sha1_key=key, af=AF_INET6)]
256         for s in sessions:
257             s.add_vpp_config()
258         removed = 0
259         for s in sessions:
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()
264             removed += 1
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")
268
269     def test_activate_auth(self):
270         """ activate SHA1 authentication """
271         key = self.factory.create_random_key(self)
272         key.add_vpp_config()
273         session = VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip4)
274         session.add_vpp_config()
275         session.activate_auth(key)
276
277     def test_deactivate_auth(self):
278         """ deactivate SHA1 authentication """
279         key = self.factory.create_random_key(self)
280         key.add_vpp_config()
281         session = VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip4)
282         session.add_vpp_config()
283         session.activate_auth(key)
284         session.deactivate_auth()
285
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,
295                                    sha1_key=key1)
296         session.add_vpp_config()
297         session.activate_auth(key2)
298
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)
308
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)
316
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)
326
327         self.loopback0.config_ip6()
328         echo_ip6 = ipaddress.IPv6Address(int(ipaddress.IPv6Address(
329             self.loopback0.local_ip6)) ^ 1).packed
330
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)
338
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)
344
345
346 class BFDTestSession(object):
347     """ BFD session as seen from test framework side """
348
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):
352         self.test = test
353         self.af = af
354         self.sha1_key = sha1_key
355         self.bfd_key_id = bfd_key_id
356         self.interface = interface
357         if phy_interface:
358             self.phy_interface = phy_interface
359         else:
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)
364         else:
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
377         self.tx_packets = 0
378         self.rx_packets = 0
379         self.tx_packets_echo = 0
380         self.rx_packets_echo = 0
381
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
386         else:
387             self.our_seq_number += 1
388
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
406         if diag is not None:
407             self.diag = diag
408         if state is not None:
409             self.state = state
410         if auth_type is not None:
411             self.auth_type = auth_type
412
413     def fill_packet_fields(self, packet):
414         """ set packet fields with known values in packet """
415         bfd = packet[BFD]
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",
437                 self.desired_min_tx)
438             bfd.desired_min_tx_interval = self.desired_min_tx
439         if self.detect_mult:
440             self.test.logger.debug(
441                 "BFD: setting packet.detect_mult=%s", self.detect_mult)
442             bfd.detect_mult = self.detect_mult
443         if self.diag:
444             self.test.logger.debug("BFD: setting packet.diag=%s", self.diag)
445             bfd.diag = self.diag
446         if self.state:
447             self.test.logger.debug("BFD: setting packet.state=%s", self.state)
448             bfd.state = self.state
449         if self.auth_type:
450             # this is used by a negative test-case
451             self.test.logger.debug("BFD: setting packet.auth_type=%s",
452                                    self.auth_type)
453             bfd.auth_type = self.auth_type
454
455     def create_packet(self):
456         """ create a BFD packet, reflecting the current state of session """
457         if self.sha1_key:
458             bfd = BFD(flags="A")
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
464         else:
465             bfd = BFD()
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:
471             packet = (packet /
472                       IPv6(src=self.interface.remote_ip6,
473                            dst=self.interface.local_ip6,
474                            hlim=255) /
475                       UDP(sport=self.udp_sport, dport=BFD.udp_dport) /
476                       bfd)
477         else:
478             packet = (packet /
479                       IP(src=self.interface.remote_ip4,
480                          dst=self.interface.local_ip4,
481                          ttl=255) /
482                       UDP(sport=self.udp_sport, dport=BFD.udp_dport) /
483                       bfd)
484         self.test.logger.debug("BFD: Creating packet")
485         self.fill_packet_fields(packet)
486         if self.sha1_key:
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()
493         return packet
494
495     def send_packet(self, packet=None, interface=None):
496         """ send packet on interface, creating the packet if needed """
497         if packet is None:
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)
503         self.tx_packets += 1
504         self.test.pg_start()
505
506     def verify_sha1_auth(self, packet):
507         """ Verify correctness of authentication in BFD layer. """
508         bfd = packet[BFD]
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,
511                                BFDAuthType)
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" %
517                                    self.vpp_seq_number)
518         else:
519             recvd_seq_num = bfd.auth_seq_num
520             self.test.logger.debug("Received followup sequence number: %s" %
521                                    recvd_seq_num)
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")
528                 else:
529                     self.test.assert_in_range(recvd_seq_num,
530                                               self.vpp_seq_number,
531                                               self.vpp_seq_number + 1,
532                                               "BFD sequence number")
533             else:
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")
538                 else:
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")
550
551     def verify_bfd(self, packet):
552         """ Verify correctness of BFD layer. """
553         bfd = packet[BFD]
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")
558         if self.sha1_key:
559             self.verify_sha1_auth(packet)
560
561
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)
566     old_offset = None
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)
572     if old_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,
580                              state=BFDState.init)
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)
595
596
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)
610
611
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)
617     if state:
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,
624                       "detect multiplier")
625     if session.sha1_key is None:
626         test.assert_equal(dump.is_authenticated, 0, "is_authenticated flag")
627     else:
628         test.assert_equal(dump.is_authenticated, 1, "is_authenticated flag")
629         test.assert_equal(dump.bfd_key_id, session.bfd_key_id,
630                           "bfd key id")
631         test.assert_equal(dump.conf_key_id,
632                           session.sha1_key.conf_key_id,
633                           "config key id")
634
635
636 def verify_ip(test, packet):
637     """ Verify correctness of IP layer. """
638     if test.vpp_session.af == AF_INET6:
639         ip = packet[IPv6]
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")
643     else:
644         ip = packet[IP]
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")
650
651
652 def verify_udp(test, packet):
653     """ Verify correctness of UDP layer. """
654     udp = packet[UDP]
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,
657                          "UDP source port")
658
659
660 def verify_event(test, event, expected_state):
661     """ Verify correctness of event values. """
662     e = event
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")
667
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,
671                       "Peer IPv6 address")
672     test.assert_equal(e.state, expected_state, BFDState)
673
674
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
677
678     :param timeout: how long to wait
679     :param pcap_time_min: ignore packets with pcap timestamp lower than this
680
681     :returns: tuple (packet, time spent waiting for packet)
682     """
683     test.logger.info("BFD: Waiting for BFD packet")
684     deadline = time.time() + timeout
685     counter = 0
686     while True:
687         counter += 1
688         # sanity check
689         test.assert_in_range(counter, 0, 100, "number of packets ignored")
690         time_left = deadline - time.time()
691         if time_left < 0:
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))
700         else:
701             break
702     if is_tunnel:
703         # strip an IP layer and move to the next
704         p = p[IP].payload
705
706     bfd = p[BFD]
707     if bfd is None:
708         raise Exception(ppp("Unexpected or invalid BFD packet:", p))
709     if bfd.payload:
710         raise Exception(ppp("Unexpected payload in BFD packet:", bfd))
711     verify_ip(test, p)
712     verify_udp(test, p)
713     test.test_session.verify_bfd(p)
714     return p
715
716
717 BFDStats = namedtuple("BFDStats", "rx rx_echo tx tx_echo")
718
719
720 def bfd_grab_stats_snapshot(test, bs_idx=0, thread_index=None):
721     s = test.statistics
722     ti = thread_index
723     if ti is 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()
728     else:
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)
734
735
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)
742
743
744 @tag_run_solo
745 class BFD4TestCase(VppTestCase):
746     """Bidirectional Forwarding Detection (BFD)"""
747
748     pg0 = None
749     vpp_clock_offset = None
750     vpp_session = None
751     test_session = None
752
753     @classmethod
754     def setUpClass(cls):
755         super(BFD4TestCase, cls).setUpClass()
756         cls.vapi.cli("set log class bfd level debug")
757         try:
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()
763             cls.pg0.config_ip4()
764             cls.pg0.configure_ipv4_neighbors()
765             cls.pg0.admin_up()
766             cls.pg0.resolve_arp()
767
768         except Exception:
769             super(BFD4TestCase, cls).tearDownClass()
770             raise
771
772     @classmethod
773     def tearDownClass(cls):
774         super(BFD4TestCase, cls).tearDownClass()
775
776     def setUp(self):
777         super(BFD4TestCase, self).setUp()
778         self.factory = AuthKeyFactory()
779         self.vapi.want_bfd_events()
780         self.pg0.enable_capture()
781         try:
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,
785                                                 self.pg0.remote_ip4)
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)
791             raise
792
793     def tearDown(self):
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()
798
799     def test_session_up(self):
800         """ bring BFD session up """
801         bfd_session_up(self)
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)
806
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,
819                                  state=BFDState.up)
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)
832
833     def test_session_down(self):
834         """ bring BFD session down """
835         bfd_session_up(self)
836         bfd_session_down(self)
837
838     def test_hold_up(self):
839         """ hold BFD session up """
840         bfd_session_up(self)
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")
846
847     def test_slow_timer(self):
848         """ verify slow periodic control frames while session down """
849         packet_count = 3
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
860
861     def test_zero_remote_min_rx(self):
862         """ no packets when zero remote required min rx interval """
863         bfd_session_up(self)
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()
870             try:
871                 p = wait_for_bfd_packet(self, timeout=0)
872                 self.logger.error(ppp("Received unexpected packet:", p))
873             except CaptureTimeoutError:
874                 pass
875         self.assert_equal(
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()
880             wait_for_bfd_packet(
881                 self, timeout=self.test_session.required_min_rx / USEC_IN_SEC)
882         self.assert_equal(
883             len(self.vapi.collect_events()), 0, "number of bfd events")
884
885     def test_conn_down(self):
886         """ verify session goes down after inactivity """
887         bfd_session_up(self)
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)
893
894     def test_peer_discr_reset_sess_down(self):
895         """ peer discriminator reset after session goes down """
896         bfd_session_up(self)
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)
903
904     def test_large_required_min_rx(self):
905         """ large remote required min rx interval """
906         bfd_session_up(self)
907         p = wait_for_bfd_packet(self)
908         interval = 3000000
909         self.test_session.update(required_min_rx=interval)
910         self.test_session.send_packet()
911         time_mark = time.time()
912         count = 0
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:
917             try:
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:
922                     continue
923                 self.logger.error(ppp("Received unexpected packet:", p))
924                 count += 1
925             except CaptureTimeoutError:
926                 pass
927             events = self.vapi.collect_events()
928             if len(events) > 0:
929                 verify_event(self, events[0], BFDState.down)
930                 break
931         self.assert_equal(count, 0, "number of packets received")
932
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)
942         bfd_session_up(self)
943         reference_packet = wait_for_bfd_packet(self)
944         time_mark = time.time()
945         interval = 300000
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")
956         reference_packet = p
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")
963             reference_packet = p
964
965     def test_modify_req_min_rx_double(self):
966         """ modify session - double required min rx """
967         bfd_session_up(self)
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")
993
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)
998         bfd_session_up(self)
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")
1029         after = time.time()
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)
1035
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,
1044                           p[BFD].detect_mult,
1045                           "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,
1053                           p[BFD].detect_mult,
1054                           "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")
1058
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
1080         packet_count = 0
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")
1089             packet_count += 1
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)
1111                 break
1112             else:
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")
1123
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%"))
1135
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) \
1145             / USEC_IN_SEC
1146         count = 0
1147         for dummy in range(self.test_session.detect_mult * 2):
1148             self.sleep(transmit_time)
1149             self.test_session.send_packet(demand)
1150             try:
1151                 p = wait_for_bfd_packet(self, timeout=0)
1152                 self.logger.error(ppp("Received unexpected packet:", p))
1153                 count += 1
1154             except CaptureTimeoutError:
1155                 pass
1156         events = self.vapi.collect_events()
1157         for e in 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")
1161
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
1180             udp_sport_tx += 1
1181             self.logger.debug(ppp("Sending packet:", echo_packet))
1182             self.pg0.add_stream(echo_packet)
1183             self.pg_start()
1184             self.logger.debug(self.vapi.ppcli("show trace"))
1185         counter = 0
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))
1190             ether = p[Ether]
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")
1194             ip = p[IP]
1195             self.assert_equal(self.pg0.remote_ip4, ip.dst, "Destination IP")
1196             udp = p[UDP]
1197             if udp.dport == BFD.udp_dport:
1198                 bfd_control_packets_rx += 1
1199                 continue
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")
1204             udp_sport_rx += 1
1205             # need to compare the hex payload here, otherwise BFD_vpp_echo
1206             # gets in way
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")
1210             counter += 1
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)
1215         self.assertEqual(
1216             0, diff.rx, "RX counter bumped but no BFD packets sent")
1217         self.assertEqual(
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")
1223
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)
1245         echo_seen = False
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:
1253                     self.assert_equal(
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
1264                     self.pg_start()
1265                     echo_seen = True
1266                 elif p.haslayer(BFD):
1267                     self.test_session.rx_packets += 1
1268                     if echo_seen:
1269                         self.assertGreaterEqual(
1270                             p[BFD].required_min_rx_interval,
1271                             1000000)
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)
1276                 else:
1277                     raise Exception(ppp("Received unknown packet:", p))
1278
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")
1283
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,
1293                              "RX echo counter")
1294         self.assert_in_range(self.test_session.rx_packets_echo,
1295                              diff.tx_echo - 1, diff.tx_echo + 1,
1296                              "TX echo counter")
1297
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:
1315                     # dropped
1316                     pass
1317                 elif p.haslayer(BFD):
1318                     if "P" in p.sprintf("%BFD.flags%"):
1319                         self.assertGreaterEqual(
1320                             p[BFD].required_min_rx_interval,
1321                             1000000)
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,
1328                                           BFDDiagCode)
1329                         verified_diag = True
1330                 else:
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")
1337
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
1346         while True:
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)
1353                 self.pg_start()
1354                 break
1355             elif p.haslayer(BFD):
1356                 # ignore BFD
1357                 pass
1358             else:
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")
1369
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
1378         while True:
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)
1385                 self.pg_start()
1386                 break
1387             elif p.haslayer(BFD):
1388                 # ignore BFD
1389                 pass
1390             else:
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")
1401
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
1410         echo_packet = None
1411         timeout_at = None
1412         timeout_ok = False
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))
1418                     echo_packet = p
1419                     timeout_at = time.time() + self.vpp_session.detect_mult * \
1420                         self.test_session.required_min_echo_rx / USEC_IN_SEC
1421                 else:
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)
1426                 self.pg_start()
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(
1435                         timeout_at,
1436                         "Session went down before first echo packet received")
1437                     now = time.time()
1438                     self.assertGreaterEqual(
1439                         now, timeout_at,
1440                         "Session timeout at %s, but is expected at %s" %
1441                         (now, timeout_at))
1442                     self.assert_equal(p[BFD].diag,
1443                                       BFDDiagCode.echo_function_failed,
1444                                       BFDDiagCode)
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)
1448                     timeout_ok = True
1449                     break
1450             else:
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")
1454
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
1463         timeout_at = None
1464         timeout_ok = False
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)
1476                 self.pg_start()
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(
1485                         timeout_at,
1486                         "Session went down before first echo packet received")
1487                     now = time.time()
1488                     self.assertGreaterEqual(
1489                         now, timeout_at,
1490                         "Session timeout at %s, but is expected at %s" %
1491                         (now, timeout_at))
1492                     self.assert_equal(p[BFD].diag,
1493                                       BFDDiagCode.echo_function_failed,
1494                                       BFDDiagCode)
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)
1498                     timeout_ok = True
1499                     break
1500             else:
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")
1504
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)
1541
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) \
1562             / USEC_IN_SEC
1563         count = 0
1564         for dummy in range(self.test_session.detect_mult * 2):
1565             self.sleep(transmit_time)
1566             self.test_session.send_packet(demand)
1567             try:
1568                 p = wait_for_bfd_packet(self, timeout=0)
1569                 self.logger.error(ppp("Received unexpected packet:", p))
1570                 count += 1
1571             except CaptureTimeoutError:
1572                 pass
1573         events = self.vapi.collect_events()
1574         for e in 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")
1578
1579     def test_intf_deleted(self):
1580         """ interface with bfd session deleted """
1581         intf = VppLoInterface(self)
1582         intf.config_ip4()
1583         intf.admin_up()
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())
1592
1593
1594 @tag_run_solo
1595 @tag_fixme_vpp_workers
1596 class BFD6TestCase(VppTestCase):
1597     """Bidirectional Forwarding Detection (BFD) (IPv6) """
1598
1599     pg0 = None
1600     vpp_clock_offset = None
1601     vpp_session = None
1602     test_session = None
1603
1604     @classmethod
1605     def setUpClass(cls):
1606         super(BFD6TestCase, cls).setUpClass()
1607         cls.vapi.cli("set log class bfd level debug")
1608         try:
1609             cls.create_pg_interfaces([0])
1610             cls.pg0.config_ip6()
1611             cls.pg0.configure_ipv6_neighbors()
1612             cls.pg0.admin_up()
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()
1618
1619         except Exception:
1620             super(BFD6TestCase, cls).tearDownClass()
1621             raise
1622
1623     @classmethod
1624     def tearDownClass(cls):
1625         super(BFD6TestCase, cls).tearDownClass()
1626
1627     def setUp(self):
1628         super(BFD6TestCase, self).setUp()
1629         self.factory = AuthKeyFactory()
1630         self.vapi.want_bfd_events()
1631         self.pg0.enable_capture()
1632         try:
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,
1637                                                 af=AF_INET6)
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)
1644             raise
1645
1646     def tearDown(self):
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()
1651
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)
1659
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,
1672                                  state=BFDState.up)
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)
1685
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)
1695
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
1714             udp_sport_tx += 1
1715             self.logger.debug(ppp("Sending packet:", echo_packet))
1716             self.pg0.add_stream(echo_packet)
1717             self.pg_start()
1718         counter = 0
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))
1723             ether = p[Ether]
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")
1727             ip = p[IPv6]
1728             self.assert_equal(self.pg0.remote_ip6, ip.dst, "Destination IP")
1729             udp = p[UDP]
1730             if udp.dport == BFD.udp_dport:
1731                 bfd_control_packets_rx += 1
1732                 continue
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")
1737             udp_sport_rx += 1
1738             # need to compare the hex payload here, otherwise BFD_vpp_echo
1739             # gets in way
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")
1743             counter += 1
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)
1748         self.assertEqual(
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")
1756
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)
1778         echo_seen = False
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:
1786                     self.assert_equal(
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)
1797                     self.pg_start()
1798                     echo_seen = True
1799                 elif p.haslayer(BFD):
1800                     self.test_session.rx_packets += 1
1801                     if echo_seen:
1802                         self.assertGreaterEqual(
1803                             p[BFD].required_min_rx_interval,
1804                             1000000)
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)
1809                 else:
1810                     raise Exception(ppp("Received unknown packet:", p))
1811
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")
1816
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,
1826                              "RX echo counter")
1827         self.assert_in_range(self.test_session.rx_packets_echo,
1828                              diff.tx_echo - 1, diff.tx_echo + 1,
1829                              "TX echo counter")
1830
1831     def test_intf_deleted(self):
1832         """ interface with bfd session deleted """
1833         intf = VppLoInterface(self)
1834         intf.config_ip6()
1835         intf.admin_up()
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())
1845
1846
1847 @tag_run_solo
1848 class BFDFIBTestCase(VppTestCase):
1849     """ BFD-FIB interactions (IPv6) """
1850
1851     vpp_session = None
1852     test_session = None
1853
1854     @classmethod
1855     def setUpClass(cls):
1856         super(BFDFIBTestCase, cls).setUpClass()
1857
1858     @classmethod
1859     def tearDownClass(cls):
1860         super(BFDFIBTestCase, cls).tearDownClass()
1861
1862     def setUp(self):
1863         super(BFDFIBTestCase, self).setUp()
1864         self.create_pg_interfaces(range(1))
1865
1866         self.vapi.want_bfd_events()
1867         self.pg0.enable_capture()
1868
1869         for i in self.pg_interfaces:
1870             i.admin_up()
1871             i.config_ip6()
1872             i.configure_ipv6_neighbors()
1873
1874     def tearDown(self):
1875         if not self.vpp_dead:
1876             self.vapi.want_bfd_events(enable_disable=False)
1877
1878         super(BFDFIBTestCase, self).tearDown()
1879
1880     @staticmethod
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):
1884             return True
1885         return False
1886
1887     def test_session_with_fib(self):
1888         """ BFD-FIB interactions """
1889
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))]
1899
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,
1907                                                 0xffffffff)])
1908         ip_2001_s_64.add_vpp_config()
1909         ip_2002_s_64.add_vpp_config()
1910
1911         # bring the session up now the routes are present
1912         self.vpp_session = VppBFDUDPSession(self,
1913                                             self.pg0,
1914                                             self.pg0.remote_ip6,
1915                                             af=AF_INET6)
1916         self.vpp_session.add_vpp_config()
1917         self.vpp_session.admin_up()
1918         self.test_session = BFDTestSession(self, self.pg0, AF_INET6)
1919
1920         # session is up - traffic passes
1921         bfd_session_up(self)
1922
1923         self.pg0.add_stream(p)
1924         self.pg_start()
1925         for packet in p:
1926             captured = self.pg0.wait_for_packet(
1927                 1,
1928                 filter_out_fn=self.pkt_is_not_data_traffic)
1929             self.assertEqual(captured[IPv6].dst,
1930                              packet[IPv6].dst)
1931
1932         # session is up - traffic is dropped
1933         bfd_session_down(self)
1934
1935         self.pg0.add_stream(p)
1936         self.pg_start()
1937         with self.assertRaises(CaptureTimeoutError):
1938             self.pg0.wait_for_packet(1, self.pkt_is_not_data_traffic)
1939
1940         # session is up - traffic passes
1941         bfd_session_up(self)
1942
1943         self.pg0.add_stream(p)
1944         self.pg_start()
1945         for packet in p:
1946             captured = self.pg0.wait_for_packet(
1947                 1,
1948                 filter_out_fn=self.pkt_is_not_data_traffic)
1949             self.assertEqual(captured[IPv6].dst,
1950                              packet[IPv6].dst)
1951
1952
1953 @unittest.skipUnless(running_extended_tests, "part of extended tests")
1954 class BFDTunTestCase(VppTestCase):
1955     """ BFD over GRE tunnel """
1956
1957     vpp_session = None
1958     test_session = None
1959
1960     @classmethod
1961     def setUpClass(cls):
1962         super(BFDTunTestCase, cls).setUpClass()
1963
1964     @classmethod
1965     def tearDownClass(cls):
1966         super(BFDTunTestCase, cls).tearDownClass()
1967
1968     def setUp(self):
1969         super(BFDTunTestCase, self).setUp()
1970         self.create_pg_interfaces(range(1))
1971
1972         self.vapi.want_bfd_events()
1973         self.pg0.enable_capture()
1974
1975         for i in self.pg_interfaces:
1976             i.admin_up()
1977             i.config_ip4()
1978             i.resolve_arp()
1979
1980     def tearDown(self):
1981         if not self.vpp_dead:
1982             self.vapi.want_bfd_events(enable_disable=0)
1983
1984         super(BFDTunTestCase, self).tearDown()
1985
1986     @staticmethod
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):
1990             return True
1991         return False
1992
1993     def test_bfd_o_gre(self):
1994         """ BFD-o-GRE  """
1995
1996         # A GRE interface over which to run a BFD session
1997         gre_if = VppGreInterface(self,
1998                                  self.pg0.local_ip4,
1999                                  self.pg0.remote_ip4)
2000         gre_if.add_vpp_config()
2001         gre_if.admin_up()
2002         gre_if.config_ip4()
2003
2004         # bring the session up now the routes are present
2005         self.vpp_session = VppBFDUDPSession(self,
2006                                             gre_if,
2007                                             gre_if.remote_ip4,
2008                                             is_tunnel=True)
2009         self.vpp_session.add_vpp_config()
2010         self.vpp_session.admin_up()
2011
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) /
2016                            GRE()),
2017             phy_interface=self.pg0)
2018
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))]
2024
2025         # session is up - traffic passes
2026         bfd_session_up(self)
2027
2028         self.send_and_expect(self.pg0, p, self.pg0)
2029
2030         # bring session down
2031         bfd_session_down(self)
2032
2033
2034 @tag_run_solo
2035 class BFDSHA1TestCase(VppTestCase):
2036     """Bidirectional Forwarding Detection (BFD) (SHA1 auth) """
2037
2038     pg0 = None
2039     vpp_clock_offset = None
2040     vpp_session = None
2041     test_session = None
2042
2043     @classmethod
2044     def setUpClass(cls):
2045         super(BFDSHA1TestCase, cls).setUpClass()
2046         cls.vapi.cli("set log class bfd level debug")
2047         try:
2048             cls.create_pg_interfaces([0])
2049             cls.pg0.config_ip4()
2050             cls.pg0.admin_up()
2051             cls.pg0.resolve_arp()
2052
2053         except Exception:
2054             super(BFDSHA1TestCase, cls).tearDownClass()
2055             raise
2056
2057     @classmethod
2058     def tearDownClass(cls):
2059         super(BFDSHA1TestCase, cls).tearDownClass()
2060
2061     def setUp(self):
2062         super(BFDSHA1TestCase, self).setUp()
2063         self.factory = AuthKeyFactory()
2064         self.vapi.want_bfd_events()
2065         self.pg0.enable_capture()
2066
2067     def tearDown(self):
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()
2072
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,
2079                                             sha1_key=key)
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)
2086
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,
2093                                             sha1_key=key)
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)
2104
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)
2125
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
2147         # updated
2148         self.assert_equal(len(e), 1, "number of bfd events")
2149         verify_event(self, e[0], expected_state=BFDState.down)
2150
2151     def execute_rogue_session_scenario(self, vpp_bfd_udp_session,
2152                                        legitimate_test_session,
2153                                        rogue_test_session,
2154                                        rogue_bfd_values=None):
2155         """ execute a rogue session interaction scenario
2156
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
2164         """
2165
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)
2187
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,
2200                                             rogue_test_session)
2201
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
2209         x = randint(0, 255)
2210         while x == vpp_session.bfd_key_id:
2211             x = randint(0, 255)
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,
2219                                             rogue_test_session)
2220
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})
2236
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)
2264
2265
2266 @tag_run_solo
2267 class BFDAuthOnOffTestCase(VppTestCase):
2268     """Bidirectional Forwarding Detection (BFD) (changing auth) """
2269
2270     pg0 = None
2271     vpp_session = None
2272     test_session = None
2273
2274     @classmethod
2275     def setUpClass(cls):
2276         super(BFDAuthOnOffTestCase, cls).setUpClass()
2277         cls.vapi.cli("set log class bfd level debug")
2278         try:
2279             cls.create_pg_interfaces([0])
2280             cls.pg0.config_ip4()
2281             cls.pg0.admin_up()
2282             cls.pg0.resolve_arp()
2283
2284         except Exception:
2285             super(BFDAuthOnOffTestCase, cls).tearDownClass()
2286             raise
2287
2288     @classmethod
2289     def tearDownClass(cls):
2290         super(BFDAuthOnOffTestCase, cls).tearDownClass()
2291
2292     def setUp(self):
2293         super(BFDAuthOnOffTestCase, self).setUp()
2294         self.factory = AuthKeyFactory()
2295         self.vapi.want_bfd_events()
2296         self.pg0.enable_capture()
2297
2298     def tearDown(self):
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()
2303
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")
2327
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")
2356
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")
2384
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")
2412
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")
2443
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")
2477
2478
2479 @tag_run_solo
2480 class BFDCLITestCase(VppTestCase):
2481     """Bidirectional Forwarding Detection (BFD) (CLI) """
2482     pg0 = None
2483
2484     @classmethod
2485     def setUpClass(cls):
2486         super(BFDCLITestCase, cls).setUpClass()
2487         cls.vapi.cli("set log class bfd level debug")
2488         try:
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()
2494
2495         except Exception:
2496             super(BFDCLITestCase, cls).tearDownClass()
2497             raise
2498
2499     @classmethod
2500     def tearDownClass(cls):
2501         super(BFDCLITestCase, cls).tearDownClass()
2502
2503     def setUp(self):
2504         super(BFDCLITestCase, self).setUp()
2505         self.factory = AuthKeyFactory()
2506         self.pg0.enable_capture()
2507
2508     def tearDown(self):
2509         try:
2510             self.vapi.want_bfd_events(enable_disable=False)
2511         except UnexpectedApiReturnValueError:
2512             # some tests aren't subscribed, so this is not an issue
2513             pass
2514         self.vapi.collect_events()  # clear the event queue
2515         super(BFDCLITestCase, self).tearDown()
2516
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),
2520                           "",
2521                           "CLI command response")
2522
2523     def cli_verify_response(self, cli, expected):
2524         """ execute a CLI, asserting that the response matches expectation """
2525         try:
2526             reply = self.vapi.cli(cli)
2527         except CliFailedCommandError as cli_error:
2528             reply = str(cli_error)
2529         self.assert_equal(reply.strip(),
2530                           expected,
2531                           "CLI command response")
2532
2533     def test_show(self):
2534         """ show commands """
2535         k1 = self.factory.create_random_key(self)
2536         k1.add_vpp_config()
2537         k2 = self.factory.create_random_key(
2538             self, auth_type=BFDAuthType.meticulous_keyed_sha1)
2539         k2.add_vpp_config()
2540         s1 = VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip4)
2541         s1.add_vpp_config()
2542         s2 = VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip6, af=AF_INET6,
2543                               sha1_key=k2)
2544         s2.add_vpp_config()
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"))
2548
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" %
2555             (k.conf_key_id,
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
2568         # is in-use
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" %
2572             (k.conf_key_id,
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())
2583
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" %
2591             (k.conf_key_id,
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,
2596                                             sha1_key=k)
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
2606         # is in-use
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" %
2610             (k.conf_key_id,
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())
2621
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(
2637             cli_add_cmd,
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())
2662
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(
2678             cli_add_cmd,
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(
2700             cli_del_cmd,
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())
2704
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(
2722             cli_add_cmd,
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(
2745             cli_del_cmd,
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())
2749
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(
2768             cli_add_cmd,
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(
2791             cli_del_cmd,
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())
2795
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,
2803                                         sha1_key=key)
2804         session.add_vpp_config()
2805         cli_activate = \
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)
2814         cli_deactivate = \
2815             "bfd udp session auth deactivate interface %s local-addr %s "\
2816             "peer-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)
2822
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,
2830                                         sha1_key=key)
2831         session.add_vpp_config()
2832         cli_activate = \
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)
2841         cli_deactivate = \
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)
2849
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()
2854         cli_down = \
2855             "bfd udp session set-flags admin down interface %s local-addr %s "\
2856             "peer-addr %s "\
2857             % (self.pg0.name, self.pg0.local_ip4, self.pg0.remote_ip4)
2858         cli_up = \
2859             "bfd udp session set-flags admin up interface %s local-addr %s "\
2860             "peer-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)
2866
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.")
2901
2902
2903 if __name__ == '__main__':
2904     unittest.main(testRunner=VppTestRunner)