Dave Barach | 0f3b680 | 2016-12-23 15:15:48 -0500 | [diff] [blame] | 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 | |