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