srv6-ad-flow: SRv6 Per-Flow Dynamic Proxy
[vpp.git] / src / plugins / srv6-ad-flow / test / test_srv6_ad_flow.py
1 #!/usr/bin/env python3
2
3 import unittest
4 import binascii
5 from socket import AF_INET6
6
7 from framework import VppTestCase, VppTestRunner
8 from vpp_ip import DpoProto
9 from vpp_ip_route import VppIpRoute, VppRoutePath, VppIpTable
10
11 import scapy.compat
12 from scapy.packet import Raw
13 from scapy.layers.l2 import Ether, Dot1Q
14 from scapy.layers.inet6 import IPv6, UDP, IPv6ExtHdrSegmentRouting
15 from scapy.layers.inet import IP, UDP
16
17 from util import ppp
18
19
20 class TestSRv6(VppTestCase):
21     """ SRv6 Flow-based Dynamic Proxy plugin Test Case """
22
23     @classmethod
24     def setUpClass(self):
25         super(TestSRv6, self).setUpClass()
26
27     @classmethod
28     def tearDownClass(cls):
29         super(TestSRv6, cls).tearDownClass()
30
31     def setUp(self):
32         """ Perform test setup before each test case.
33         """
34         super(TestSRv6, self).setUp()
35
36         # packet sizes, inclusive L2 overhead
37         self.pg_packet_sizes = [64, 512, 1518, 9018]
38
39         # reset packet_infos
40         self.reset_packet_infos()
41
42     def tearDown(self):
43         """ Clean up test setup after each test case.
44         """
45         self.teardown_interfaces()
46
47         super(TestSRv6, self).tearDown()
48
49     def configure_interface(self,
50                             interface,
51                             ipv6=False, ipv4=False,
52                             ipv6_table_id=0, ipv4_table_id=0):
53         """ Configure interface.
54         :param ipv6: configure IPv6 on interface
55         :param ipv4: configure IPv4 on interface
56         :param ipv6_table_id: FIB table_id for IPv6
57         :param ipv4_table_id: FIB table_id for IPv4
58         """
59         self.logger.debug("Configuring interface %s" % (interface.name))
60         if ipv6:
61             self.logger.debug("Configuring IPv6")
62             interface.set_table_ip6(ipv6_table_id)
63             interface.config_ip6()
64             interface.resolve_ndp(timeout=5)
65         if ipv4:
66             self.logger.debug("Configuring IPv4")
67             interface.set_table_ip4(ipv4_table_id)
68             interface.config_ip4()
69             interface.resolve_arp()
70         interface.admin_up()
71
72     def setup_interfaces(self, ipv6=[], ipv4=[],
73                          ipv6_table_id=[], ipv4_table_id=[]):
74         """ Create and configure interfaces.
75
76         :param ipv6: list of interface IPv6 capabilities
77         :param ipv4: list of interface IPv4 capabilities
78         :param ipv6_table_id: list of intf IPv6 FIB table_ids
79         :param ipv4_table_id: list of intf IPv4 FIB table_ids
80         :returns: List of created interfaces.
81         """
82         # how many interfaces?
83         if len(ipv6):
84             count = len(ipv6)
85         else:
86             count = len(ipv4)
87         self.logger.debug("Creating and configuring %d interfaces" % (count))
88
89         # fill up ipv6 and ipv4 lists if needed
90         # not enabled (False) is the default
91         if len(ipv6) < count:
92             ipv6 += (count - len(ipv6)) * [False]
93         if len(ipv4) < count:
94             ipv4 += (count - len(ipv4)) * [False]
95
96         # fill up table_id lists if needed
97         # table_id 0 (global) is the default
98         if len(ipv6_table_id) < count:
99             ipv6_table_id += (count - len(ipv6_table_id)) * [0]
100         if len(ipv4_table_id) < count:
101             ipv4_table_id += (count - len(ipv4_table_id)) * [0]
102
103         # create 'count' pg interfaces
104         self.create_pg_interfaces(range(count))
105
106         # setup all interfaces
107         for i in range(count):
108             intf = self.pg_interfaces[i]
109             self.configure_interface(intf,
110                                      ipv6[i], ipv4[i],
111                                      ipv6_table_id[i], ipv4_table_id[i])
112
113         if any(ipv6):
114             self.logger.debug(self.vapi.cli("show ip6 neighbors"))
115         if any(ipv4):
116             self.logger.debug(self.vapi.cli("show ip4 neighbors"))
117         self.logger.debug(self.vapi.cli("show interface"))
118         self.logger.debug(self.vapi.cli("show hardware"))
119
120         return self.pg_interfaces
121
122     def teardown_interfaces(self):
123         """ Unconfigure and bring down interface.
124         """
125         self.logger.debug("Tearing down interfaces")
126         # tear down all interfaces
127         # AFAIK they cannot be deleted
128         for i in self.pg_interfaces:
129             self.logger.debug("Tear down interface %s" % (i.name))
130             i.admin_down()
131             i.unconfig()
132             i.set_table_ip4(0)
133             i.set_table_ip6(0)
134
135     def test_SRv6_End_AD_IPv6(self):
136         """ Test SRv6 End.AD behavior with IPv6 traffic.
137         """
138         self.src_addr = 'a0::'
139         self.sid_list = ['a1::', 'a2::a6', 'a3::']
140         self.test_sid_index = 1
141
142         # send traffic to one destination interface
143         # source and destination interfaces are IPv6 only
144         self.setup_interfaces(ipv6=[True, True])
145
146         # configure route to next segment
147         route = VppIpRoute(self, self.sid_list[self.test_sid_index + 1], 128,
148                            [VppRoutePath(self.pg0.remote_ip6,
149                                          self.pg0.sw_if_index,
150                                          proto=DpoProto.DPO_PROTO_IP6)])
151         route.add_vpp_config()
152
153         # configure SRv6 localSID behavior
154         cli_str = "sr localsid address " + \
155                   self.sid_list[self.test_sid_index] + \
156                   " behavior end.ad.flow" + \
157                   " nh " + self.pg1.remote_ip6 + \
158                   " oif " + self.pg1.name + \
159                   " iif " + self.pg1.name
160         self.vapi.cli(cli_str)
161
162         # log the localsids
163         self.logger.debug(self.vapi.cli("show sr localsid"))
164
165         # send one packet per packet size
166         count = len(self.pg_packet_sizes)
167
168         # prepare IPv6 in SRv6 headers
169         packet_header1 = self.create_packet_header_IPv6_SRH_IPv6(
170             srcaddr=self.src_addr,
171             sidlist=self.sid_list[::-1],
172             segleft=len(self.sid_list) - self.test_sid_index - 1)
173
174         # generate packets (pg0->pg1)
175         pkts1 = self.create_stream(self.pg0, self.pg1, packet_header1,
176                                    self.pg_packet_sizes, count)
177
178         # send packets and verify received packets
179         self.send_and_verify_pkts(self.pg0, pkts1, self.pg1,
180                                   self.compare_rx_tx_packet_End_AD_IPv6_out)
181
182         # log the localsid counters
183         self.logger.info(self.vapi.cli("show sr localsid"))
184
185         # prepare IPv6 header for returning packets
186         packet_header2 = self.create_packet_header_IPv6()
187
188         # generate returning packets (pg1->pg0)
189         pkts2 = self.create_stream(self.pg1, self.pg0, packet_header2,
190                                    self.pg_packet_sizes, count)
191
192         # send packets and verify received packets
193         self.send_and_verify_pkts(self.pg1, pkts2, self.pg0,
194                                   self.compare_rx_tx_packet_End_AD_IPv6_in)
195
196         # log the localsid counters
197         self.logger.info(self.vapi.cli("show sr localsid"))
198
199         # remove SRv6 localSIDs
200         cli_str = "sr localsid del address " + \
201                   self.sid_list[self.test_sid_index]
202         self.vapi.cli(cli_str)
203
204         # cleanup interfaces
205         self.teardown_interfaces()
206
207     def compare_rx_tx_packet_End_AD_IPv6_out(self, tx_pkt, rx_pkt):
208         """ Compare input and output packet after passing End.AD with IPv6
209
210         :param tx_pkt: transmitted packet
211         :param rx_pkt: received packet
212         """
213
214         # get first (outer) IPv6 header of rx'ed packet
215         rx_ip = rx_pkt.getlayer(IPv6)
216
217         tx_ip = tx_pkt.getlayer(IPv6)
218         tx_ip2 = tx_pkt.getlayer(IPv6, 2)
219
220         # verify if rx'ed packet has no SRH
221         self.assertFalse(rx_pkt.haslayer(IPv6ExtHdrSegmentRouting))
222
223         # the whole rx_ip pkt should be equal to tx_ip2
224         # except for the hlim field
225         #   -> adjust tx'ed hlim to expected hlim
226         tx_ip2.hlim = tx_ip2.hlim - 1
227
228         self.assertEqual(rx_ip, tx_ip2)
229
230         self.logger.debug("packet verification: SUCCESS")
231
232     def compare_rx_tx_packet_End_AD_IPv6_in(self, tx_pkt, rx_pkt):
233         """ Compare input and output packet after passing End.AD
234
235         :param tx_pkt: transmitted packet
236         :param rx_pkt: received packet
237         """
238
239         # get first (outer) IPv6 header of rx'ed packet
240         rx_ip = rx_pkt.getlayer(IPv6)
241         # received ip.src should be equal to SR Policy source
242         self.assertEqual(rx_ip.src, self.src_addr)
243         # received ip.dst should be equal to expected sidlist next segment
244         self.assertEqual(rx_ip.dst, self.sid_list[self.test_sid_index + 1])
245
246         # rx'ed packet should have SRH
247         self.assertTrue(rx_pkt.haslayer(IPv6ExtHdrSegmentRouting))
248
249         # get SRH
250         rx_srh = rx_pkt.getlayer(IPv6ExtHdrSegmentRouting)
251         # rx'ed seglist should be equal to SID-list in reversed order
252         self.assertEqual(rx_srh.addresses, self.sid_list[::-1])
253         # segleft should be equal to previous segleft value minus 1
254         self.assertEqual(rx_srh.segleft,
255                          len(self.sid_list) - self.test_sid_index - 2)
256         # lastentry should be equal to the SID-list length minus 1
257         self.assertEqual(rx_srh.lastentry, len(self.sid_list) - 1)
258
259         # the whole rx'ed pkt beyond SRH should be equal to tx'ed pkt
260         # except for the hop-limit field
261         tx_ip = tx_pkt.getlayer(IPv6)
262         #   -> update tx'ed hlim to the expected hlim
263         tx_ip.hlim -= 1
264         #   -> check payload
265         self.assertEqual(rx_srh.payload, tx_ip)
266
267         self.logger.debug("packet verification: SUCCESS")
268
269     def test_SRv6_End_AD_IPv4(self):
270         """ Test SRv6 End.AD behavior with IPv4 traffic.
271         """
272         self.src_addr = 'a0::'
273         self.sid_list = ['a1::', 'a2::a4', 'a3::']
274         self.test_sid_index = 1
275
276         # send traffic to one destination interface
277         # source and destination interfaces are IPv6 only
278         self.setup_interfaces(ipv6=[True, False], ipv4=[False, True])
279
280         # configure route to next segment
281         route = VppIpRoute(self, self.sid_list[self.test_sid_index + 1], 128,
282                            [VppRoutePath(self.pg0.remote_ip6,
283                                          self.pg0.sw_if_index,
284                                          proto=DpoProto.DPO_PROTO_IP6)])
285         route.add_vpp_config()
286
287         # configure SRv6 localSID behavior
288         cli_str = "sr localsid address " + \
289                   self.sid_list[self.test_sid_index] + \
290                   " behavior end.ad.flow" + \
291                   " nh " + self.pg1.remote_ip4 + \
292                   " oif " + self.pg1.name + \
293                   " iif " + self.pg1.name
294         self.vapi.cli(cli_str)
295
296         # log the localsids
297         self.logger.debug(self.vapi.cli("show sr localsid"))
298
299         # send one packet per packet size
300         count = len(self.pg_packet_sizes)
301
302         # prepare IPv4 in SRv6 headers
303         packet_header1 = self.create_packet_header_IPv6_SRH_IPv4(
304             srcaddr=self.src_addr,
305             sidlist=self.sid_list[::-1],
306             segleft=len(self.sid_list) - self.test_sid_index - 1)
307
308         # generate packets (pg0->pg1)
309         pkts1 = self.create_stream(self.pg0, self.pg1, packet_header1,
310                                    self.pg_packet_sizes, count)
311
312         # send packets and verify received packets
313         self.send_and_verify_pkts(self.pg0, pkts1, self.pg1,
314                                   self.compare_rx_tx_packet_End_AD_IPv4_out)
315
316         # log the localsid counters
317         self.logger.info(self.vapi.cli("show sr localsid"))
318
319         # prepare IPv6 header for returning packets
320         packet_header2 = self.create_packet_header_IPv4()
321
322         # generate returning packets (pg1->pg0)
323         pkts2 = self.create_stream(self.pg1, self.pg0, packet_header2,
324                                    self.pg_packet_sizes, count)
325
326         # send packets and verify received packets
327         self.send_and_verify_pkts(self.pg1, pkts2, self.pg0,
328                                   self.compare_rx_tx_packet_End_AD_IPv4_in)
329
330         # log the localsid counters
331         self.logger.info(self.vapi.cli("show sr localsid"))
332
333         # remove SRv6 localSIDs
334         cli_str = "sr localsid del address " + \
335                   self.sid_list[self.test_sid_index]
336         self.vapi.cli(cli_str)
337
338         # cleanup interfaces
339         self.teardown_interfaces()
340
341     def compare_rx_tx_packet_End_AD_IPv4_out(self, tx_pkt, rx_pkt):
342         """ Compare input and output packet after passing End.AD with IPv4
343
344         :param tx_pkt: transmitted packet
345         :param rx_pkt: received packet
346         """
347
348         # get IPv4 header of rx'ed packet
349         rx_ip = rx_pkt.getlayer(IP)
350
351         tx_ip = tx_pkt.getlayer(IPv6)
352         tx_ip2 = tx_pkt.getlayer(IP)
353
354         # verify if rx'ed packet has no SRH
355         self.assertFalse(rx_pkt.haslayer(IPv6ExtHdrSegmentRouting))
356
357         # the whole rx_ip pkt should be equal to tx_ip2
358         # except for the ttl field and ip checksum
359         #   -> adjust tx'ed ttl to expected ttl
360         tx_ip2.ttl = tx_ip2.ttl - 1
361         #   -> set tx'ed ip checksum to None and let scapy recompute
362         tx_ip2.chksum = None
363         # read back the pkt (with str()) to force computing these fields
364         # probably other ways to accomplish this are possible
365         tx_ip2 = IP(scapy.compat.raw(tx_ip2))
366
367         self.assertEqual(rx_ip, tx_ip2)
368
369         self.logger.debug("packet verification: SUCCESS")
370
371     def compare_rx_tx_packet_End_AD_IPv4_in(self, tx_pkt, rx_pkt):
372         """ Compare input and output packet after passing End.AD
373
374         :param tx_pkt: transmitted packet
375         :param rx_pkt: received packet
376         """
377
378         # get first (outer) IPv6 header of rx'ed packet
379         rx_ip = rx_pkt.getlayer(IPv6)
380         # received ip.src should be equal to SR Policy source
381         self.assertEqual(rx_ip.src, self.src_addr)
382         # received ip.dst should be equal to expected sidlist next segment
383         self.assertEqual(rx_ip.dst, self.sid_list[self.test_sid_index + 1])
384
385         # rx'ed packet should have SRH
386         self.assertTrue(rx_pkt.haslayer(IPv6ExtHdrSegmentRouting))
387
388         # get SRH
389         rx_srh = rx_pkt.getlayer(IPv6ExtHdrSegmentRouting)
390         # rx'ed seglist should be equal to SID-list in reversed order
391         self.assertEqual(rx_srh.addresses, self.sid_list[::-1])
392         # segleft should be equal to previous segleft value minus 1
393         self.assertEqual(rx_srh.segleft,
394                          len(self.sid_list) - self.test_sid_index - 2)
395         # lastentry should be equal to the SID-list length minus 1
396         self.assertEqual(rx_srh.lastentry, len(self.sid_list) - 1)
397
398         # the whole rx'ed pkt beyond SRH should be equal to tx'ed pkt
399         # except for the ttl field and ip checksum
400         tx_ip = tx_pkt.getlayer(IP)
401         #   -> adjust tx'ed ttl to expected ttl
402         tx_ip.ttl = tx_ip.ttl - 1
403         #   -> set tx'ed ip checksum to None and let scapy recompute
404         tx_ip.chksum = None
405         #   -> read back the pkt (with str()) to force computing these fields
406         # probably other ways to accomplish this are possible
407         self.assertEqual(rx_srh.payload, IP(scapy.compat.raw(tx_ip)))
408
409         self.logger.debug("packet verification: SUCCESS")
410
411     def create_stream(self, src_if, dst_if, packet_header, packet_sizes,
412                       count):
413         """Create SRv6 input packet stream for defined interface.
414
415         :param VppInterface src_if: Interface to create packet stream for
416         :param VppInterface dst_if: destination interface of packet stream
417         :param packet_header: Layer3 scapy packet headers,
418                 L2 is added when not provided,
419                 Raw(payload) with packet_info is added
420         :param list packet_sizes: packet stream pckt sizes,sequentially applied
421                to packets in stream have
422         :param int count: number of packets in packet stream
423         :return: list of packets
424         """
425         self.logger.info("Creating packets")
426         pkts = []
427         for i in range(0, count - 1):
428             payload_info = self.create_packet_info(src_if, dst_if)
429             self.logger.debug(
430                 "Creating packet with index %d" % (payload_info.index))
431             payload = self.info_to_payload(payload_info)
432             # add L2 header if not yet provided in packet_header
433             if packet_header.getlayer(0).name == 'Ethernet':
434                 p = packet_header / Raw(payload)
435             else:
436                 p = Ether(dst=src_if.local_mac, src=src_if.remote_mac) / \
437                     packet_header / Raw(payload)
438             size = packet_sizes[i % len(packet_sizes)]
439             self.logger.debug("Packet size %d" % (size))
440             self.extend_packet(p, size)
441             # we need to store the packet with the automatic fields computed
442             # read back the dumped packet (with str())
443             # to force computing these fields
444             # probably other ways are possible
445             p = Ether(scapy.compat.raw(p))
446             payload_info.data = p.copy()
447             self.logger.debug(ppp("Created packet:", p))
448             pkts.append(p)
449         self.logger.info("Done creating packets")
450         return pkts
451
452     def send_and_verify_pkts(self, input, pkts, output, compare_func):
453         """Send packets and verify received packets using compare_func
454
455         :param input: ingress interface of DUT
456         :param pkts: list of packets to transmit
457         :param output: egress interface of DUT
458         :param compare_func: function to compare in and out packets
459         """
460         # add traffic stream to input interface
461         input.add_stream(pkts)
462
463         # enable capture on all interfaces
464         self.pg_enable_capture(self.pg_interfaces)
465
466         # start traffic
467         self.logger.info("Starting traffic")
468         self.pg_start()
469
470         # get output capture
471         self.logger.info("Getting packet capture")
472         capture = output.get_capture()
473
474         # assert nothing was captured on input interface
475         # input.assert_nothing_captured()
476
477         # verify captured packets
478         self.verify_captured_pkts(output, capture, compare_func)
479
480     def create_packet_header_IPv6(self, saddr='1234::1', daddr='4321::1',
481                                   sport=1234, dport=1234):
482         """Create packet header: IPv6 header, UDP header
483
484         :param dst: IPv6 destination address
485
486         IPv6 source address is 1234::1
487         IPv6 destination address is 4321::1
488         UDP source port and destination port are 1234
489         """
490
491         p = IPv6(src=saddr, dst=daddr) / UDP(sport=sport, dport=dport)
492         return p
493
494     def create_packet_header_IPv6_SRH_IPv6(self, srcaddr, sidlist, segleft,
495                                            insrc='1234::1', indst='4321::1',
496                                            sport=1234, dport=1234):
497         """Create packet header: IPv6 encapsulated in SRv6:
498         IPv6 header with SRH, IPv6 header, UDP header
499
500         :param int srcaddr: outer source address
501         :param list sidlist: segment list of outer IPv6 SRH
502         :param int segleft: segments-left field of outer IPv6 SRH
503
504         Outer IPv6 source address is set to srcaddr
505         Outer IPv6 destination address is set to sidlist[segleft]
506         Inner IPv6 source addresses is 1234::1
507         Inner IPv6 destination address is 4321::1
508         UDP source port and destination port are 1234
509         """
510
511         p = IPv6(src=srcaddr, dst=sidlist[segleft]) / \
512             IPv6ExtHdrSegmentRouting(addresses=sidlist,
513                                      segleft=segleft, nh=41) / \
514             IPv6(src=insrc, dst=indst) / \
515             UDP(sport=sport, dport=dport)
516         return p
517
518     def create_packet_header_IPv4(self):
519         """Create packet header: IPv4 header, UDP header
520
521         :param dst: IPv4 destination address
522
523         IPv4 source address is 123.1.1.1
524         IPv4 destination address is 124.1.1.1
525         UDP source port and destination port are 1234
526         """
527
528         p = IP(src='123.1.1.1', dst='124.1.1.1') / UDP(sport=1234, dport=1234)
529         return p
530
531     def create_packet_header_IPv6_SRH_IPv4(self, srcaddr, sidlist, segleft):
532         """Create packet header: IPv4 encapsulated in SRv6:
533         IPv6 header with SRH, IPv4 header, UDP header
534
535         :param int srcaddr: outer source address
536         :param list sidlist: segment list of outer IPv6 SRH
537         :param int segleft: segments-left field of outer IPv6 SRH
538
539         Outer IPv6 source address is set to srcaddr
540         Outer IPv6 destination address is set to sidlist[segleft]
541         Inner IPv4 source address is 123.1.1.1
542         Inner IPv4 destination address is 124.1.1.1
543         UDP source port and destination port are 1234
544         """
545
546         p = IPv6(src=srcaddr, dst=sidlist[segleft]) / \
547             IPv6ExtHdrSegmentRouting(addresses=sidlist,
548                                      segleft=segleft, nh=4) / \
549             IP(src='123.1.1.1', dst='124.1.1.1') / \
550             UDP(sport=1234, dport=1234)
551         return p
552
553     def get_payload_info(self, packet):
554         """ Extract the payload_info from the packet
555         """
556         # in most cases, payload_info is in packet[Raw]
557         # but packet[Raw] gives the complete payload
558         # (incl L2 header) for the T.Encaps L2 case
559         try:
560             payload_info = self.payload_to_info(packet[Raw])
561
562         except:
563             # remote L2 header from packet[Raw]:
564             # take packet[Raw], convert it to an Ether layer
565             # and then extract Raw from it
566             payload_info = self.payload_to_info(
567                 Ether(scapy.compat.raw(packet[Raw]))[Raw])
568
569         return payload_info
570
571     def verify_captured_pkts(self, dst_if, capture, compare_func):
572         """
573         Verify captured packet stream for specified interface.
574         Compare ingress with egress packets using the specified compare fn
575
576         :param dst_if: egress interface of DUT
577         :param capture: captured packets
578         :param compare_func: function to compare in and out packet
579         """
580         self.logger.info("Verifying capture on interface %s using function %s"
581                          % (dst_if.name, compare_func.__name__))
582
583         last_info = dict()
584         for i in self.pg_interfaces:
585             last_info[i.sw_if_index] = None
586         dst_sw_if_index = dst_if.sw_if_index
587
588         for packet in capture:
589             try:
590                 # extract payload_info from packet's payload
591                 payload_info = self.get_payload_info(packet)
592                 packet_index = payload_info.index
593
594                 self.logger.debug("Verifying packet with index %d"
595                                   % (packet_index))
596                 # packet should have arrived on the expected interface
597                 self.assertEqual(payload_info.dst, dst_sw_if_index)
598                 self.logger.debug(
599                     "Got packet on interface %s: src=%u (idx=%u)" %
600                     (dst_if.name, payload_info.src, packet_index))
601
602                 # search for payload_info with same src and dst if_index
603                 # this will give us the transmitted packet
604                 next_info = self.get_next_packet_info_for_interface2(
605                     payload_info.src, dst_sw_if_index,
606                     last_info[payload_info.src])
607                 last_info[payload_info.src] = next_info
608                 # next_info should not be None
609                 self.assertTrue(next_info is not None)
610                 # index of tx and rx packets should be equal
611                 self.assertEqual(packet_index, next_info.index)
612                 # data field of next_info contains the tx packet
613                 txed_packet = next_info.data
614
615                 self.logger.debug(ppp("Transmitted packet:",
616                                       txed_packet))  # ppp=Pretty Print Packet
617
618                 self.logger.debug(ppp("Received packet:", packet))
619
620                 # compare rcvd packet with expected packet using compare_func
621                 compare_func(txed_packet, packet)
622
623             except:
624                 self.logger.error(ppp("Unexpected or invalid packet:", packet))
625                 raise
626
627         # have all expected packets arrived?
628         for i in self.pg_interfaces:
629             remaining_packet = self.get_next_packet_info_for_interface2(
630                 i.sw_if_index, dst_sw_if_index, last_info[i.sw_if_index])
631             self.assertTrue(remaining_packet is None,
632                             "Interface %s: Packet expected from interface %s "
633                             "didn't arrive" % (dst_if.name, i.name))
634
635
636 if __name__ == '__main__':
637     unittest.main(testRunner=VppTestRunner)