1 """ BFD protocol implementation """
3 from random import randint
4 from socket import AF_INET, AF_INET6, inet_pton
5 from scapy.all import bind_layers
6 from scapy.layers.inet import UDP
7 from scapy.packet import Packet
8 from scapy.fields import (
16 from vpp_object import VppObject
17 from util import NumericConstant
18 from vpp_papi import VppEnum
21 class BFDDiagCode(NumericConstant):
22 """BFD Diagnostic Code"""
25 control_detection_time_expired = 1
26 echo_function_failed = 2
27 neighbor_signaled_session_down = 3
28 forwarding_plane_reset = 4
30 concatenated_path_down = 6
31 administratively_down = 7
32 reverse_concatenated_path_down = 8
35 no_diagnostic: "No diagnostic",
36 control_detection_time_expired: "Control Detection Time Expired",
37 echo_function_failed: "Echo Function Failed",
38 neighbor_signaled_session_down: "Neighbor Signaled Session Down",
39 forwarding_plane_reset: "Forwarding Plane Reset",
40 path_down: "Path Down",
41 concatenated_path_down: "Concatenated Path Down",
42 administratively_down: "Administratively Down",
43 reverse_concatenated_path_down: "Reverse Concatenated Path Down",
47 class BFDState(NumericConstant):
56 admin_down: "AdminDown",
63 class BFDAuthType(NumericConstant):
64 """BFD Authentication Type"""
69 meticulous_keyed_md5 = 3
71 meticulous_keyed_sha1 = 5
74 no_auth: "No authentication",
75 simple_pwd: "Simple Password",
76 keyed_md5: "Keyed MD5",
77 meticulous_keyed_md5: "Meticulous Keyed MD5",
78 keyed_sha1: "Keyed SHA1",
79 meticulous_keyed_sha1: "Meticulous Keyed SHA1",
83 def bfd_is_auth_used(pkt):
84 """is packet authenticated?"""
85 return "A" in pkt.sprintf("%BFD.flags%")
88 def bfd_is_simple_pwd_used(pkt):
89 """is simple password authentication used?"""
90 return bfd_is_auth_used(pkt) and pkt.auth_type == BFDAuthType.simple_pwd
93 def bfd_is_sha1_used(pkt):
94 """is sha1 authentication used?"""
95 return bfd_is_auth_used(pkt) and pkt.auth_type in (
96 BFDAuthType.keyed_sha1,
97 BFDAuthType.meticulous_keyed_sha1,
101 def bfd_is_md5_used(pkt):
102 """is md5 authentication used?"""
103 return bfd_is_auth_used(pkt) and pkt.auth_type in (
104 BFDAuthType.keyed_md5,
105 BFDAuthType.meticulous_keyed_md5,
109 def bfd_is_md5_or_sha1_used(pkt):
110 """is md5 or sha1 used?"""
111 return bfd_is_md5_used(pkt) or bfd_is_sha1_used(pkt)
115 """BFD protocol layer for scapy"""
117 udp_dport = 3784 #: BFD destination port per RFC 5881
118 udp_dport_echo = 3785 # : BFD destination port for ECHO per RFC 5881
119 udp_sport_min = 49152 #: BFD source port min value per RFC 5881
120 udp_sport_max = 65535 #: BFD source port max value per RFC 5881
121 bfd_pkt_len = 24 # : length of BFD pkt without authentication section
122 sha1_auth_len = 28 # : length of authentication section if SHA1 used
127 BitField("version", 1, 3),
128 BitEnumField("diag", 0, 5, BFDDiagCode.desc_dict),
129 BitEnumField("state", 0, 2, BFDState.desc_dict),
130 FlagsField("flags", 0, 6, ["M", "D", "A", "C", "F", "P"]),
131 XByteField("detect_mult", 0),
132 BitField("length", bfd_pkt_len, 8),
133 BitField("my_discriminator", 0, 32),
134 BitField("your_discriminator", 0, 32),
135 BitField("desired_min_tx_interval", 0, 32),
136 BitField("required_min_rx_interval", 0, 32),
137 BitField("required_min_echo_rx_interval", 0, 32),
139 BitEnumField("auth_type", 0, 8, BFDAuthType.desc_dict), bfd_is_auth_used
141 ConditionalField(BitField("auth_len", 0, 8), bfd_is_auth_used),
142 ConditionalField(BitField("auth_key_id", 0, 8), bfd_is_auth_used),
143 ConditionalField(BitField("auth_reserved", 0, 8), bfd_is_md5_or_sha1_used),
144 ConditionalField(BitField("auth_seq_num", 0, 32), bfd_is_md5_or_sha1_used),
145 ConditionalField(StrField("auth_key_hash", "0" * 16), bfd_is_md5_used),
146 ConditionalField(StrField("auth_key_hash", "0" * 20), bfd_is_sha1_used),
151 "BFD(my_disc=%BFD.my_discriminator%, your_disc=%BFD.your_discriminator%)"
155 # glue the BFD packet class to scapy parser
156 bind_layers(UDP, BFD, dport=BFD.udp_dport)
159 class BFD_vpp_echo(Packet):
160 """BFD echo packet as used by VPP (non-rfc, as rfc doesn't define one)"""
162 udp_dport = 3785 #: BFD echo destination port per RFC 5881
163 name = "BFD_VPP_ECHO"
166 BitField("discriminator", 0, 32),
167 BitField("expire_time_clocks", 0, 64),
168 BitField("checksum", 0, 64),
173 "BFD_VPP_ECHO(disc=%BFD_VPP_ECHO.discriminator%,"
174 "expire_time_clocks=%BFD_VPP_ECHO.expire_time_clocks%)"
178 # glue the BFD echo packet class to scapy parser
179 bind_layers(UDP, BFD_vpp_echo, dport=BFD_vpp_echo.udp_dport)
182 class VppBFDAuthKey(VppObject):
183 """Represents BFD authentication key in VPP"""
185 def __init__(self, test, conf_key_id, auth_type, key):
188 self._auth_type = auth_type
189 test.assertIn(auth_type, BFDAuthType.desc_dict)
190 self._conf_key_id = conf_key_id
194 """Test which created this key"""
199 """Authentication type for this key"""
200 return self._auth_type
208 def key(self, value):
212 def conf_key_id(self):
213 """configuration key ID"""
214 return self._conf_key_id
216 def add_vpp_config(self):
217 self.test.vapi.bfd_auth_set_key(
218 conf_key_id=self._conf_key_id,
219 auth_type=self._auth_type,
221 key_len=len(self._key),
223 self._test.registry.register(self, self.test.logger)
225 def get_bfd_auth_keys_dump_entry(self):
226 """get the entry in the auth keys dump corresponding to this key"""
227 result = self.test.vapi.bfd_auth_keys_dump()
229 if k.conf_key_id == self._conf_key_id:
233 def query_vpp_config(self):
234 return self.get_bfd_auth_keys_dump_entry() is not None
236 def remove_vpp_config(self):
237 self.test.vapi.bfd_auth_del_key(conf_key_id=self._conf_key_id)
240 return "bfd-auth-key-%s" % self._conf_key_id
243 class VppBFDUDPSession(VppObject):
244 """Represents BFD UDP session in VPP"""
253 desired_min_tx=300000,
254 required_min_rx=300000,
261 self._interface = interface
264 self._local_addr = local_addr
266 self._local_addr = None
267 self._peer_addr = peer_addr
268 self._desired_min_tx = desired_min_tx
269 self._required_min_rx = required_min_rx
270 self._detect_mult = detect_mult
271 self._sha1_key = sha1_key
272 if bfd_key_id is not None:
273 self._bfd_key_id = bfd_key_id
275 self._bfd_key_id = randint(0, 255)
276 self._is_tunnel = is_tunnel
280 """Test which created this session"""
285 """Interface on which this session lives"""
286 return self._interface
290 """Address family - AF_INET or AF_INET6"""
294 def local_addr(self):
295 """BFD session local address (VPP address)"""
296 if self._local_addr is None:
297 if self.af == AF_INET:
298 return self._interface.local_ip4
299 elif self.af == AF_INET6:
300 return self._interface.local_ip6
302 raise Exception("Unexpected af '%s'" % self.af)
303 return self._local_addr
307 """BFD session peer address"""
308 return self._peer_addr
310 def get_bfd_udp_session_dump_entry(self):
311 """get the namedtuple entry from bfd udp session dump"""
312 result = self.test.vapi.bfd_udp_session_dump()
314 self.test.logger.debug("session entry: %s" % str(s))
315 if s.sw_if_index == self.interface.sw_if_index:
318 and self.interface.local_ip4 == str(s.local_addr)
319 and self.interface.remote_ip4 == str(s.peer_addr)
324 and self.interface.local_ip6 == str(s.local_addr)
325 and self.interface.remote_ip6 == str(s.peer_addr)
332 """BFD session state"""
333 session = self.get_bfd_udp_session_dump_entry()
335 raise Exception("Could not find BFD session in VPP response")
339 def desired_min_tx(self):
340 """desired minimum tx interval"""
341 return self._desired_min_tx
344 def required_min_rx(self):
345 """required minimum rx interval"""
346 return self._required_min_rx
349 def detect_mult(self):
350 """detect multiplier"""
351 return self._detect_mult
356 return self._sha1_key
359 def bfd_key_id(self):
360 """bfd key id in use"""
361 return self._bfd_key_id
365 return self._is_tunnel
367 def activate_auth(self, key, bfd_key_id=None, delayed=False):
368 """activate authentication for this session"""
369 self._bfd_key_id = bfd_key_id if bfd_key_id else randint(0, 255)
371 conf_key_id = self._sha1_key.conf_key_id
372 is_delayed = 1 if delayed else 0
373 self.test.vapi.bfd_udp_auth_activate(
374 sw_if_index=self._interface.sw_if_index,
375 local_addr=self.local_addr,
376 peer_addr=self.peer_addr,
377 bfd_key_id=self._bfd_key_id,
378 conf_key_id=conf_key_id,
379 is_delayed=is_delayed,
382 def deactivate_auth(self, delayed=False):
383 """deactivate authentication"""
384 self._bfd_key_id = None
385 self._sha1_key = None
386 is_delayed = 1 if delayed else 0
387 self.test.vapi.bfd_udp_auth_deactivate(
388 sw_if_index=self._interface.sw_if_index,
389 local_addr=self.local_addr,
390 peer_addr=self.peer_addr,
391 is_delayed=is_delayed,
394 def modify_parameters(
395 self, detect_mult=None, desired_min_tx=None, required_min_rx=None
397 """modify session parameters"""
399 self._detect_mult = detect_mult
401 self._desired_min_tx = desired_min_tx
403 self._required_min_rx = required_min_rx
404 self.test.vapi.bfd_udp_mod(
405 sw_if_index=self._interface.sw_if_index,
406 desired_min_tx=self.desired_min_tx,
407 required_min_rx=self.required_min_rx,
408 detect_mult=self.detect_mult,
409 local_addr=self.local_addr,
410 peer_addr=self.peer_addr,
413 def add_vpp_config(self):
414 bfd_key_id = self._bfd_key_id if self._sha1_key else None
415 conf_key_id = self._sha1_key.conf_key_id if self._sha1_key else None
416 is_authenticated = True if self._sha1_key else False
417 self.test.vapi.bfd_udp_add(
418 sw_if_index=self._interface.sw_if_index,
419 desired_min_tx=self.desired_min_tx,
420 required_min_rx=self.required_min_rx,
421 detect_mult=self.detect_mult,
422 local_addr=self.local_addr,
423 peer_addr=self.peer_addr,
424 bfd_key_id=bfd_key_id,
425 conf_key_id=conf_key_id,
426 is_authenticated=is_authenticated,
428 self._test.registry.register(self, self.test.logger)
431 self, detect_mult=None, desired_min_tx=None, required_min_rx=None
434 self._desired_min_tx = desired_min_tx
436 self._required_min_rx = required_min_rx
438 self._detect_mult = detect_mult
439 bfd_key_id = self._bfd_key_id if self._sha1_key else None
440 conf_key_id = self._sha1_key.conf_key_id if self._sha1_key else None
441 is_authenticated = True if self._sha1_key else False
442 self.test.vapi.bfd_udp_upd(
443 sw_if_index=self._interface.sw_if_index,
444 desired_min_tx=self.desired_min_tx,
445 required_min_rx=self.required_min_rx,
446 detect_mult=self.detect_mult,
447 local_addr=self.local_addr,
448 peer_addr=self.peer_addr,
449 bfd_key_id=bfd_key_id,
450 conf_key_id=conf_key_id,
451 is_authenticated=is_authenticated,
453 self._test.registry.register(self, self.test.logger)
455 def query_vpp_config(self):
456 session = self.get_bfd_udp_session_dump_entry()
457 return session is not None
459 def remove_vpp_config(self):
460 self.test.vapi.bfd_udp_del(
461 self._interface.sw_if_index,
462 local_addr=self.local_addr,
463 peer_addr=self.peer_addr,
467 return "bfd-udp-%s-%s-%s-%s" % (
468 self._interface.sw_if_index,
475 """set bfd session admin-up"""
476 self.test.vapi.bfd_udp_session_set_flags(
477 flags=VppEnum.vl_api_if_status_flags_t.IF_STATUS_API_FLAG_ADMIN_UP,
478 sw_if_index=self._interface.sw_if_index,
479 local_addr=self.local_addr,
480 peer_addr=self.peer_addr,
483 def admin_down(self):
484 """set bfd session admin-down"""
485 self.test.vapi.bfd_udp_session_set_flags(
487 sw_if_index=self._interface.sw_if_index,
488 local_addr=self.local_addr,
489 peer_addr=self.peer_addr,