1 # Copyright (c) 2023 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_v2"
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_v2"
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 # Cannot use get_replies due to VPP-2203.
258 papi_exec.add(cmd1, **args1).get_reply(err_msg)
259 papi_exec.add(cmd2, **args2).get_reply(err_msg)
260 papi_exec.add(cmd2, **args3).get_reply(err_msg)
263 def vpp_setup_bidirectional_cross_connect(node, interface1, interface2):
264 """Create bidirectional cross-connect between 2 interfaces on vpp node.
266 :param node: Node to add bidirectional cross-connect.
267 :param interface1: First interface name or sw_if_index.
268 :param interface2: Second interface name or sw_if_index.
270 :type interface1: str or int
271 :type interface2: str or int
273 if isinstance(interface1, str):
274 sw_iface1 = Topology().get_interface_sw_index(node, interface1)
276 sw_iface1 = interface1
278 if isinstance(interface2, str):
279 sw_iface2 = Topology().get_interface_sw_index(node, interface2)
281 sw_iface2 = interface2
283 cmd = u"sw_interface_set_l2_xconnect"
285 rx_sw_if_index=sw_iface1,
286 tx_sw_if_index=sw_iface2,
290 rx_sw_if_index=sw_iface2,
291 tx_sw_if_index=sw_iface1,
294 err_msg = f"Failed to add L2 cross-connect between two interfaces " \
295 f"on host {node['host']}"
297 with PapiSocketExecutor(node) as papi_exec:
298 # Cannot use get_replies due to VPP-2203.
299 papi_exec.add(cmd, **args1).get_reply(err_msg)
300 papi_exec.add(cmd, **args2).get_reply(err_msg)
303 def vpp_setup_bidirectional_l2_patch(node, interface1, interface2):
304 """Create bidirectional l2 patch between 2 interfaces on vpp node.
306 :param node: Node to add bidirectional l2 patch.
307 :param interface1: First interface name or sw_if_index.
308 :param interface2: Second interface name or sw_if_index.
310 :type interface1: str or int
311 :type interface2: str or int
313 if isinstance(interface1, str):
314 sw_iface1 = Topology().get_interface_sw_index(node, interface1)
316 sw_iface1 = interface1
318 if isinstance(interface2, str):
319 sw_iface2 = Topology().get_interface_sw_index(node, interface2)
321 sw_iface2 = interface2
323 cmd = u"l2_patch_add_del"
325 rx_sw_if_index=sw_iface1,
326 tx_sw_if_index=sw_iface2,
330 rx_sw_if_index=sw_iface2,
331 tx_sw_if_index=sw_iface1,
334 err_msg = f"Failed to add L2 patch between two interfaces " \
335 f"on host {node['host']}"
337 with PapiSocketExecutor(node) as papi_exec:
338 # Cannot use get_replies due to VPP-2203.
339 papi_exec.add(cmd, **args1).get_reply(err_msg)
340 papi_exec.add(cmd, **args2).get_reply(err_msg)
343 def linux_add_bridge(node, br_name, if_1, if_2, set_up=True):
344 """Bridge two interfaces on linux node.
346 :param node: Node to add bridge on.
347 :param br_name: Bridge name.
348 :param if_1: First interface to be added to the bridge.
349 :param if_2: Second interface to be added to the bridge.
350 :param set_up: Change bridge interface state to up after create bridge.
351 Optional. Default: True.
358 cmd = f"brctl addbr {br_name}"
359 exec_cmd_no_error(node, cmd, sudo=True)
361 cmd = f"brctl addif {br_name} {if_1}"
362 exec_cmd_no_error(node, cmd, sudo=True)
364 cmd = f"brctl addif {br_name} {if_2}"
365 exec_cmd_no_error(node, cmd, sudo=True)
368 cmd = f"ip link set dev {br_name} up"
369 exec_cmd_no_error(node, cmd, sudo=True)
372 def linux_del_bridge(node, br_name, set_down=True):
373 """Delete bridge from linux node.
375 ..note:: The network interface corresponding to the bridge must be
376 down before it can be deleted!
378 :param node: Node to delete bridge from.
379 :param br_name: Bridge name.
380 :param set_down: Change bridge interface state to down before delbr
381 command. Optional. Default: True.
387 cmd = f"ip link set dev {br_name} down"
388 exec_cmd_no_error(node, cmd, sudo=True)
390 cmd = f"brctl delbr {br_name}"
391 exec_cmd_no_error(node, cmd, sudo=True)
394 def vpp_get_bridge_domain_data(node, bd_id=Constants.BITWISE_NON_ZERO):
395 """Get all bridge domain data from a VPP node. If a domain ID number is
396 provided, return only data for the matching bridge domain.
398 :param node: VPP node to get bridge domain data from.
399 :param bd_id: Numeric ID of a specific bridge domain.
402 :returns: List of dictionaries containing data for each bridge domain,
403 or a single dictionary for the specified bridge domain.
406 cmd = u"bridge_domain_dump"
410 err_msg = f"Failed to get L2FIB dump on host {node[u'host']}"
412 with PapiSocketExecutor(node) as papi_exec:
413 details = papi_exec.add(cmd, **args).get_details(err_msg)
415 retval = details if bd_id == Constants.BITWISE_NON_ZERO else None
417 for bridge_domain in details:
418 if bridge_domain[u"bd_id"] == bd_id:
419 retval = bridge_domain
424 def l2_vlan_tag_rewrite(
425 node, interface, tag_rewrite_method, push_dot1q=True, tag1_id=None,
427 """Rewrite tags in ethernet frame.
429 :param node: Node to rewrite tags.
430 :param interface: Interface on which rewrite tags.
431 :param tag_rewrite_method: Method of tag rewrite.
432 :param push_dot1q: Optional parameter to disable to push dot1q tag
434 :param tag1_id: Optional tag1 ID for VLAN.
435 :param tag2_id: Optional tag2 ID for VLAN.
437 :type interface: str or int
438 :type tag_rewrite_method: str
439 :type push_dot1q: bool
443 tag1_id = int(tag1_id) if tag1_id else 0
444 tag2_id = int(tag2_id) if tag2_id else 0
447 L2VtrOp, f"L2_VTR_{tag_rewrite_method.replace(u'-', u'_').upper()}"
450 if isinstance(interface, str):
451 iface_key = Topology.get_interface_by_name(node, interface)
452 sw_if_index = Topology.get_interface_sw_index(node, iface_key)
454 sw_if_index = interface
456 cmd = u"l2_interface_vlan_tag_rewrite"
458 sw_if_index=sw_if_index,
459 vtr_op=int(vtr_oper),
460 push_dot1q=int(push_dot1q),
464 err_msg = f"Failed to set VLAN TAG rewrite on host {node['host']}"
466 with PapiSocketExecutor(node) as papi_exec:
467 papi_exec.add(cmd, **args).get_reply(err_msg)
470 def get_l2_fib_table(node, bd_id):
471 """Retrieves the L2 FIB table.
473 :param node: VPP node.
474 :param bd_id: Index of the bridge domain.
477 :returns: L2 FIB table.
480 cmd = u"l2_fib_table_dump"
484 err_msg = f"Failed to get L2FIB dump on host {node['host']}"
486 with PapiSocketExecutor(node) as papi_exec:
487 details = papi_exec.add(cmd, **args).get_details(err_msg)
489 for fib_item in details:
490 fib_item[u"mac"] = L2Util.bin_to_mac(fib_item[u"mac"])
495 def get_l2_fib_entry_by_mac(node, bd_index, mac):
496 """Retrieves the L2 FIB entry specified by MAC address using PAPI.
498 :param node: VPP node.
499 :param bd_index: Index of the bridge domain.
500 :param mac: MAC address used as the key in L2 FIB data structure.
504 :returns: L2 FIB entry
507 bd_data = L2Util.vpp_get_bridge_domain_data(node)
508 bd_id = bd_data[bd_index-1][u"bd_id"]
510 table = L2Util.get_l2_fib_table(node, bd_id)
513 if entry[u"mac"] == mac: