Make test support for the ipfix flow-per-pkt plugin
[vpp.git] / test / test_flowperpkt.py
1 #!/usr/bin/env python
2
3 import unittest
4 import socket
5 import binascii
6 import time
7
8 from framework import VppTestCase, VppTestRunner
9
10 from scapy.packet import Raw
11 from scapy.layers.l2 import Ether
12 from scapy.layers.inet import IP, UDP
13 from scapy.utils import hexdump
14 from util import ppp
15
16 class TestFlowperpkt(VppTestCase):
17     """ Flow-per-packet plugin: test both L2 and IP4 reporting """
18
19     def setUp(self):
20         """
21         Set up
22
23         **Config:**
24             - create three PG interfaces
25             - create a couple of loopback interfaces
26         """
27         super(TestFlowperpkt, self).setUp()
28
29         self.create_pg_interfaces(range(3))
30
31         self.pg_if_packet_sizes = [150]
32
33         self.interfaces = list(self.pg_interfaces)
34
35         for intf in self.interfaces:
36             intf.admin_up()
37             intf.config_ip4()
38             intf.resolve_arp()
39
40     def tearDown(self):
41         """Run standard test teardown"""
42         super(TestFlowperpkt, self).tearDown()
43
44
45     def create_stream(self, src_if, dst_if, packet_sizes):
46         """Create a packet stream to tickle the plugin
47
48         :param VppInterface src_if: Source interface for packet stream
49         :param VppInterface src_if: Dst interface for packet stream
50         :param list packet_sizes: Sizes to test
51         """
52         pkts = []
53         for size in packet_sizes:
54             info = self.create_packet_info(src_if.sw_if_index, 
55                                            dst_if.sw_if_index)
56             payload = self.info_to_payload(info)
57             p = (Ether(src=src_if.local_mac, dst=dst_if.remote_mac) /
58                  IP(src=src_if.remote_ip4, dst=dst_if.remote_ip4) /
59                  UDP(sport=1234, dport=4321) /
60                  Raw(payload))
61             info.data = p.copy()
62             self.extend_packet(p, size)
63             pkts.append(p)
64         return pkts
65
66     def verify_ipfix(self, collector_if):
67         """Check the ipfix capture"""
68         found_data_packet = 0
69         found_template_packet = 0
70         found_l2_data_packet = 0
71         found_l2_template_packet = 0
72
73         # Scapy, of course, understands ipfix not at all...
74         # These data vetted by manual inspection in wireshark
75         # X'ed out fields are timestamps, which will absolutely
76         # fail to compare. At L2, kill the pg src MAC address, which
77         # is random.
78         
79         data_udp_string = "1283128300370000000a002fXXXXXXXX00000000000000010100001f0000000100000002ac100102ac10020200XXXXXXXXXXXXXXXX0092"
80
81         template_udp_string = "12831283003c0000000a0034XXXXXXXX00000002000000010002002401000007000a0004000e000400080004000c000400050001009c000801380002"
82
83         l2_data_udp_string =  "12831283003c0000000a0034XXXXXXXX0000000100000001010100240000000100000002XXXXXXXXXXXX02020000ff020008XXXXXXXXXXXXXXXX0092"
84
85         l2_template_udp_string = "12831283003c0000000a0034XXXXXXXX00000002000000010002002401010007000a0004000e0004003800060050000601000002009c000801380002"
86
87         cap_x = "X"
88         data_udp_len = len(data_udp_string)
89         template_udp_len = len(template_udp_string)
90         l2_data_udp_len = len(l2_data_udp_string)
91         l2_template_udp_len = len(l2_template_udp_string)
92
93         self.logger.info("Look for ipfix packets on %s sw_if_index %d " 
94                          % (collector_if.name, collector_if.sw_if_index))
95         capture = collector_if.get_capture()
96
97         for p in capture:
98             data_result = ""
99             template_result = ""
100             l2_data_result = ""
101             l2_template_result = ""
102             unmasked_result = ""
103             ip = p[IP]
104             udp = p[UDP]
105             self.logger.info("src %s dst %s" % (ip.src, ip.dst))
106             self.logger.info(" udp src_port %s dst_port %s" 
107                              % (udp.sport, udp.dport))
108
109             # Hex-dump the UDP datagram 4 ways in parallel
110             # X'ing out incomparable fields
111             # Python completely bites at this sort of thing, of course
112
113             x = str(udp)
114             l = len(x)
115             i = 0
116             while i < l:
117                 # If current index within range
118                 if i < data_udp_len/2:
119                     # See if we're supposed to don't care the data
120                     if ord(data_udp_string[i*2]) == ord(cap_x[0]):
121                         data_result = data_result + "XX"
122                     else:
123                         data_result = data_result + ("%02x" % ord(x[i]))
124                 else:
125                     # index out of range, emit actual data
126                     # The test will fail, but it may help debug, etc.
127                     data_result = data_result + ("%02x" % ord(x[i]))
128                     
129                 if i < template_udp_len/2:
130                     if ord(template_udp_string[i*2]) == ord(cap_x[0]):
131                         template_result = template_result + "XX"
132                     else:
133                         template_result = template_result + ("%02x" % ord(x[i]))
134                 else:
135                     template_result = template_result + ("%02x" % ord(x[i]))
136
137                 if i < l2_data_udp_len/2:
138                     # See if we're supposed to don't care the data
139                     if ord(l2_data_udp_string[i*2]) == ord(cap_x[0]):
140                         l2_data_result = l2_data_result + "XX"
141                     else:
142                         l2_data_result = l2_data_result + ("%02x" % ord(x[i]))
143                 else:
144                     # index out of range, emit actual data
145                     # The test will fail, but it may help debug, etc.
146                     l2_data_result = l2_data_result + ("%02x" % ord(x[i]))
147                 
148                 if i < l2_template_udp_len/2:
149                     if ord(l2_template_udp_string[i*2]) == ord(cap_x[0]):
150                         l2_template_result = l2_template_result + "XX"
151                     else:
152                         l2_template_result = l2_template_result + ("%02x" % ord(x[i]))
153                 else:
154                     l2_template_result = l2_template_result + ("%02x" % ord(x[i]))
155                 # In case we need to 
156                 unmasked_result = unmasked_result + ("%02x" % ord(x[i]))
157
158                 i = i + 1
159
160             if data_result == data_udp_string:
161                 self.logger.info ("found ip4 data packet")
162                 found_data_packet = 1
163             elif template_result == template_udp_string:
164                 self.logger.info ("found ip4 template packet")
165                 found_template_packet = 1
166             elif l2_data_result == l2_data_udp_string:
167                 self.logger.info ("found l2 data packet")
168                 found_l2_data_packet = 1
169             elif l2_template_result == l2_template_udp_string:
170                 self.logger.info ("found l2 template packet")
171                 found_l2_template_packet = 1
172             else:
173                 self.logger.info ("unknown pkt '%s'" % unmasked_result)
174                 
175         self.assertTrue (found_data_packet == 1)
176         self.assertTrue (found_template_packet == 1)
177         self.assertTrue (found_l2_data_packet == 1)
178         self.assertTrue (found_l2_template_packet == 1)
179
180     def test_L3_fpp(self):
181         """ Flow per packet L3 test """
182
183         # Configure an ipfix report on the [nonexistent] collector
184         # 172.16.3.2, as if it was connected to the pg2 interface
185         # Install a FIB entry, so the exporter's work won't turn into
186         # an ARP request
187
188         self.pg_enable_capture(self.pg_interfaces)
189         self.vapi.cli("set ip arp pg2 172.16.3.2 dead.beef.0002")
190         self.logger.info(self.vapi.cli("set ipfix exporter collector 172.16.3.2 src 172.16.3.1 path-mtu 1450 template-interval 1"))
191
192         # Export flow records for all pkts transmitted on pg1
193
194         self.logger.info(self.vapi.cli("flowperpkt feature add-del pg1"))
195         self.logger.info(self.vapi.cli("flowperpkt feature add-del pg1 l2"))
196
197         # Arrange to minimally trace generated ipfix packets
198         self.logger.info(self.vapi.cli("trace add flowperpkt-ipv4 10"))
199         self.logger.info(self.vapi.cli("trace add flowperpkt-l2 10"))
200
201         # Create a stream from pg0 -> pg1, which causes
202         # an ipfix packet to be transmitted on pg2
203         
204         pkts = self.create_stream(self.pg0, self.pg1, 
205                                   self.pg_if_packet_sizes)
206         self.pg0.add_stream(pkts)
207         self.pg_start()
208         
209         # Flush the ipfix collector, so we don't need any
210         # asinine time.sleep(5) action
211
212         self.logger.info(self.vapi.cli("ipfix flush"))
213         
214         # Make sure the 4 pkts we expect actually showed up
215         self.verify_ipfix(self.pg2)
216
217 if __name__ == '__main__':
218     unittest.main(testRunner=VppTestRunner)
219             
220         
221     
222