1 # Copyright (c) 2021 Cisco and/or its affiliates.
2 # Licensed under the Apache License, Version 2.0 (the "License");
3 # you may not use this file except in compliance with the License.
4 # You may obtain a copy of the License at:
6 # http://www.apache.org/licenses/LICENSE-2.0
8 # Unless required by applicable law or agreed to in writing, software
9 # distributed under the License is distributed on an "AS IS" BASIS,
10 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 # See the License for the specific language governing permissions and
12 # limitations under the License.
14 """L2 Utilities Library."""
16 from enum import IntEnum
18 from resources.libraries.python.Constants import Constants
19 from resources.libraries.python.PapiExecutor import PapiSocketExecutor
20 from resources.libraries.python.topology import Topology
21 from resources.libraries.python.ssh import exec_cmd_no_error
24 class L2VtrOp(IntEnum):
25 """VLAN tag rewrite operation."""
31 L2_VTR_TRANSLATE_1_1 = 5
32 L2_VTR_TRANSLATE_1_2 = 6
33 L2_VTR_TRANSLATE_2_1 = 7
34 L2_VTR_TRANSLATE_2_2 = 8
38 """Utilities for l2 configuration."""
41 def mac_to_int(mac_str):
42 """Convert MAC address from string format (e.g. 01:02:03:04:05:06) to
43 integer representation (1108152157446).
45 :param mac_str: MAC address in string representation.
47 :returns: Integer representation of MAC address.
50 return int(mac_str.replace(u":", u""), 16)
53 def int_to_mac(mac_int):
54 """Convert MAC address from integer representation (e.g. 1108152157446)
55 to string format (01:02:03:04:05:06).
57 :param mac_int: MAC address in integer representation.
59 :returns: String representation of MAC address.
63 f"{hex(mac_int)[2:]:0>12}"[i:i+2] for i in range(0, 12, 2)
67 def mac_to_bin(mac_str):
68 """Convert MAC address from string format (e.g. 01:02:03:04:05:06) to
69 binary representation (\x01\x02\x03\x04\x05\x06).
71 :param mac_str: MAC address in string representation.
73 :returns: Binary representation of MAC address.
76 return bytes.fromhex(mac_str.replace(u":", u""))
79 def bin_to_mac(mac_bin):
80 """Convert MAC address from binary representation
81 (\x01\x02\x03\x04\x05\x06) to string format (e.g. 01:02:03:04:05:06).
83 :param mac_bin: MAC address in binary representation.
85 :returns: String representation of MAC address.
88 return u":".join(mac_bin.hex()[i:i + 2] for i in range(0, 12, 2))
91 def vpp_add_l2fib_entry(
92 node, mac, interface, bd_id, static_mac=1, filter_mac=0, bvi_mac=0):
93 """ Create a static L2FIB entry on a VPP node.
95 :param node: Node to add L2FIB entry on.
96 :param mac: Destination mac address in string format 01:02:03:04:05:06.
97 :param interface: Interface name or sw_if_index.
98 :param bd_id: Bridge domain index.
99 :param static_mac: Set to 1 to create static MAC entry.
101 :param filter_mac: Set to 1 to drop packet that's source or destination
102 MAC address contains defined MAC address. (Default value = 0)
103 :param bvi_mac: Set to 1 to create entry that points to BVI interface.
107 :type interface: str or int
108 :type bd_id: int or str
109 :type static_mac: int or str
110 :type filter_mac: int or str
111 :type bvi_mac: int or str
113 if isinstance(interface, str):
114 sw_if_index = Topology.get_interface_sw_index(node, interface)
116 sw_if_index = interface
118 cmd = u"l2fib_add_del"
119 err_msg = f"Failed to add L2FIB entry on host {node[u'host']}"
121 mac=L2Util.mac_to_bin(mac),
123 sw_if_index=sw_if_index,
125 static_mac=int(static_mac),
126 filter_mac=int(filter_mac),
130 with PapiSocketExecutor(node) as papi_exec:
131 papi_exec.add(cmd, **args).get_reply(err_msg)
135 node, bd_id, flood=True, uu_flood=True, forward=True, learn=True,
137 """Create an L2 bridge domain on a VPP node.
139 :param node: Node where we wish to crate the L2 bridge domain.
140 :param bd_id: Bridge domain index.
141 :param flood: Enable/disable bcast/mcast flooding in the BD.
143 :param uu_flood: Enable/disable unknown unicast flood in the BD.
145 :param forward: Enable/disable forwarding on all interfaces in
146 the BD. (Default value = 1)
147 :param learn: Enable/disable MAC learning on all interfaces in the BD.
149 :param arp_term: Enable/disable arp termination in the BD.
152 :type bd_id: int or str
159 cmd = u"bridge_domain_add_del"
160 err_msg = f"Failed to create L2 bridge domain on host {node[u'host']}"
170 with PapiSocketExecutor(node) as papi_exec:
171 papi_exec.add(cmd, **args).get_reply(err_msg)
174 def add_interface_to_l2_bd(node, interface, bd_id, shg=0, port_type=0):
175 """Add an interface to the L2 bridge domain.
177 Get SW IF ID and add it to the bridge domain.
179 :param node: Node where we want to execute the command that does this.
180 :param interface: Interface name.
181 :param bd_id: Bridge domain index.
182 :param shg: Split-horizon group index. (Default value = 0)
183 :param port_type: Port mode: 0 - normal, 1 - BVI, 2 - UU_FWD.
187 :type bd_id: int or str
188 :type shg: int or str
189 :type port_type: int or str
191 sw_if_index = Topology.get_interface_sw_index(node, interface)
193 cmd = u"sw_interface_set_l2_bridge"
194 err_msg = f"Failed to add interface {interface} to L2 bridge domain " \
195 f"on host {node[u'host']}"
197 rx_sw_if_index=sw_if_index,
200 port_type=int(port_type),
204 with PapiSocketExecutor(node) as papi_exec:
205 papi_exec.add(cmd, **args).get_reply(err_msg)
208 def vpp_add_l2_bridge_domain(node, bd_id, port_1, port_2, learn=True):
209 """Add L2 bridge domain with 2 interfaces to the VPP node.
211 :param node: Node to add L2BD on.
212 :param bd_id: Bridge domain ID.
213 :param port_1: First interface name added to L2BD.
214 :param port_2: Second interface name added to L2BD.
215 :param learn: Enable/disable MAC learn.
222 sw_if_index1 = Topology.get_interface_sw_index(node, port_1)
223 sw_if_index2 = Topology.get_interface_sw_index(node, port_2)
225 cmd1 = u"bridge_domain_add_del"
236 cmd2 = u"sw_interface_set_l2_bridge"
238 rx_sw_if_index=sw_if_index1,
246 rx_sw_if_index=sw_if_index2,
253 err_msg = f"Failed to add L2 bridge domain with 2 interfaces " \
254 f"on host {node[u'host']}"
256 with PapiSocketExecutor(node) as papi_exec:
257 papi_exec.add(cmd1, **args1).add(cmd2, **args2).add(cmd2, **args3)
258 papi_exec.get_replies(err_msg)
261 def vpp_setup_bidirectional_cross_connect(node, interface1, interface2):
262 """Create bidirectional cross-connect between 2 interfaces on vpp node.
264 :param node: Node to add bidirectional cross-connect.
265 :param interface1: First interface name or sw_if_index.
266 :param interface2: Second interface name or sw_if_index.
268 :type interface1: str or int
269 :type interface2: str or int
271 if isinstance(interface1, str):
272 sw_iface1 = Topology().get_interface_sw_index(node, interface1)
274 sw_iface1 = interface1
276 if isinstance(interface2, str):
277 sw_iface2 = Topology().get_interface_sw_index(node, interface2)
279 sw_iface2 = interface2
281 cmd = u"sw_interface_set_l2_xconnect"
283 rx_sw_if_index=sw_iface1,
284 tx_sw_if_index=sw_iface2,
288 rx_sw_if_index=sw_iface2,
289 tx_sw_if_index=sw_iface1,
292 err_msg = f"Failed to add L2 cross-connect between two interfaces " \
293 f"on host {node['host']}"
295 with PapiSocketExecutor(node) as papi_exec:
296 papi_exec.add(cmd, **args1).add(cmd, **args2).get_replies(err_msg)
299 def vpp_setup_bidirectional_l2_patch(node, interface1, interface2):
300 """Create bidirectional l2 patch between 2 interfaces on vpp node.
302 :param node: Node to add bidirectional l2 patch.
303 :param interface1: First interface name or sw_if_index.
304 :param interface2: Second interface name or sw_if_index.
306 :type interface1: str or int
307 :type interface2: str or int
309 if isinstance(interface1, str):
310 sw_iface1 = Topology().get_interface_sw_index(node, interface1)
312 sw_iface1 = interface1
314 if isinstance(interface2, str):
315 sw_iface2 = Topology().get_interface_sw_index(node, interface2)
317 sw_iface2 = interface2
319 cmd = u"l2_patch_add_del"
321 rx_sw_if_index=sw_iface1,
322 tx_sw_if_index=sw_iface2,
326 rx_sw_if_index=sw_iface2,
327 tx_sw_if_index=sw_iface1,
330 err_msg = f"Failed to add L2 patch between two interfaces " \
331 f"on host {node['host']}"
333 with PapiSocketExecutor(node) as papi_exec:
334 papi_exec.add(cmd, **args1).add(cmd, **args2).get_replies(err_msg)
337 def linux_add_bridge(node, br_name, if_1, if_2, set_up=True):
338 """Bridge two interfaces on linux node.
340 :param node: Node to add bridge on.
341 :param br_name: Bridge name.
342 :param if_1: First interface to be added to the bridge.
343 :param if_2: Second interface to be added to the bridge.
344 :param set_up: Change bridge interface state to up after create bridge.
345 Optional. Default: True.
352 cmd = f"brctl addbr {br_name}"
353 exec_cmd_no_error(node, cmd, sudo=True)
355 cmd = f"brctl addif {br_name} {if_1}"
356 exec_cmd_no_error(node, cmd, sudo=True)
358 cmd = f"brctl addif {br_name} {if_2}"
359 exec_cmd_no_error(node, cmd, sudo=True)
362 cmd = f"ip link set dev {br_name} up"
363 exec_cmd_no_error(node, cmd, sudo=True)
366 def linux_del_bridge(node, br_name, set_down=True):
367 """Delete bridge from linux node.
369 ..note:: The network interface corresponding to the bridge must be
370 down before it can be deleted!
372 :param node: Node to delete bridge from.
373 :param br_name: Bridge name.
374 :param set_down: Change bridge interface state to down before delbr
375 command. Optional. Default: True.
381 cmd = f"ip link set dev {br_name} down"
382 exec_cmd_no_error(node, cmd, sudo=True)
384 cmd = f"brctl delbr {br_name}"
385 exec_cmd_no_error(node, cmd, sudo=True)
388 def vpp_get_bridge_domain_data(node, bd_id=Constants.BITWISE_NON_ZERO):
389 """Get all bridge domain data from a VPP node. If a domain ID number is
390 provided, return only data for the matching bridge domain.
392 :param node: VPP node to get bridge domain data from.
393 :param bd_id: Numeric ID of a specific bridge domain.
396 :returns: List of dictionaries containing data for each bridge domain,
397 or a single dictionary for the specified bridge domain.
400 cmd = u"bridge_domain_dump"
404 err_msg = f"Failed to get L2FIB dump on host {node[u'host']}"
406 with PapiSocketExecutor(node) as papi_exec:
407 details = papi_exec.add(cmd, **args).get_details(err_msg)
409 retval = details if bd_id == Constants.BITWISE_NON_ZERO else None
411 for bridge_domain in details:
412 if bridge_domain[u"bd_id"] == bd_id:
413 retval = bridge_domain
418 def l2_vlan_tag_rewrite(
419 node, interface, tag_rewrite_method, push_dot1q=True, tag1_id=None,
421 """Rewrite tags in ethernet frame.
423 :param node: Node to rewrite tags.
424 :param interface: Interface on which rewrite tags.
425 :param tag_rewrite_method: Method of tag rewrite.
426 :param push_dot1q: Optional parameter to disable to push dot1q tag
428 :param tag1_id: Optional tag1 ID for VLAN.
429 :param tag2_id: Optional tag2 ID for VLAN.
431 :type interface: str or int
432 :type tag_rewrite_method: str
433 :type push_dot1q: bool
437 tag1_id = int(tag1_id) if tag1_id else 0
438 tag2_id = int(tag2_id) if tag2_id else 0
441 L2VtrOp, f"L2_VTR_{tag_rewrite_method.replace(u'-', u'_').upper()}"
444 if isinstance(interface, str):
445 iface_key = Topology.get_interface_by_name(node, interface)
446 sw_if_index = Topology.get_interface_sw_index(node, iface_key)
448 sw_if_index = interface
450 cmd = u"l2_interface_vlan_tag_rewrite"
452 sw_if_index=sw_if_index,
453 vtr_op=int(vtr_oper),
454 push_dot1q=int(push_dot1q),
458 err_msg = f"Failed to set VLAN TAG rewrite on host {node['host']}"
460 with PapiSocketExecutor(node) as papi_exec:
461 papi_exec.add(cmd, **args).get_reply(err_msg)
464 def get_l2_fib_table(node, bd_id):
465 """Retrieves the L2 FIB table.
467 :param node: VPP node.
468 :param bd_id: Index of the bridge domain.
471 :returns: L2 FIB table.
474 cmd = u"l2_fib_table_dump"
478 err_msg = f"Failed to get L2FIB dump on host {node['host']}"
480 with PapiSocketExecutor(node) as papi_exec:
481 details = papi_exec.add(cmd, **args).get_details(err_msg)
483 for fib_item in details:
484 fib_item[u"mac"] = L2Util.bin_to_mac(fib_item[u"mac"])
489 def get_l2_fib_entry_by_mac(node, bd_index, mac):
490 """Retrieves the L2 FIB entry specified by MAC address using PAPI.
492 :param node: VPP node.
493 :param bd_index: Index of the bridge domain.
494 :param mac: MAC address used as the key in L2 FIB data structure.
498 :returns: L2 FIB entry
501 bd_data = L2Util.vpp_get_bridge_domain_data(node)
502 bd_id = bd_data[bd_index-1][u"bd_id"]
504 table = L2Util.get_l2_fib_table(node, bd_id)
507 if entry[u"mac"] == mac: