blob: af68a69e09beabc6c76addc3ccffa9239646eb4f [file] [log] [blame]
Dave Barach0f3b6802016-12-23 15:15:48 -05001#!/usr/bin/env python
2
3import unittest
4import socket
5import binascii
6import time
7
8from framework import VppTestCase, VppTestRunner
9
10from scapy.packet import Raw
11from scapy.layers.l2 import Ether
12from scapy.layers.inet import IP, UDP
13from scapy.utils import hexdump
14from util import ppp
15
16class 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
217if __name__ == '__main__':
218 unittest.main(testRunner=VppTestRunner)
219
220
221
222