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