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 PapiExecutor
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 PapiExecutor(node) as papi_exec:
133 papi_exec.add(cmd, **args).get_replies(err_msg).\
134 verify_reply(err_msg=err_msg)
137 def create_l2_bd(node, bd_id, flood=1, uu_flood=1, forward=1, learn=1,
139 """Create an L2 bridge domain on a VPP node.
141 :param node: Node where we wish to crate the L2 bridge domain.
142 :param bd_id: Bridge domain index.
143 :param flood: Enable/disable bcast/mcast flooding in the BD.
145 :param uu_flood: Enable/disable unknown unicast flood in the BD.
147 :param forward: Enable/disable forwarding on all interfaces in
148 the BD. (Default value = 1)
149 :param learn: Enable/disable MAC learning on all interfaces in the BD.
151 :param arp_term: Enable/disable arp termination in the BD.
154 :type bd_id: int or str
155 :type flood: int or str
156 :type uu_flood: int or str
157 :type forward: int or str
158 :type learn: int or str
159 :type arp_term: int or str
162 cmd = 'bridge_domain_add_del'
163 err_msg = 'Failed to create L2 bridge domain on host {host}'.format(
165 args = dict(bd_id=int(bd_id),
167 uu_flood=int(uu_flood),
168 forward=int(forward),
170 arp_term=int(arp_term),
172 with PapiExecutor(node) as papi_exec:
173 papi_exec.add(cmd, **args).get_replies(err_msg).\
174 verify_reply(err_msg=err_msg)
177 def add_interface_to_l2_bd(node, interface, bd_id, shg=0, port_type=0):
178 """Add an interface to the L2 bridge domain.
180 Get SW IF ID and add it to the bridge domain.
182 :param node: Node where we want to execute the command that does this.
183 :param interface: Interface name.
184 :param bd_id: Bridge domain index.
185 :param shg: Split-horizon group index. (Default value = 0)
186 :param port_type: Port mode: 0 - normal, 1 - BVI, 2 - UU_FWD.
190 :type bd_id: int or str
191 :type shg: int or str
192 :type port_type: int or str
195 sw_if_index = Topology.get_interface_sw_index(node, interface)
197 cmd = 'sw_interface_set_l2_bridge'
198 err_msg = 'Failed to add interface {ifc} to L2 bridge domain on host ' \
199 '{host}'.format(ifc=interface, host=node['host'])
200 args = dict(rx_sw_if_index=sw_if_index,
203 port_type=int(port_type),
205 with PapiExecutor(node) as papi_exec:
206 papi_exec.add(cmd, **args).get_replies(err_msg).\
207 verify_reply(err_msg=err_msg)
210 def vpp_add_l2_bridge_domain(node, bd_id, port_1, port_2, learn=True):
211 """Add L2 bridge domain with 2 interfaces to the VPP node.
213 :param node: Node to add L2BD on.
214 :param bd_id: Bridge domain ID.
215 :param port_1: First interface name added to L2BD.
216 :param port_2: Second interface name added to L2BD.
217 :param learn: Enable/disable MAC learn.
225 sw_if_index1 = Topology.get_interface_sw_index(node, port_1)
226 sw_if_index2 = Topology.get_interface_sw_index(node, port_2)
227 learn_int = 1 if learn else 0
229 cmd1 = 'bridge_domain_add_del'
230 args1 = dict(bd_id=int(bd_id),
238 cmd2 = 'sw_interface_set_l2_bridge'
239 args2 = dict(rx_sw_if_index=sw_if_index1,
245 args3 = dict(rx_sw_if_index=sw_if_index2,
251 err_msg = 'Failed to add L2 bridge domain with 2 interfaces on host' \
252 ' {host}'.format(host=node['host'])
254 with PapiExecutor(node) as papi_exec:
255 papi_exec.add(cmd1, **args1).add(cmd2, **args2).add(cmd2, **args3).\
256 get_replies(err_msg).verify_replies(err_msg=err_msg)
259 def vpp_setup_bidirectional_cross_connect(node, interface1, interface2):
260 """Create bidirectional cross-connect between 2 interfaces on vpp node.
262 :param node: Node to add bidirectional cross-connect.
263 :param interface1: First interface name or sw_if_index.
264 :param interface2: Second interface name or sw_if_index.
266 :type interface1: str or int
267 :type interface2: str or int
270 if isinstance(interface1, basestring):
271 sw_iface1 = Topology().get_interface_sw_index(node, interface1)
273 sw_iface1 = interface1
275 if isinstance(interface2, basestring):
276 sw_iface2 = Topology().get_interface_sw_index(node, interface2)
278 sw_iface2 = interface2
280 cmd = 'sw_interface_set_l2_xconnect'
281 args1 = dict(rx_sw_if_index=sw_iface1,
282 tx_sw_if_index=sw_iface2,
284 args2 = dict(rx_sw_if_index=sw_iface2,
285 tx_sw_if_index=sw_iface1,
288 err_msg = 'Failed to add L2 cross-connect between two interfaces on' \
289 ' host {host}'.format(host=node['host'])
291 with PapiExecutor(node) as papi_exec:
292 papi_exec.add(cmd, **args1).add(cmd, **args2).get_replies(err_msg).\
293 verify_replies(err_msg=err_msg)
296 def vpp_setup_bidirectional_l2_patch(node, interface1, interface2):
297 """Create bidirectional l2 patch between 2 interfaces on vpp node.
299 :param node: Node to add bidirectional l2 patch.
300 :param interface1: First interface name or sw_if_index.
301 :param interface2: Second interface name or sw_if_index.
303 :type interface1: str or int
304 :type interface2: str or int
307 if isinstance(interface1, basestring):
308 sw_iface1 = Topology().get_interface_sw_index(node, interface1)
310 sw_iface1 = interface1
312 if isinstance(interface2, basestring):
313 sw_iface2 = Topology().get_interface_sw_index(node, interface2)
315 sw_iface2 = interface2
317 cmd = 'l2_patch_add_del'
318 args1 = dict(rx_sw_if_index=sw_iface1,
319 tx_sw_if_index=sw_iface2,
321 args2 = dict(rx_sw_if_index=sw_iface2,
322 tx_sw_if_index=sw_iface1,
325 err_msg = 'Failed to add L2 patch between two interfaces on' \
326 ' host {host}'.format(host=node['host'])
328 with PapiExecutor(node) as papi_exec:
329 papi_exec.add(cmd, **args1).add(cmd, **args2).get_replies(err_msg).\
330 verify_replies(err_msg=err_msg)
333 def linux_add_bridge(node, br_name, if_1, if_2, set_up=True):
334 """Bridge two interfaces on linux node.
336 :param node: Node to add bridge on.
337 :param br_name: Bridge name.
338 :param if_1: First interface to be added to the bridge.
339 :param if_2: Second interface to be added to the bridge.
340 :param set_up: Change bridge interface state to up after create bridge.
341 Optional. Default: True.
349 cmd = 'brctl addbr {0}'.format(br_name)
350 exec_cmd_no_error(node, cmd, sudo=True)
351 cmd = 'brctl addif {0} {1}'.format(br_name, if_1)
352 exec_cmd_no_error(node, cmd, sudo=True)
353 cmd = 'brctl addif {0} {1}'.format(br_name, if_2)
354 exec_cmd_no_error(node, cmd, sudo=True)
356 cmd = 'ip link set dev {0} up'.format(br_name)
357 exec_cmd_no_error(node, cmd, sudo=True)
360 def linux_del_bridge(node, br_name, set_down=True):
361 """Delete bridge from linux node.
363 ..note:: The network interface corresponding to the bridge must be
364 down before it can be deleted!
366 :param node: Node to delete bridge from.
367 :param br_name: Bridge name.
368 :param set_down: Change bridge interface state to down before delbr
369 command. Optional. Default: True.
376 cmd = 'ip link set dev {0} down'.format(br_name)
377 exec_cmd_no_error(node, cmd, sudo=True)
378 cmd = 'brctl delbr {0}'.format(br_name)
379 exec_cmd_no_error(node, cmd, sudo=True)
382 def vpp_get_bridge_domain_data(node, bd_id=0xffffffff):
383 """Get all bridge domain data from a VPP node. If a domain ID number is
384 provided, return only data for the matching bridge domain.
386 :param node: VPP node to get bridge domain data from.
387 :param bd_id: Numeric ID of a specific bridge domain.
390 :returns: List of dictionaries containing data for each bridge domain,
391 or a single dictionary for the specified bridge domain.
395 cmd = 'bridge_domain_dump'
396 cmd_reply = 'bridge_domain_details'
397 args = dict(bd_id=int(bd_id))
398 err_msg = 'Failed to get L2FIB dump on host {host}'.format(
400 with PapiExecutor(node) as papi_exec:
401 papi_resp = papi_exec.add(cmd, **args).get_dump(err_msg)
403 data = papi_resp.reply[0]['api_reply']
405 bd_data = list() if bd_id == Constants.BITWISE_NON_ZERO else dict()
406 for bridge_domain in data:
407 if bd_id == Constants.BITWISE_NON_ZERO:
408 bd_data.append(bridge_domain[cmd_reply])
410 if bridge_domain[cmd_reply]['bd_id'] == bd_id:
411 return bridge_domain[cmd_reply]
416 def l2_vlan_tag_rewrite(node, interface, tag_rewrite_method,
417 push_dot1q=True, tag1_id=None, tag2_id=None):
418 """Rewrite tags in ethernet frame.
420 :param node: Node to rewrite tags.
421 :param interface: Interface on which rewrite tags.
422 :param tag_rewrite_method: Method of tag rewrite.
423 :param push_dot1q: Optional parameter to disable to push dot1q tag
425 :param tag1_id: Optional tag1 ID for VLAN.
426 :param tag2_id: Optional tag2 ID for VLAN.
428 :type interface: str or int
429 :type tag_rewrite_method: str
430 :type push_dot1q: bool
435 tag1_id = int(tag1_id) if tag1_id else 0
436 tag2_id = int(tag2_id) if tag2_id else 0
438 vtr_oper = getattr(L2VtrOp, 'L2_VTR_{}'.format(
439 tag_rewrite_method.replace('-', '_').upper()))
441 if isinstance(interface, basestring):
442 iface_key = Topology.get_interface_by_name(node, interface)
443 sw_if_index = Topology.get_interface_sw_index(node, iface_key)
445 sw_if_index = interface
447 cmd = 'l2_interface_vlan_tag_rewrite'
448 args = dict(sw_if_index=sw_if_index,
449 vtr_op=int(vtr_oper),
450 push_dot1q=int(push_dot1q),
453 err_msg = 'Failed to set VLAN TAG rewrite on host {host}'.format(
455 with PapiExecutor(node) as papi_exec:
456 papi_exec.add(cmd, **args).get_replies(err_msg).\
457 verify_reply(err_msg=err_msg)
460 def get_l2_fib_table(node, bd_id):
461 """Retrieves the L2 FIB table.
463 :param node: VPP node.
464 :param bd_id: Index of the bridge domain.
467 :returns: L2 FIB table.
471 cmd = 'l2_fib_table_dump'
472 cmd_reply = 'l2_fib_table_details'
473 args = dict(bd_id=int(bd_id))
474 err_msg = 'Failed to get L2FIB dump on host {host}'.format(
476 with PapiExecutor(node) as papi_exec:
477 papi_resp = papi_exec.add(cmd, **args).get_dump(err_msg)
479 data = papi_resp.reply[0]['api_reply']
483 fib_item = fib[cmd_reply]
484 fib_item['mac'] = L2Util.bin_to_mac(fib_item['mac'])
485 fib_data.append(fib_item)
490 def get_l2_fib_entry_by_mac(node, bd_index, mac):
491 """Retrieves the L2 FIB entry specified by MAC address using PAPI.
493 :param node: VPP node.
494 :param bd_index: Index of the bridge domain.
495 :param mac: MAC address used as the key in L2 FIB data structure.
499 :returns: L2 FIB entry
503 bd_data = L2Util.vpp_get_bridge_domain_data(node)
504 bd_id = bd_data[bd_index-1]['bd_id']
506 table = L2Util.get_l2_fib_table(node, bd_id)
509 if entry['mac'] == mac: