a297dab25273b8ce876d05af4b7bf8557972ef84
[vpp.git] / test / bfd.py
1 """ BFD protocol implementation """
2
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 BitField, BitEnumField, XByteField, FlagsField,\
9     ConditionalField, StrField
10 from vpp_object import VppObject
11 from util import NumericConstant
12
13
14 class BFDDiagCode(NumericConstant):
15     """ BFD Diagnostic Code """
16     no_diagnostic = 0
17     control_detection_time_expired = 1
18     echo_function_failed = 2
19     neighbor_signaled_session_down = 3
20     forwarding_plane_reset = 4
21     path_down = 5
22     concatenated_path_down = 6
23     administratively_down = 7
24     reverse_concatenated_path_down = 8
25
26     desc_dict = {
27         no_diagnostic: "No diagnostic",
28         control_detection_time_expired: "Control Detection Time Expired",
29         echo_function_failed: "Echo Function Failed",
30         neighbor_signaled_session_down: "Neighbor Signaled Session Down",
31         forwarding_plane_reset: "Forwarding Plane Reset",
32         path_down: "Path Down",
33         concatenated_path_down: "Concatenated Path Down",
34         administratively_down: "Administratively Down",
35         reverse_concatenated_path_down: "Reverse Concatenated Path Down",
36     }
37
38
39 class BFDState(NumericConstant):
40     """ BFD State """
41     admin_down = 0
42     down = 1
43     init = 2
44     up = 3
45
46     desc_dict = {
47         admin_down: "AdminDown",
48         down: "Down",
49         init: "Init",
50         up: "Up",
51     }
52
53
54 class BFDAuthType(NumericConstant):
55     """ BFD Authentication Type """
56     no_auth = 0
57     simple_pwd = 1
58     keyed_md5 = 2
59     meticulous_keyed_md5 = 3
60     keyed_sha1 = 4
61     meticulous_keyed_sha1 = 5
62
63     desc_dict = {
64         no_auth: "No authentication",
65         simple_pwd: "Simple Password",
66         keyed_md5: "Keyed MD5",
67         meticulous_keyed_md5: "Meticulous Keyed MD5",
68         keyed_sha1: "Keyed SHA1",
69         meticulous_keyed_sha1: "Meticulous Keyed SHA1",
70     }
71
72
73 def bfd_is_auth_used(pkt):
74     """ is packet authenticated? """
75     return "A" in pkt.sprintf("%BFD.flags%")
76
77
78 def bfd_is_simple_pwd_used(pkt):
79     """ is simple password authentication used? """
80     return bfd_is_auth_used(pkt) and pkt.auth_type == BFDAuthType.simple_pwd
81
82
83 def bfd_is_sha1_used(pkt):
84     """ is sha1 authentication used? """
85     return bfd_is_auth_used(pkt) and pkt.auth_type in \
86         (BFDAuthType.keyed_sha1, BFDAuthType.meticulous_keyed_sha1)
87
88
89 def bfd_is_md5_used(pkt):
90     """ is md5 authentication used? """
91     return bfd_is_auth_used(pkt) and pkt.auth_type in \
92         (BFDAuthType.keyed_md5, BFDAuthType.meticulous_keyed_md5)
93
94
95 def bfd_is_md5_or_sha1_used(pkt):
96     """ is md5 or sha1 used? """
97     return bfd_is_md5_used(pkt) or bfd_is_sha1_used(pkt)
98
99
100 class BFD(Packet):
101     """ BFD protocol layer for scapy """
102
103     udp_dport = 3784  #: BFD destination port per RFC 5881
104     udp_dport_echo = 3785  # : BFD destination port for ECHO per RFC 5881
105     udp_sport_min = 49152  #: BFD source port min value per RFC 5881
106     udp_sport_max = 65535  #: BFD source port max value per RFC 5881
107     bfd_pkt_len = 24  # : length of BFD pkt without authentication section
108     sha1_auth_len = 28  # : length of authentication section if SHA1 used
109
110     name = "BFD"
111
112     fields_desc = [
113         BitField("version", 1, 3),
114         BitEnumField("diag", 0, 5, BFDDiagCode.desc_dict),
115         BitEnumField("state", 0, 2, BFDState.desc_dict),
116         FlagsField("flags", 0, 6, ['M', 'D', 'A', 'C', 'F', 'P']),
117         XByteField("detect_mult", 0),
118         BitField("length", bfd_pkt_len, 8),
119         BitField("my_discriminator", 0, 32),
120         BitField("your_discriminator", 0, 32),
121         BitField("desired_min_tx_interval", 0, 32),
122         BitField("required_min_rx_interval", 0, 32),
123         BitField("required_min_echo_rx_interval", 0, 32),
124         ConditionalField(
125             BitEnumField("auth_type", 0, 8, BFDAuthType.desc_dict),
126             bfd_is_auth_used),
127         ConditionalField(BitField("auth_len", 0, 8), bfd_is_auth_used),
128         ConditionalField(BitField("auth_key_id", 0, 8), bfd_is_auth_used),
129         ConditionalField(BitField("auth_reserved", 0, 8),
130                          bfd_is_md5_or_sha1_used),
131         ConditionalField(
132             BitField("auth_seq_num", 0, 32), bfd_is_md5_or_sha1_used),
133         ConditionalField(StrField("auth_key_hash", "0" * 16), bfd_is_md5_used),
134         ConditionalField(
135             StrField("auth_key_hash", "0" * 20), bfd_is_sha1_used),
136     ]
137
138     def mysummary(self):
139         return self.sprintf("BFD(my_disc=%BFD.my_discriminator%,"
140                             "your_disc=%BFD.your_discriminator%)")
141
142
143 # glue the BFD packet class to scapy parser
144 bind_layers(UDP, BFD, dport=BFD.udp_dport)
145
146
147 class BFD_vpp_echo(Packet):
148     """ BFD echo packet as used by VPP (non-rfc, as rfc doesn't define one) """
149
150     udp_dport = 3785  #: BFD echo destination port per RFC 5881
151     name = "BFD_VPP_ECHO"
152
153     fields_desc = [
154         BitField("discriminator", 0, 32),
155         BitField("expire_time_clocks", 0, 64),
156         BitField("checksum", 0, 64)
157     ]
158
159     def mysummary(self):
160         return self.sprintf(
161             "BFD_VPP_ECHO(disc=%BFD_VPP_ECHO.discriminator%,"
162             "expire_time_clocks=%BFD_VPP_ECHO.expire_time_clocks%)")
163
164
165 # glue the BFD echo packet class to scapy parser
166 bind_layers(UDP, BFD_vpp_echo, dport=BFD_vpp_echo.udp_dport)
167
168
169 class VppBFDAuthKey(VppObject):
170     """ Represents BFD authentication key in VPP """
171
172     def __init__(self, test, conf_key_id, auth_type, key):
173         self._test = test
174         self._key = key
175         self._auth_type = auth_type
176         test.assertIn(auth_type, BFDAuthType.desc_dict)
177         self._conf_key_id = conf_key_id
178
179     @property
180     def test(self):
181         """ Test which created this key """
182         return self._test
183
184     @property
185     def auth_type(self):
186         """ Authentication type for this key """
187         return self._auth_type
188
189     @property
190     def key(self):
191         """ key data """
192         return self._key
193
194     @key.setter
195     def key(self, value):
196         self._key = value
197
198     @property
199     def conf_key_id(self):
200         """ configuration key ID """
201         return self._conf_key_id
202
203     def add_vpp_config(self):
204         self.test.vapi.bfd_auth_set_key(
205             self._conf_key_id, self._auth_type, self._key)
206         self._test.registry.register(self, self.test.logger)
207
208     def get_bfd_auth_keys_dump_entry(self):
209         """ get the entry in the auth keys dump corresponding to this key """
210         result = self.test.vapi.bfd_auth_keys_dump()
211         for k in result:
212             if k.conf_key_id == self._conf_key_id:
213                 return k
214         return None
215
216     def query_vpp_config(self):
217         return self.get_bfd_auth_keys_dump_entry() is not None
218
219     def remove_vpp_config(self):
220         self.test.vapi.bfd_auth_del_key(self._conf_key_id)
221
222     def object_id(self):
223         return "bfd-auth-key-%s" % self._conf_key_id
224
225
226 class VppBFDUDPSession(VppObject):
227     """ Represents BFD UDP session in VPP """
228
229     def __init__(self, test, interface, peer_addr, local_addr=None, af=AF_INET,
230                  desired_min_tx=300000, required_min_rx=300000, detect_mult=3,
231                  sha1_key=None, bfd_key_id=None, is_tunnel=False):
232         self._test = test
233         self._interface = interface
234         self._af = af
235         self._local_addr = local_addr
236         if local_addr is not None:
237             self._local_addr_n = inet_pton(af, local_addr)
238         else:
239             self._local_addr_n = None
240         self._peer_addr = peer_addr
241         self._peer_addr_n = inet_pton(af, peer_addr)
242         self._desired_min_tx = desired_min_tx
243         self._required_min_rx = required_min_rx
244         self._detect_mult = detect_mult
245         self._sha1_key = sha1_key
246         if bfd_key_id is not None:
247             self._bfd_key_id = bfd_key_id
248         else:
249             self._bfd_key_id = randint(0, 255)
250         self._is_tunnel = is_tunnel
251
252     @property
253     def test(self):
254         """ Test which created this session """
255         return self._test
256
257     @property
258     def interface(self):
259         """ Interface on which this session lives """
260         return self._interface
261
262     @property
263     def af(self):
264         """ Address family - AF_INET or AF_INET6 """
265         return self._af
266
267     @property
268     def local_addr(self):
269         """ BFD session local address (VPP address) """
270         if self._local_addr is None:
271             if self.af == AF_INET:
272                 return self._interface.local_ip4
273             elif self.af == AF_INET6:
274                 return self._interface.local_ip6
275             else:
276                 raise Exception("Unexpected af '%s'" % self.af)
277         return self._local_addr
278
279     @property
280     def local_addr_n(self):
281         """ BFD session local address (VPP address) - raw, suitable for API """
282         if self._local_addr is None:
283             if self.af == AF_INET:
284                 return self._interface.local_ip4n
285             elif self.af == AF_INET6:
286                 return self._interface.local_ip6n
287             else:
288                 raise Exception("Unexpected af '%s'" % self.af)
289         return self._local_addr_n
290
291     @property
292     def peer_addr(self):
293         """ BFD session peer address """
294         return self._peer_addr
295
296     @property
297     def peer_addr_n(self):
298         """ BFD session peer address - raw, suitable for API """
299         return self._peer_addr_n
300
301     def get_bfd_udp_session_dump_entry(self):
302         """ get the namedtuple entry from bfd udp session dump """
303         result = self.test.vapi.bfd_udp_session_dump()
304         for s in result:
305             self.test.logger.debug("session entry: %s" % str(s))
306             if s.sw_if_index == self.interface.sw_if_index:
307                 if self.af == AF_INET \
308                         and s.is_ipv6 == 0 \
309                         and self.interface.local_ip4n == s.local_addr[:4] \
310                         and self.interface.remote_ip4n == s.peer_addr[:4]:
311                     return s
312                 if self.af == AF_INET6 \
313                         and s.is_ipv6 == 1 \
314                         and self.interface.local_ip6n == s.local_addr \
315                         and self.interface.remote_ip6n == s.peer_addr:
316                     return s
317         return None
318
319     @property
320     def state(self):
321         """ BFD session state """
322         session = self.get_bfd_udp_session_dump_entry()
323         if session is None:
324             raise Exception("Could not find BFD session in VPP response")
325         return session.state
326
327     @property
328     def desired_min_tx(self):
329         """ desired minimum tx interval """
330         return self._desired_min_tx
331
332     @property
333     def required_min_rx(self):
334         """ required minimum rx interval """
335         return self._required_min_rx
336
337     @property
338     def detect_mult(self):
339         """ detect multiplier """
340         return self._detect_mult
341
342     @property
343     def sha1_key(self):
344         """ sha1 key """
345         return self._sha1_key
346
347     @property
348     def bfd_key_id(self):
349         """ bfd key id in use """
350         return self._bfd_key_id
351
352     @property
353     def is_tunnel(self):
354         return self._is_tunnel
355
356     def activate_auth(self, key, bfd_key_id=None, delayed=False):
357         """ activate authentication for this session """
358         self._bfd_key_id = bfd_key_id if bfd_key_id else randint(0, 255)
359         self._sha1_key = key
360         is_ipv6 = 1 if AF_INET6 == self.af else 0
361         conf_key_id = self._sha1_key.conf_key_id
362         is_delayed = 1 if delayed else 0
363         self.test.vapi.bfd_udp_auth_activate(self._interface.sw_if_index,
364                                              self.local_addr_n,
365                                              self.peer_addr_n,
366                                              is_ipv6=is_ipv6,
367                                              bfd_key_id=self._bfd_key_id,
368                                              conf_key_id=conf_key_id,
369                                              is_delayed=is_delayed)
370
371     def deactivate_auth(self, delayed=False):
372         """ deactivate authentication """
373         self._bfd_key_id = None
374         self._sha1_key = None
375         is_delayed = 1 if delayed else 0
376         is_ipv6 = 1 if AF_INET6 == self.af else 0
377         self.test.vapi.bfd_udp_auth_deactivate(self._interface.sw_if_index,
378                                                self.local_addr_n,
379                                                self.peer_addr_n,
380                                                is_ipv6=is_ipv6,
381                                                is_delayed=is_delayed)
382
383     def modify_parameters(self,
384                           detect_mult=None,
385                           desired_min_tx=None,
386                           required_min_rx=None):
387         """ modify session parameters """
388         if detect_mult:
389             self._detect_mult = detect_mult
390         if desired_min_tx:
391             self._desired_min_tx = desired_min_tx
392         if required_min_rx:
393             self._required_min_rx = required_min_rx
394         is_ipv6 = 1 if AF_INET6 == self.af else 0
395         self.test.vapi.bfd_udp_mod(self._interface.sw_if_index,
396                                    self.desired_min_tx,
397                                    self.required_min_rx,
398                                    self.detect_mult,
399                                    self.local_addr_n,
400                                    self.peer_addr_n,
401                                    is_ipv6=is_ipv6)
402
403     def add_vpp_config(self):
404         is_ipv6 = 1 if AF_INET6 == self.af else 0
405         bfd_key_id = self._bfd_key_id if self._sha1_key else None
406         conf_key_id = self._sha1_key.conf_key_id if self._sha1_key else None
407         self.test.vapi.bfd_udp_add(self._interface.sw_if_index,
408                                    self.desired_min_tx,
409                                    self.required_min_rx,
410                                    self.detect_mult,
411                                    self.local_addr_n,
412                                    self.peer_addr_n,
413                                    is_ipv6=is_ipv6,
414                                    bfd_key_id=bfd_key_id,
415                                    conf_key_id=conf_key_id)
416         self._test.registry.register(self, self.test.logger)
417
418     def query_vpp_config(self):
419         session = self.get_bfd_udp_session_dump_entry()
420         return session is not None
421
422     def remove_vpp_config(self):
423         is_ipv6 = 1 if AF_INET6 == self._af else 0
424         self.test.vapi.bfd_udp_del(self._interface.sw_if_index,
425                                    self.local_addr_n,
426                                    self.peer_addr_n,
427                                    is_ipv6=is_ipv6)
428
429     def object_id(self):
430         return "bfd-udp-%s-%s-%s-%s" % (self._interface.sw_if_index,
431                                         self.local_addr,
432                                         self.peer_addr,
433                                         self.af)
434
435     def admin_up(self):
436         """ set bfd session admin-up """
437         is_ipv6 = 1 if AF_INET6 == self._af else 0
438         self.test.vapi.bfd_udp_session_set_flags(1,
439                                                  self._interface.sw_if_index,
440                                                  self.local_addr_n,
441                                                  self.peer_addr_n,
442                                                  is_ipv6=is_ipv6)
443
444     def admin_down(self):
445         """ set bfd session admin-down """
446         is_ipv6 = 1 if AF_INET6 == self._af else 0
447         self.test.vapi.bfd_udp_session_set_flags(0,
448                                                  self._interface.sw_if_index,
449                                                  self.local_addr_n,
450                                                  self.peer_addr_n,
451                                                  is_ipv6=is_ipv6)