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.PapiExecutor import PapiExecutor
22 from resources.libraries.python.topology import Topology
23 from resources.libraries.python.ssh import exec_cmd_no_error
26 class L2VtrOp(IntEnum):
27 """VLAN tag rewrite operation."""
33 L2_VTR_TRANSLATE_1_1 = 5
34 L2_VTR_TRANSLATE_1_2 = 6
35 L2_VTR_TRANSLATE_2_1 = 7
36 L2_VTR_TRANSLATE_2_2 = 8
40 """Utilities for l2 configuration."""
43 def mac_to_int(mac_str):
44 """Convert MAC address from string format (e.g. 01:02:03:04:05:06) to
45 integer representation (1108152157446).
47 :param mac_str: MAC address in string representation.
49 :returns: Integer representation of MAC address.
52 return int(mac_str.replace(':', ''), 16)
55 def int_to_mac(mac_int):
56 """Convert MAC address from integer representation (e.g. 1108152157446)
57 to string format (01:02:03:04:05:06).
59 :param mac_int: MAC address in integer representation.
61 :returns: String representation of MAC address.
64 return ':'.join(wrap("{:012x}".format(mac_int), width=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 binascii.unhexlify(mac_str.replace(':', ''))
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 x = ':'.join(binascii.hexlify(mac_bin)[i:i + 2]
89 for i in range(0, 12, 2))
90 return str(x.decode('ascii'))
93 def vpp_add_l2fib_entry(node, mac, interface, bd_id, static_mac=1,
94 filter_mac=0, bvi_mac=0):
95 """ Create a static L2FIB entry on a VPP node.
97 :param node: Node to add L2FIB entry on.
98 :param mac: Destination mac address in string format 01:02:03:04:05:06.
99 :param interface: Interface name or sw_if_index.
100 :param bd_id: Bridge domain index.
101 :param static_mac: Set to 1 to create static MAC entry.
103 :param filter_mac: Set to 1 to drop packet that's source or destination
104 MAC address contains defined MAC address. (Default value = 0)
105 :param bvi_mac: Set to 1 to create entry that points to BVI interface.
109 :type interface: str or int
110 :type bd_id: int or str
111 :type static_mac: int or str
112 :type filter_mac: int or str
113 :type bvi_mac: int or str
116 if isinstance(interface, basestring):
117 sw_if_index = Topology.get_interface_sw_index(node, interface)
119 sw_if_index = interface
121 cmd = 'l2fib_add_del'
122 err_msg = 'Failed to add L2FIB entry on host {host}'.format(
124 args = dict(mac=L2Util.mac_to_bin(mac),
126 sw_if_index=sw_if_index,
128 static_mac=int(static_mac),
129 filter_mac=int(filter_mac),
130 bvi_mac=int(bvi_mac))
131 with PapiExecutor(node) as papi_exec:
132 papi_exec.add(cmd, **args).get_replies(err_msg).\
133 verify_reply(err_msg=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 PapiExecutor(node) as papi_exec:
172 papi_exec.add(cmd, **args).get_replies(err_msg).\
173 verify_reply(err_msg=err_msg)
176 def add_interface_to_l2_bd(node, interface, bd_id, shg=0, port_type=0):
177 """Add an interface to the L2 bridge domain.
179 Get SW IF ID and add it to the bridge domain.
181 :param node: Node where we want to execute the command that does this.
182 :param interface: Interface name.
183 :param bd_id: Bridge domain index.
184 :param shg: Split-horizon group index. (Default value = 0)
185 :param port_type: Port mode: 0 - normal, 1 - BVI, 2 - UU_FWD.
189 :type bd_id: int or str
190 :type shg: int or str
191 :type port_type: int or str
194 sw_if_index = Topology.get_interface_sw_index(node, interface)
196 cmd = 'sw_interface_set_l2_bridge'
197 err_msg = 'Failed to add interface {ifc} to L2 bridge domain on host ' \
198 '{host}'.format(ifc=interface, host=node['host'])
199 args = dict(rx_sw_if_index=sw_if_index,
202 port_type=int(port_type),
204 with PapiExecutor(node) as papi_exec:
205 papi_exec.add(cmd, **args).get_replies(err_msg).\
206 verify_reply(err_msg=err_msg)
209 def vpp_add_l2_bridge_domain(node, bd_id, port_1, port_2, learn=True):
210 """Add L2 bridge domain with 2 interfaces to the VPP node.
212 :param node: Node to add L2BD on.
213 :param bd_id: Bridge domain ID.
214 :param port_1: First interface name added to L2BD.
215 :param port_2: Second interface name added to L2BD.
216 :param learn: Enable/disable MAC learn.
224 sw_if_index1 = Topology.get_interface_sw_index(node, port_1)
225 sw_if_index2 = Topology.get_interface_sw_index(node, port_2)
226 learn_int = 1 if learn else 0
228 cmd1 = 'bridge_domain_add_del'
229 args1 = dict(bd_id=int(bd_id),
237 cmd2 = 'sw_interface_set_l2_bridge'
238 args2 = dict(rx_sw_if_index=sw_if_index1,
244 args3 = dict(rx_sw_if_index=sw_if_index2,
250 err_msg = 'Failed to add L2 bridge domain with 2 interfaces on host' \
251 ' {host}'.format(host=node['host'])
253 with PapiExecutor(node) as papi_exec:
254 papi_exec.add(cmd1, **args1).add(cmd2, **args2).add(cmd2, **args3).\
255 get_replies(err_msg).verify_replies(err_msg=err_msg)
258 def vpp_setup_bidirectional_cross_connect(node, interface1, interface2):
259 """Create bidirectional cross-connect between 2 interfaces on vpp node.
261 :param node: Node to add bidirectional cross-connect.
262 :param interface1: First interface name or sw_if_index.
263 :param interface2: Second interface name or sw_if_index.
265 :type interface1: str or int
266 :type interface2: str or int
269 if isinstance(interface1, basestring):
270 sw_iface1 = Topology().get_interface_sw_index(node, interface1)
272 sw_iface1 = interface1
274 if isinstance(interface2, basestring):
275 sw_iface2 = Topology().get_interface_sw_index(node, interface2)
277 sw_iface2 = interface2
279 cmd = 'sw_interface_set_l2_xconnect'
280 args1 = dict(rx_sw_if_index=sw_iface1,
281 tx_sw_if_index=sw_iface2,
283 args2 = dict(rx_sw_if_index=sw_iface2,
284 tx_sw_if_index=sw_iface1,
287 err_msg = 'Failed to add L2 cross-connect between two interfaces on' \
288 ' host {host}'.format(host=node['host'])
290 with PapiExecutor(node) as papi_exec:
291 papi_exec.add(cmd, **args1).add(cmd, **args2).get_replies(err_msg).\
292 verify_replies(err_msg=err_msg)
295 def vpp_setup_bidirectional_l2_patch(node, interface1, interface2):
296 """Create bidirectional l2 patch between 2 interfaces on vpp node.
298 :param node: Node to add bidirectional l2 patch.
299 :param interface1: First interface name or sw_if_index.
300 :param interface2: Second interface name or sw_if_index.
302 :type interface1: str or int
303 :type interface2: str or int
306 if isinstance(interface1, basestring):
307 sw_iface1 = Topology().get_interface_sw_index(node, interface1)
309 sw_iface1 = interface1
311 if isinstance(interface2, basestring):
312 sw_iface2 = Topology().get_interface_sw_index(node, interface2)
314 sw_iface2 = interface2
316 cmd = 'l2_patch_add_del'
317 args1 = dict(rx_sw_if_index=sw_iface1,
318 tx_sw_if_index=sw_iface2,
320 args2 = dict(rx_sw_if_index=sw_iface2,
321 tx_sw_if_index=sw_iface1,
324 err_msg = 'Failed to add L2 patch between two interfaces on' \
325 ' host {host}'.format(host=node['host'])
327 with PapiExecutor(node) as papi_exec:
328 papi_exec.add(cmd, **args1).add(cmd, **args2).get_replies(err_msg).\
329 verify_replies(err_msg=err_msg)
332 def linux_add_bridge(node, br_name, if_1, if_2, set_up=True):
333 """Bridge two interfaces on linux node.
335 :param node: Node to add bridge on.
336 :param br_name: Bridge name.
337 :param if_1: First interface to be added to the bridge.
338 :param if_2: Second interface to be added to the bridge.
339 :param set_up: Change bridge interface state to up after create bridge.
340 Optional. Default: True.
348 cmd = 'brctl addbr {0}'.format(br_name)
349 exec_cmd_no_error(node, cmd, sudo=True)
350 cmd = 'brctl addif {0} {1}'.format(br_name, if_1)
351 exec_cmd_no_error(node, cmd, sudo=True)
352 cmd = 'brctl addif {0} {1}'.format(br_name, if_2)
353 exec_cmd_no_error(node, cmd, sudo=True)
355 cmd = 'ip link set dev {0} up'.format(br_name)
356 exec_cmd_no_error(node, cmd, sudo=True)
359 def linux_del_bridge(node, br_name, set_down=True):
360 """Delete bridge from linux node.
362 ..note:: The network interface corresponding to the bridge must be
363 down before it can be deleted!
365 :param node: Node to delete bridge from.
366 :param br_name: Bridge name.
367 :param set_down: Change bridge interface state to down before delbr
368 command. Optional. Default: True.
375 cmd = 'ip link set dev {0} down'.format(br_name)
376 exec_cmd_no_error(node, cmd, sudo=True)
377 cmd = 'brctl delbr {0}'.format(br_name)
378 exec_cmd_no_error(node, cmd, sudo=True)
381 def vpp_get_bridge_domain_data(node, bd_id=0xffffffff):
382 """Get all bridge domain data from a VPP node. If a domain ID number is
383 provided, return only data for the matching bridge domain.
385 :param node: VPP node to get bridge domain data from.
386 :param bd_id: Numeric ID of a specific bridge domain.
389 :returns: List of dictionaries containing data for each bridge domain,
390 or a single dictionary for the specified bridge domain.
394 # TODO: set following variable per whole suite when planned FIB API
395 # changes are merged in VPP
396 bitwise_non_zero = 0xffffffff # equals to ~0 used in vpp code
398 cmd = 'bridge_domain_dump'
399 cmd_reply = 'bridge_domain_details'
400 args = dict(bd_id=bd_id if isinstance(bd_id, int) else int(bd_id))
401 err_msg = 'Failed to get L2FIB dump on host {host}'.format(
403 with PapiExecutor(node) as papi_exec:
404 papi_resp = papi_exec.add(cmd, **args).get_dump(err_msg)
406 data = papi_resp.reply[0]['api_reply']
408 bd_data = list() if bd_id == bitwise_non_zero else dict()
409 for bridge_domain in data:
410 if bd_id == bitwise_non_zero:
411 bd_data.append(bridge_domain[cmd_reply])
413 if bridge_domain[cmd_reply]['bd_id'] == bd_id:
414 return bridge_domain[cmd_reply]
419 def l2_vlan_tag_rewrite(node, interface, tag_rewrite_method,
420 push_dot1q=True, tag1_id=None, tag2_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
438 tag1_id = int(tag1_id) if tag1_id else 0
439 tag2_id = int(tag2_id) if tag2_id else 0
441 vtr_oper = getattr(L2VtrOp, 'L2_VTR_{}'.format(
442 tag_rewrite_method.replace('-', '_').upper()))
444 if isinstance(interface, basestring):
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 = 'l2_interface_vlan_tag_rewrite'
451 args = dict(sw_if_index=sw_if_index,
452 vtr_op=int(vtr_oper),
453 push_dot1q=int(push_dot1q),
456 err_msg = 'Failed to set VLAN TAG rewrite on host {host}'.format(
458 with PapiExecutor(node) as papi_exec:
459 papi_exec.add(cmd, **args).get_replies(err_msg).\
460 verify_reply(err_msg=err_msg)
463 def get_l2_fib_table(node, bd_id):
464 """Retrieves the L2 FIB table.
466 :param node: VPP node.
467 :param bd_id: Index of the bridge domain.
470 :returns: L2 FIB table.
474 cmd = 'l2_fib_table_dump'
475 cmd_reply = 'l2_fib_table_details'
476 args = dict(bd_id=int(bd_id))
477 err_msg = 'Failed to get L2FIB dump on host {host}'.format(
479 with PapiExecutor(node) as papi_exec:
480 papi_resp = papi_exec.add(cmd, **args).get_dump(err_msg)
482 data = papi_resp.reply[0]['api_reply']
486 fib_item = fib[cmd_reply]
487 fib_item['mac'] = L2Util.bin_to_mac(fib_item['mac'])
488 fib_data.append(fib_item)
493 def get_l2_fib_entry_by_mac(node, bd_index, mac):
494 """Retrieves the L2 FIB entry specified by MAC address using PAPI.
496 :param node: VPP node.
497 :param bd_index: Index of the bridge domain.
498 :param mac: MAC address used as the key in L2 FIB data structure.
502 :returns: L2 FIB entry
506 bd_data = L2Util.vpp_get_bridge_domain_data(node)
507 bd_id = bd_data[bd_index-1]['bd_id']
509 table = L2Util.get_l2_fib_table(node, bd_id)
512 if entry['mac'] == mac: