3 # Module to provide L2 bridge domain test case.
5 # The module provides a set of tools for L2 bridge domain tests.
8 logging.getLogger("scapy.runtime").setLevel(logging.ERROR)
13 from framework import *
14 from scapy.all import *
17 ## Subclass of the VppTestCase class.
19 # This subclass is a class for L2 bridge domain test cases. It provides methods
20 # to create interfaces, configure L2 bridge domain, create and verify packet
22 class TestL2bd(VppTestCase):
23 """ L2BD Test Case """
26 interf_nr = 3 # Number of interfaces
27 bd_id = 1 # Bridge domain ID
28 mac_entries = 100 # Number of MAC entries for bridge-domain to learn
29 dot1q_sub_id = 100 # SubID of dot1q sub-interface
30 dot1q_tag = 100 # VLAN tag for dot1q sub-interface
31 dot1ad_sub_id = 200 # SubID of dot1ad sub-interface
32 dot1ad_outer_tag = 200 # VLAN S-tag for dot1ad sub-interface
33 dot1ad_inner_tag = 300 # VLAN C-tag for dot1ad sub-interface
34 pkts_per_burst = 257 # Number of packets per burst
36 ## Class method to start the test case.
37 # Overrides setUpClass method in VppTestCase class.
38 # Python try..except statement is used to ensure that the tear down of
39 # the class will be executed even if exception is raised.
40 # @param cls The class pointer.
43 super(TestL2bd, cls).setUpClass()
46 ## Create interfaces and sub-interfaces
47 cls.create_interfaces_and_subinterfaces(TestL2bd.interf_nr)
49 ## Create BD with MAC learning enabled and put interfaces and
50 # sub-interfaces to this BD
51 cls.api("bridge_domain_add_del bd_id %u learn 1" % TestL2bd.bd_id)
52 for i in cls.interfaces:
53 if isinstance(cls.INT_DETAILS[i], cls.Subint):
54 interface = "pg%u.%u" % (i, cls.INT_DETAILS[i].sub_id)
56 interface = "pg%u" % i
57 cls.api("sw_interface_set_l2_bridge %s bd_id %u"
58 % (interface, TestL2bd.bd_id))
60 ## Make the BD learn a number of MAC entries specified by the test
61 # variable <mac_entries>.
62 cls.create_mac_entries(TestL2bd.mac_entries)
63 cls.cli(0, "show l2fib")
65 except Exception as e:
66 super(TestL2bd, cls).tearDownClass()
69 ## Method to define tear down VPP actions of the test case.
70 # Overrides tearDown method in VppTestCase class.
71 # @param self The object pointer.
73 self.cli(2, "show int")
74 self.cli(2, "show trace")
75 self.cli(2, "show hardware")
76 self.cli(2, "show l2fib verbose")
77 self.cli(2, "show error")
78 self.cli(2, "show run")
79 self.cli(2, "show bridge-domain 1 detail")
81 ## Class method to create VLAN sub-interface.
82 # Uses VPP API command to create VLAN sub-interface.
83 # @param cls The class pointer.
84 # @param pg_index Integer variable to store the index of the packet
85 # generator interface to create VLAN sub-interface on.
86 # @param vlan_id Integer variable to store required VLAN tag value.
88 def create_vlan_subif(cls, pg_index, vlan_id):
89 cls.api("create_vlan_subif pg%u vlan %u" % (pg_index, vlan_id))
91 ## Class method to create dot1ad sub-interface.
92 # Use VPP API command to create dot1ad sub-interface.
93 # @param cls The class pointer.
94 # @param pg_index Integer variable to store the index of the packet
95 # generator interface to create dot1ad sub-interface on.
96 # @param outer_vlan_id Integer variable to store required outer VLAN tag
98 # @param inner_vlan_id Integer variable to store required inner VLAN tag
101 def create_dot1ad_subif(cls, pg_index, sub_id, outer_vlan_id,
103 cls.api("create_subif pg%u sub_id %u outer_vlan_id %u inner_vlan_id"
104 " %u dot1ad" % (pg_index, sub_id, outer_vlan_id, inner_vlan_id))
106 ## Base class for interface.
107 # To define object representation of the interface.
108 class Interface(object):
111 ## Sub-class of the interface class.
112 # To define object representation of the HW interface.
113 class HardInt(Interface):
116 ## Sub-class of the interface class.
117 # To define object representation of the SW interface.
118 class SoftInt(Interface):
121 ## Sub-class of the SW interface class.
122 # To represent the general sub-interface.
123 class Subint(SoftInt):
125 # @param sub_id Integer variable to store sub-interface ID.
126 def __init__(self, sub_id):
129 ## Sub-class of the SW interface class.
130 # To represent dot1q sub-interface.
131 class Dot1QSubint(Subint):
133 # @param sub_id Integer variable to store sub-interface ID.
134 # @param vlan Integer variable (optional) to store VLAN tag value. Set
135 # to sub_id value when VLAN tag value not provided.
136 def __init__(self, sub_id, vlan=None):
139 super(TestL2bd.Dot1QSubint, self).__init__(sub_id)
142 ## Sub-class of the SW interface class.
143 # To represent dot1ad sub-interface.
144 class Dot1ADSubint(Subint):
146 # @param sub_id Integer variable to store sub-interface ID.
147 # @param outer_vlan Integer variable to store outer VLAN tag value.
148 # @param inner_vlan Integer variable to store inner VLAN tag value.
149 def __init__(self, sub_id, outer_vlan, inner_vlan):
150 super(TestL2bd.Dot1ADSubint, self).__init__(sub_id)
151 self.outer_vlan = outer_vlan
152 self.inner_vlan = inner_vlan
154 ## Class method to create interfaces and sub-interfaces.
155 # Current implementation: create three interfaces, then create Dot1Q
156 # sub-interfaces for the second and the third interface with VLAN tags
157 # equal to their sub-interface IDs. Set sub-interfaces status to admin-up.
158 # @param cls The class pointer.
159 # @param int_nr Integer variable to store the number of interfaces to be
161 # TODO: Parametrize required numbers of dot1q and dot1ad to be created.
163 def create_interfaces_and_subinterfaces(cls, int_nr):
164 ## A class list variable to store interface indexes.
165 cls.interfaces = range(int_nr)
168 cls.create_interfaces(cls.interfaces)
170 # Make vpp_api_test see interfaces created using debug CLI (in function
172 cls.api("sw_interface_dump")
174 ## A class dictionary variable to store data about interfaces.
175 # First create an empty dictionary then store interface data there.
176 cls.INT_DETAILS = dict()
178 # 1st interface is untagged - no sub-interface required
179 cls.INT_DETAILS[0] = cls.HardInt()
181 # 2nd interface is dot1q tagged
182 cls.INT_DETAILS[1] = cls.Dot1QSubint(TestL2bd.dot1q_sub_id,
184 cls.create_vlan_subif(1, cls.INT_DETAILS[1].vlan)
186 # 3rd interface is dot1ad tagged
187 # FIXME: Wrong packet format/wrong layer on output of interface 2
188 #self.INT_DETAILS[2] = self.Dot1ADSubint(TestL2bd.dot1ad_sub_id, TestL2bd.dot1ad_outer_tag, TestL2bd.dot1ad_inner_tag)
189 #self.create_dot1ad_subif(2, self.INT_DETAILS[2].sub_id, self.INT_DETAILS[2].outer_vlan, self.INT_DETAILS[2].inner_vlan)
192 cls.INT_DETAILS[2] = cls.Dot1QSubint(TestL2bd.dot1ad_sub_id,
193 TestL2bd.dot1ad_outer_tag)
194 cls.create_vlan_subif(2, cls.INT_DETAILS[2].vlan)
196 for i in cls.interfaces:
197 if isinstance(cls.INT_DETAILS[i], cls.Subint):
198 cls.api("sw_interface_set_flags pg%u.%u admin-up"
199 % (i, cls.INT_DETAILS[i].sub_id))
201 # List variable to store interface indexes.
203 # Dictionary variable to store data about interfaces.
205 ## Class method for bridge-domain to learn defined number of MAC addresses.
206 # Create required number of host MAC addresses and distribute them among
207 # interfaces. Create host IPv4 address for every host MAC address. Create
208 # L2 MAC packet stream with host MAC addresses per interface to let
209 # the bridge domain learn these MAC addresses.
210 # @param cls The class pointer.
211 # @param count Integer variable to store the number of MAC addresses to be
214 def create_mac_entries(cls, count):
215 n_int = len(cls.interfaces)
216 macs_per_if = count / n_int
217 for i in cls.interfaces:
218 start_nr = macs_per_if*i
219 end_nr = count if i == (n_int - 1) else macs_per_if*(i+1)
223 for j in range(start_nr, end_nr):
224 cls.MY_MACS[i].append("00:00:00:ff:%02x:%02x" % (i, j))
225 cls.MY_IP4S[i].append("172.17.1%02x.%u" % (i, j))
226 packet = (Ether(dst="ff:ff:ff:ff:ff:ff", src=cls.MY_MACS[i]))
227 packets.append(packet)
228 cls.pg_add_stream(i, packets)
229 # Based on the verbosity level set in the system print the log.
230 cls.log("Sending broadcast eth frames for MAC learning", 1)
232 # Packet stream capturing is not started as we don't need to read
235 # Integer variable to store the number of interfaces.
237 # Integer variable to store the number of MAC addresses per interface.
239 # Integer variable to store the starting number of the range used to
240 # generate MAC addresses for the interface.
242 # Integer variable to store the ending number of the range used to
243 # generate MAC addresses for the interface.
245 # Dictionary variable to store list of MAC addresses per interface.
247 # Dictionary variable to store list of IPv4 addresses per interface.
249 ## Class method to add dot1q or dot1ad layer to the packet.
250 # Based on sub-interface data of the defined interface add dot1q or dot1ad
251 # Ethernet header layer to the packet.
252 # @param cls The class pointer.
253 # @param i Integer variable to store the index of the interface.
254 # @param packet Object variable to store the packet where to add dot1q or
256 # TODO: Move this class method to utils.py.
258 def add_dot1_layers(cls, i, packet):
259 assert(type(packet) is Ether)
260 payload = packet.payload
261 if isinstance(cls.INT_DETAILS[i], cls.Dot1QSubint):
262 packet.remove_payload()
263 packet.add_payload(Dot1Q(vlan=cls.INT_DETAILS[i].vlan) / payload)
264 elif isinstance(cls.INT_DETAILS[i], cls.Dot1ADSubint):
265 packet.remove_payload()
266 packet.add_payload(Dot1Q(vlan=cls.INT_DETAILS[i].outer_vlan,
268 Dot1Q(vlan=cls.INT_DETAILS[i].inner_vlan) /
272 # Object variable to store payload of the packet.
274 # Dictionary variable to store data about interfaces.
276 # Class variable representing dot1q sub-interfaces.
278 # Class variable representing dot1ad sub-interfaces.
280 ## Method to remove dot1q or dot1ad layer from the packet.
281 # Based on sub-interface data of the defined interface remove dot1q or
282 # dot1ad layer from the packet.
283 # @param cls The class pointer.
284 # @param i Integer variable to store the index of the interface.
285 # @param packet Object variable to store the packet where to remove dot1q
287 def remove_dot1_layers(self, i, packet):
288 self.assertEqual(type(packet), Ether)
289 payload = packet.payload
290 if isinstance(self.INT_DETAILS[i], self.Dot1QSubint):
291 self.assertEqual(type(payload), Dot1Q)
292 self.assertEqual(payload.vlan, self.INT_DETAILS[i].vlan)
293 payload = payload.payload
294 elif isinstance(self.INT_DETAILS[i], self.Dot1ADSubint): # TODO: change 88A8 type
295 self.assertEqual(type(payload), Dot1Q)
296 self.assertEqual(payload.vlan, self.INT_DETAILS[i].outer_vlan)
297 payload = payload.payload
298 self.assertEqual(type(payload), Dot1Q)
299 self.assertEqual(payload.vlan, self.INT_DETAILS[i].inner_vlan)
300 payload = payload.payload
301 packet.remove_payload()
302 packet.add_payload(payload)
304 # Object variable to store payload of the packet.
306 # Dictionary variable to store data about interfaces.
308 # Class variable representing dot1q sub-interfaces.
310 # Class variable representing dot1ad sub-interfaces.
312 ## Method to create packet stream for the packet generator interface.
313 # Create input packet stream for the given packet generator interface with
314 # packets of different length targeted for all other created packet
315 # generator interfaces.
316 # @param self The object pointer.
317 # @param pg_id Integer variable to store the index of the interface to
318 # create the input packet stream.
319 # @return pkts List variable to store created input stream of packets.
320 def create_stream(self, pg_id):
321 # TODO: use variables to create lists based on interface number
322 pg_targets = [None] * 3
323 pg_targets[0] = [1, 2]
324 pg_targets[1] = [0, 2]
325 pg_targets[2] = [0, 1]
327 for i in range(0, TestL2bd.pkts_per_burst):
328 target_pg_id = pg_targets[pg_id][i % 2]
329 target_host_id = random.randrange(len(self.MY_MACS[target_pg_id]))
330 source_host_id = random.randrange(len(self.MY_MACS[pg_id]))
331 pkt_info = self.create_packet_info(pg_id, target_pg_id)
332 payload = self.info_to_payload(pkt_info)
333 p = (Ether(dst=self.MY_MACS[target_pg_id][target_host_id],
334 src=self.MY_MACS[pg_id][source_host_id]) /
335 IP(src=self.MY_IP4S[pg_id][source_host_id],
336 dst=self.MY_IP4S[target_pg_id][target_host_id]) /
337 UDP(sport=1234, dport=1234) /
339 pkt_info.data = p.copy()
340 self.add_dot1_layers(pg_id, p)
341 if not isinstance(self.INT_DETAILS[pg_id], self.Subint):
342 packet_sizes = [64, 512, 1518, 9018]
344 packet_sizes = [64, 512, 1518+4, 9018+4]
345 size = packet_sizes[(i / 2) % len(packet_sizes)]
346 self.extend_packet(p, size)
350 # List variable to store list of indexes of target packet generator
351 # interfaces for every source packet generator interface.
353 # Integer variable to store the index of the random target packet
354 # generator interfaces.
355 ## @var target_host_id
356 # Integer variable to store the index of the randomly chosen
357 # destination host MAC/IPv4 address.
358 ## @var source_host_id
359 # Integer variable to store the index of the randomly chosen source
360 # host MAC/IPv4 address.
362 # Object variable to store the information about the generated packet.
364 # String variable to store the payload of the packet to be generated.
366 # Object variable to store the generated packet.
368 # List variable to store required packet sizes.
370 # List variable to store required packet sizes.
372 ## Method to verify packet stream received on the packet generator interface.
373 # Verify packet-by-packet the output stream captured on a given packet
374 # generator (pg) interface using following packet payload data - order of
375 # packet in the stream, index of the source and destination pg interface,
376 # src and dst host IPv4 addresses and src port and dst port values of UDP
378 # @param self The object pointer.
379 # @param o Integer variable to store the index of the interface to
380 # verify the output packet stream.
381 # @param capture List variable to store the captured output packet stream.
382 def verify_capture(self, o, capture):
384 for i in self.interfaces:
386 for packet in capture:
390 payload_info = self.payload_to_info(str(packet[Raw]))
391 # Check VLAN tags and Ethernet header
392 # TODO: Rework to check VLAN tag(s) and do not remove them
393 self.remove_dot1_layers(payload_info.src, packet)
394 self.assertTrue(Dot1Q not in packet)
395 self.assertEqual(payload_info.dst, o)
396 self.log("Got packet on port %u: src=%u (id=%u)"
397 % (o, payload_info.src, payload_info.index), 2)
398 next_info = self.get_next_packet_info_for_interface2(
399 payload_info.src, payload_info.dst,
400 last_info[payload_info.src])
401 last_info[payload_info.src] = next_info
402 self.assertTrue(next_info is not None)
403 self.assertEqual(payload_info.index, next_info.index)
404 # Check standard fields
405 self.assertEqual(ip.src, next_info.data[IP].src)
406 self.assertEqual(ip.dst, next_info.data[IP].dst)
407 self.assertEqual(udp.sport, next_info.data[UDP].sport)
408 self.assertEqual(udp.dport, next_info.data[UDP].dport)
410 self.log("Unexpected or invalid packet:")
413 for i in self.interfaces:
414 remaining_packet = self.get_next_packet_info_for_interface2(
416 self.assertTrue(remaining_packet is None,
417 "Port %u: Packet expected from source %u didn't"
420 # Dictionary variable to store verified packets per packet generator
423 # Object variable to store the IP layer of the packet.
425 # Object variable to store the UDP layer of the packet.
427 # Object variable to store required information about the packet.
429 # Object variable to store information about next packet.
430 ## @var remaining_packet
431 # Object variable to store information about remaining packet.
433 ## Method defining VPP L2 bridge domain test case.
434 # Contains execution steps of the test case.
435 # @param self The object pointer.
437 """ L2BD MAC learning test
442 3 interfaces: untagged, dot1q, dot1ad (dot1q used instead of dot1ad
443 in the first version)
445 2.sending l2 eth pkts between 3 interface
446 64B, 512B, 1518B, 9200B (ether_size)
447 burst of 257 pkts per interface
450 ## Create incoming packet streams for packet-generator interfaces
451 for i in self.interfaces:
452 pkts = self.create_stream(i)
453 self.pg_add_stream(i, pkts)
455 ## Enable packet capture and start packet sending
456 self.pg_enable_capture(self.interfaces)
459 ## Verify outgoing packet streams per packet-generator interface
460 for i in self.interfaces:
461 out = self.pg_get_capture(i)
462 self.log("Verifying capture %u" % i)
463 self.verify_capture(i, out)
465 # List variable to store created input stream of packets for the packet
466 # generator interface.
468 # List variable to store captured output stream of packets for
469 # the packet generator interface.
472 if __name__ == '__main__':
473 unittest.main(testRunner = VppTestRunner)