1 # Copyright (c) 2019 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."""
17 from textwrap import wrap
19 from enum import IntEnum
21 from resources.libraries.python.Constants import Constants
22 from resources.libraries.python.PapiExecutor import PapiSocketExecutor
23 from resources.libraries.python.topology import Topology
24 from resources.libraries.python.ssh import exec_cmd_no_error
27 class L2VtrOp(IntEnum):
28 """VLAN tag rewrite operation."""
34 L2_VTR_TRANSLATE_1_1 = 5
35 L2_VTR_TRANSLATE_1_2 = 6
36 L2_VTR_TRANSLATE_2_1 = 7
37 L2_VTR_TRANSLATE_2_2 = 8
41 """Utilities for l2 configuration."""
44 def mac_to_int(mac_str):
45 """Convert MAC address from string format (e.g. 01:02:03:04:05:06) to
46 integer representation (1108152157446).
48 :param mac_str: MAC address in string representation.
50 :returns: Integer representation of MAC address.
53 return int(mac_str.replace(':', ''), 16)
56 def int_to_mac(mac_int):
57 """Convert MAC address from integer representation (e.g. 1108152157446)
58 to string format (01:02:03:04:05:06).
60 :param mac_int: MAC address in integer representation.
62 :returns: String representation of MAC address.
65 return ':'.join(wrap("{:012x}".format(mac_int), width=2))
68 def mac_to_bin(mac_str):
69 """Convert MAC address from string format (e.g. 01:02:03:04:05:06) to
70 binary representation (\x01\x02\x03\x04\x05\x06).
72 :param mac_str: MAC address in string representation.
74 :returns: Binary representation of MAC address.
77 return binascii.unhexlify(mac_str.replace(':', ''))
80 def bin_to_mac(mac_bin):
81 """Convert MAC address from binary representation
82 (\x01\x02\x03\x04\x05\x06) to string format (e.g. 01:02:03:04:05:06).
84 :param mac_bin: MAC address in binary representation.
86 :returns: String representation of MAC address.
89 mac_str = ':'.join(binascii.hexlify(mac_bin)[i:i + 2]
90 for i in range(0, 12, 2))
91 return str(mac_str.decode('ascii'))
94 def vpp_add_l2fib_entry(node, mac, interface, bd_id, static_mac=1,
95 filter_mac=0, bvi_mac=0):
96 """ Create a static L2FIB entry on a VPP node.
98 :param node: Node to add L2FIB entry on.
99 :param mac: Destination mac address in string format 01:02:03:04:05:06.
100 :param interface: Interface name or sw_if_index.
101 :param bd_id: Bridge domain index.
102 :param static_mac: Set to 1 to create static MAC entry.
104 :param filter_mac: Set to 1 to drop packet that's source or destination
105 MAC address contains defined MAC address. (Default value = 0)
106 :param bvi_mac: Set to 1 to create entry that points to BVI interface.
110 :type interface: str or int
111 :type bd_id: int or str
112 :type static_mac: int or str
113 :type filter_mac: int or str
114 :type bvi_mac: int or str
117 if isinstance(interface, basestring):
118 sw_if_index = Topology.get_interface_sw_index(node, interface)
120 sw_if_index = interface
122 cmd = 'l2fib_add_del'
123 err_msg = 'Failed to add L2FIB entry on host {host}'.format(
125 args = dict(mac=L2Util.mac_to_bin(mac),
127 sw_if_index=sw_if_index,
129 static_mac=int(static_mac),
130 filter_mac=int(filter_mac),
131 bvi_mac=int(bvi_mac))
132 with PapiSocketExecutor(node) as papi_exec:
133 papi_exec.add(cmd, **args).get_reply(err_msg)
136 def create_l2_bd(node, bd_id, flood=1, uu_flood=1, forward=1, learn=1,
138 """Create an L2 bridge domain on a VPP node.
140 :param node: Node where we wish to crate the L2 bridge domain.
141 :param bd_id: Bridge domain index.
142 :param flood: Enable/disable bcast/mcast flooding in the BD.
144 :param uu_flood: Enable/disable unknown unicast flood in the BD.
146 :param forward: Enable/disable forwarding on all interfaces in
147 the BD. (Default value = 1)
148 :param learn: Enable/disable MAC learning on all interfaces in the BD.
150 :param arp_term: Enable/disable arp termination in the BD.
153 :type bd_id: int or str
154 :type flood: int or str
155 :type uu_flood: int or str
156 :type forward: int or str
157 :type learn: int or str
158 :type arp_term: int or str
161 cmd = 'bridge_domain_add_del'
162 err_msg = 'Failed to create L2 bridge domain on host {host}'.format(
164 args = dict(bd_id=int(bd_id),
166 uu_flood=int(uu_flood),
167 forward=int(forward),
169 arp_term=int(arp_term),
171 with PapiSocketExecutor(node) as papi_exec:
172 papi_exec.add(cmd, **args).get_reply(err_msg)
175 def add_interface_to_l2_bd(node, interface, bd_id, shg=0, port_type=0):
176 """Add an interface to the L2 bridge domain.
178 Get SW IF ID and add it to the bridge domain.
180 :param node: Node where we want to execute the command that does this.
181 :param interface: Interface name.
182 :param bd_id: Bridge domain index.
183 :param shg: Split-horizon group index. (Default value = 0)
184 :param port_type: Port mode: 0 - normal, 1 - BVI, 2 - UU_FWD.
188 :type bd_id: int or str
189 :type shg: int or str
190 :type port_type: int or str
193 sw_if_index = Topology.get_interface_sw_index(node, interface)
195 cmd = 'sw_interface_set_l2_bridge'
196 err_msg = 'Failed to add interface {ifc} to L2 bridge domain on host ' \
197 '{host}'.format(ifc=interface, host=node['host'])
198 args = dict(rx_sw_if_index=sw_if_index,
201 port_type=int(port_type),
203 with PapiSocketExecutor(node) as papi_exec:
204 papi_exec.add(cmd, **args).get_reply(err_msg)
207 def vpp_add_l2_bridge_domain(node, bd_id, port_1, port_2, learn=True):
208 """Add L2 bridge domain with 2 interfaces to the VPP node.
210 :param node: Node to add L2BD on.
211 :param bd_id: Bridge domain ID.
212 :param port_1: First interface name added to L2BD.
213 :param port_2: Second interface name added to L2BD.
214 :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)
224 learn_int = 1 if learn else 0
226 cmd1 = 'bridge_domain_add_del'
227 args1 = dict(bd_id=int(bd_id),
235 cmd2 = 'sw_interface_set_l2_bridge'
236 args2 = dict(rx_sw_if_index=sw_if_index1,
242 args3 = dict(rx_sw_if_index=sw_if_index2,
248 err_msg = 'Failed to add L2 bridge domain with 2 interfaces on host' \
249 ' {host}'.format(host=node['host'])
251 with PapiSocketExecutor(node) as papi_exec:
252 papi_exec.add(cmd1, **args1).add(cmd2, **args2).add(cmd2, **args3)
253 papi_exec.get_replies(err_msg)
256 def vpp_setup_bidirectional_cross_connect(node, interface1, interface2):
257 """Create bidirectional cross-connect between 2 interfaces on vpp node.
259 :param node: Node to add bidirectional cross-connect.
260 :param interface1: First interface name or sw_if_index.
261 :param interface2: Second interface name or sw_if_index.
263 :type interface1: str or int
264 :type interface2: str or int
267 if isinstance(interface1, basestring):
268 sw_iface1 = Topology().get_interface_sw_index(node, interface1)
270 sw_iface1 = interface1
272 if isinstance(interface2, basestring):
273 sw_iface2 = Topology().get_interface_sw_index(node, interface2)
275 sw_iface2 = interface2
277 cmd = 'sw_interface_set_l2_xconnect'
278 args1 = dict(rx_sw_if_index=sw_iface1,
279 tx_sw_if_index=sw_iface2,
281 args2 = dict(rx_sw_if_index=sw_iface2,
282 tx_sw_if_index=sw_iface1,
285 err_msg = 'Failed to add L2 cross-connect between two interfaces on' \
286 ' host {host}'.format(host=node['host'])
288 with PapiSocketExecutor(node) as papi_exec:
289 papi_exec.add(cmd, **args1).add(cmd, **args2).get_replies(err_msg)
292 def vpp_setup_bidirectional_l2_patch(node, interface1, interface2):
293 """Create bidirectional l2 patch between 2 interfaces on vpp node.
295 :param node: Node to add bidirectional l2 patch.
296 :param interface1: First interface name or sw_if_index.
297 :param interface2: Second interface name or sw_if_index.
299 :type interface1: str or int
300 :type interface2: str or int
303 if isinstance(interface1, basestring):
304 sw_iface1 = Topology().get_interface_sw_index(node, interface1)
306 sw_iface1 = interface1
308 if isinstance(interface2, basestring):
309 sw_iface2 = Topology().get_interface_sw_index(node, interface2)
311 sw_iface2 = interface2
313 cmd = 'l2_patch_add_del'
314 args1 = dict(rx_sw_if_index=sw_iface1,
315 tx_sw_if_index=sw_iface2,
317 args2 = dict(rx_sw_if_index=sw_iface2,
318 tx_sw_if_index=sw_iface1,
321 err_msg = 'Failed to add L2 patch between two interfaces on' \
322 ' host {host}'.format(host=node['host'])
324 with PapiSocketExecutor(node) as papi_exec:
325 papi_exec.add(cmd, **args1).add(cmd, **args2).get_replies(err_msg)
328 def linux_add_bridge(node, br_name, if_1, if_2, set_up=True):
329 """Bridge two interfaces on linux node.
331 :param node: Node to add bridge on.
332 :param br_name: Bridge name.
333 :param if_1: First interface to be added to the bridge.
334 :param if_2: Second interface to be added to the bridge.
335 :param set_up: Change bridge interface state to up after create bridge.
336 Optional. Default: True.
344 cmd = 'brctl addbr {0}'.format(br_name)
345 exec_cmd_no_error(node, cmd, sudo=True)
346 cmd = 'brctl addif {0} {1}'.format(br_name, if_1)
347 exec_cmd_no_error(node, cmd, sudo=True)
348 cmd = 'brctl addif {0} {1}'.format(br_name, if_2)
349 exec_cmd_no_error(node, cmd, sudo=True)
351 cmd = 'ip link set dev {0} up'.format(br_name)
352 exec_cmd_no_error(node, cmd, sudo=True)
355 def linux_del_bridge(node, br_name, set_down=True):
356 """Delete bridge from linux node.
358 ..note:: The network interface corresponding to the bridge must be
359 down before it can be deleted!
361 :param node: Node to delete bridge from.
362 :param br_name: Bridge name.
363 :param set_down: Change bridge interface state to down before delbr
364 command. Optional. Default: True.
371 cmd = 'ip link set dev {0} down'.format(br_name)
372 exec_cmd_no_error(node, cmd, sudo=True)
373 cmd = 'brctl delbr {0}'.format(br_name)
374 exec_cmd_no_error(node, cmd, sudo=True)
377 def vpp_get_bridge_domain_data(node, bd_id=0xffffffff):
378 """Get all bridge domain data from a VPP node. If a domain ID number is
379 provided, return only data for the matching bridge domain.
381 :param node: VPP node to get bridge domain data from.
382 :param bd_id: Numeric ID of a specific bridge domain.
385 :returns: List of dictionaries containing data for each bridge domain,
386 or a single dictionary for the specified bridge domain.
390 cmd = 'bridge_domain_dump'
391 args = dict(bd_id=int(bd_id))
392 err_msg = 'Failed to get L2FIB dump on host {host}'.format(
394 with PapiSocketExecutor(node) as papi_exec:
395 details = papi_exec.add(cmd, **args).get_details(err_msg)
397 if bd_id == Constants.BITWISE_NON_ZERO:
399 for bridge_domain in details:
400 if bridge_domain['bd_id'] == bd_id:
404 def l2_vlan_tag_rewrite(node, interface, tag_rewrite_method,
405 push_dot1q=True, tag1_id=None, tag2_id=None):
406 """Rewrite tags in ethernet frame.
408 :param node: Node to rewrite tags.
409 :param interface: Interface on which rewrite tags.
410 :param tag_rewrite_method: Method of tag rewrite.
411 :param push_dot1q: Optional parameter to disable to push dot1q tag
413 :param tag1_id: Optional tag1 ID for VLAN.
414 :param tag2_id: Optional tag2 ID for VLAN.
416 :type interface: str or int
417 :type tag_rewrite_method: str
418 :type push_dot1q: bool
423 tag1_id = int(tag1_id) if tag1_id else 0
424 tag2_id = int(tag2_id) if tag2_id else 0
426 vtr_oper = getattr(L2VtrOp, 'L2_VTR_{}'.format(
427 tag_rewrite_method.replace('-', '_').upper()))
429 if isinstance(interface, basestring):
430 iface_key = Topology.get_interface_by_name(node, interface)
431 sw_if_index = Topology.get_interface_sw_index(node, iface_key)
433 sw_if_index = interface
435 cmd = 'l2_interface_vlan_tag_rewrite'
436 args = dict(sw_if_index=sw_if_index,
437 vtr_op=int(vtr_oper),
438 push_dot1q=int(push_dot1q),
441 err_msg = 'Failed to set VLAN TAG rewrite on host {host}'.format(
443 with PapiSocketExecutor(node) as papi_exec:
444 papi_exec.add(cmd, **args).get_reply(err_msg)
447 def get_l2_fib_table(node, bd_id):
448 """Retrieves the L2 FIB table.
450 :param node: VPP node.
451 :param bd_id: Index of the bridge domain.
454 :returns: L2 FIB table.
458 cmd = 'l2_fib_table_dump'
459 args = dict(bd_id=int(bd_id))
460 err_msg = 'Failed to get L2FIB dump on host {host}'.format(
462 with PapiSocketExecutor(node) as papi_exec:
463 details = papi_exec.add(cmd, **args).get_details(err_msg)
465 for fib_item in details:
466 fib_item['mac'] = L2Util.bin_to_mac(fib_item['mac'])
471 def get_l2_fib_entry_by_mac(node, bd_index, mac):
472 """Retrieves the L2 FIB entry specified by MAC address using PAPI.
474 :param node: VPP node.
475 :param bd_index: Index of the bridge domain.
476 :param mac: MAC address used as the key in L2 FIB data structure.
480 :returns: L2 FIB entry
484 bd_data = L2Util.vpp_get_bridge_domain_data(node)
485 bd_id = bd_data[bd_index-1]['bd_id']
487 table = L2Util.get_l2_fib_table(node, bd_id)
490 if entry['mac'] == mac: