#!/usr/bin/env python import unittest from random import shuffle from framework import VppTestCase, VppTestRunner from scapy.packet import Raw from scapy.layers.l2 import Ether, GRE from scapy.layers.inet import IP, UDP from util import ppp, fragment_rfc791, fragment_rfc8200 from scapy.layers.inet6 import IPv6, IPv6ExtHdrFragment, ICMPv6ParamProblem,\ ICMPv6TimeExceeded from vpp_gre_interface import VppGreInterface, VppGre6Interface from vpp_ip_route import VppIpRoute, VppRoutePath, DpoProto test_packet_count = 257 class TestIPv4Reassembly(VppTestCase): """ IPv4 Reassembly """ @classmethod def setUpClass(cls): super(TestIPv4Reassembly, cls).setUpClass() cls.create_pg_interfaces([0, 1]) cls.src_if = cls.pg0 cls.dst_if = cls.pg1 # setup all interfaces for i in cls.pg_interfaces: i.admin_up() i.config_ip4() i.resolve_arp() # packet sizes cls.packet_sizes = [64, 512, 1518, 9018] cls.padding = " abcdefghijklmn" cls.create_stream(cls.packet_sizes) cls.create_fragments() def setUp(self): """ Test setup - force timeout on existing reassemblies """ super(TestIPv4Reassembly, self).setUp() self.vapi.ip_reassembly_enable_disable( sw_if_index=self.src_if.sw_if_index, enable_ip4=True) self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000, expire_walk_interval_ms=10) self.sleep(.25) self.vapi.ip_reassembly_set(timeout_ms=1000000, max_reassemblies=1000, expire_walk_interval_ms=10000) def tearDown(self): super(TestIPv4Reassembly, self).tearDown() self.logger.debug(self.vapi.ppcli("show ip4-reassembly details")) @classmethod def create_stream(cls, packet_sizes, packet_count=test_packet_count): """Create input packet stream for defined interface. :param list packet_sizes: Required packet sizes. """ for i in range(0, packet_count): info = cls.create_packet_info(cls.src_if, cls.src_if) payload = cls.info_to_payload(info) p = (Ether(dst=cls.src_if.local_mac, src=cls.src_if.remote_mac) / IP(id=info.index, src=cls.src_if.remote_ip4, dst=cls.dst_if.remote_ip4) / UDP(sport=1234, dport=5678) / Raw(payload)) size = packet_sizes[(i // 2) % len(packet_sizes)] cls.extend_packet(p, size, cls.padding) info.data = p @classmethod def create_fragments(cls): infos = cls._packet_infos cls.pkt_infos = [] for index, info in infos.iteritems(): p = info.data # cls.logger.debug(ppp("Packet:", p.__class__(str(p)))) fragments_400 = fragment_rfc791(p, 400) fragments_300 = fragment_rfc791(p, 300) fragments_200 = [ x for f in fragments_400 for x in fragment_rfc791(f, 200)] cls.pkt_infos.append( (index, fragments_400, fragments_300, fragments_200)) cls.fragments_400 = [ x for (_, frags, _, _) in cls.pkt_infos for x in frags] cls.fragments_300 = [ x for (_, _, frags, _) in cls.pkt_infos for x in frags] cls.fragments_200 = [ x for (_, _, _, frags) in cls.pkt_infos for x in frags] cls.logger.debug("Fragmented %s packets into %s 400-byte fragments, " "%s 300-byte fragments and %s 200-byte fragments" % (len(infos), len(cls.fragments_400), len(cls.fragments_300), len(cls.fragments_200))) def verify_capture(self, capture, dropped_packet_indexes=[]): """Verify captured packet stream. :param list capture: Captured packet stream. """ info = None seen = set() for packet in capture: try: self.logger.debug(ppp("Got packet:", packet)) ip = packet[IP] udp = packet[UDP] payload_info = self.payload_to_info(str(packet[Raw])) packet_index = payload_info.index self.assertTrue( packet_index not in dropped_packet_indexes, ppp("Packet received, but should be dropped:", packet)) if packet_index in seen: raise Exception(ppp("Duplicate packet received", packet)) seen.add(packet_index) self.assertEqual(payload_info.dst, self.src_if.sw_if_index) info = self._packet_infos[packet_index] self.assertTrue(info is not None) self.assertEqual(packet_index, info.index) saved_packet = info.data self.assertEqual(ip.src, saved_packet[IP].src) self.assertEqual(ip.dst, saved_packet[IP].dst) self.assertEqual(udp.payload, saved_packet[UDP].payload) except Exception: self.logger.error(ppp("Unexpected or invalid packet:", packet)) raise for index in self._packet_infos: self.assertTrue(index in seen or index in dropped_packet_indexes, "Packet with packet_index %d not received" % index) def test_reassembly(self): """ basic reassembly """ self.pg_enable_capture() self.src_if.add_stream(self.fragments_200) self.pg_start() packets = self.dst_if.get_capture(len(self.pkt_infos)) self.verify_capture(packets) self.src_if.assert_nothing_captured() # run it all again to verify correctness self.pg_enable_capture() self.src_if.add_stream(self.fragments_200) self.pg_start() packets = self.dst_if.get_capture(len(self.pkt_infos)) self.verify_capture(packets) self.src_if.assert_nothing_captured() def test_reversed(self): """ reverse order reassembly """ fragments = list(self.fragments_200) fragments.reverse() self.pg_enable_capture() self.src_if.add_stream(fragments) self.pg_start() packets = self.dst_if.get_capture(len(self.packet_infos)) self.verify_capture(packets) self.src_if.assert_nothing_captured() # run it all again to verify correctness self.pg_enable_capture() self.src_if.add_stream(fragments) self.pg_start() packets = self.dst_if.get_capture(len(self.packet_infos)) self.verify_capture(packets) self.src_if.assert_nothing_captured() def test_random(self): """ random order reassembly """ fragments = list(self.fragments_200) shuffle(fragments) self.pg_enable_capture() self.src_if.add_stream(fragments) self.pg_start() packets = self.dst_if.get_capture(len(self.packet_infos)) self.verify_capture(packets) self.src_if.assert_nothing_captured() # run it all again to verify correctness self.pg_enable_capture() self.src_if.add_stream(fragments) self.pg_start() packets = self.dst_if.get_capture(len(self.packet_infos)) self.verify_capture(packets) self.src_if.assert_nothing_captured() def test_duplicates(self): """ duplicate fragments """ fragments = [ x for (_, frags, _, _) in self.pkt_infos for x in frags for _ in range(0, min(2, len(frags))) ] self.pg_enable_capture() self.src_if.add_stream(fragments) self.pg_start() packets = self.dst_if.get_capture(len(self.pkt_infos)) self.verify_capture(packets) self.src_if.assert_nothing_captured() def test_overlap1(self): """ overlapping fragments case #1 """ fragments = [] for _, _, frags_300, frags_200 in self.pkt_infos: if len(frags_300) == 1: fragments.extend(frags_300) else: for i, j in zip(frags_200, frags_300): fragments.extend(i) fragments.extend(j) self.pg_enable_capture() self.src_if.add_stream(fragments) self.pg_start() packets = self.dst_if.get_capture(len(self.pkt_infos)) self.verify_capture(packets) self.src_if.assert_nothing_captured() # run it all to verify correctness self.pg_enable_capture() self.src_if.add_stream(fragments) self.pg_start() packets = self.dst_if.get_capture(len(self.pkt_infos)) self.verify_capture(packets) self.src_if.assert_nothing_captured() def test_overlap2(self): """ overlapping fragments case #2 """ fragments = [] for _, _, frags_300, frags_200 in self.pkt_infos: if len(frags_300) == 1: fragments.extend(frags_300) else: # care must be taken here so that there are no fragments # received by vpp after reassembly is finished, otherwise # new reassemblies will be started and packet generator will # freak out when it detects unfreed buffers zipped = zip(frags_300, frags_200) for i, j in zipped[:-1]: fragments.extend(i) fragments.extend(j) fragments.append(zipped[-1][0]) self.pg_enable_capture() self.src_if.add_stream(fragments) self.pg_start() packets = self.dst_if.get_capture(len(self.pkt_infos)) self.verify_capture(packets) self.src_if.assert_nothing_captured() # run it all to verify correctness self.pg_enable_capture() self.src_if.add_stream(fragments) self.pg_start() packets = self.dst_if.get_capture(len(self.pkt_infos)) self.verify_capture(packets) self.src_if.assert_nothing_captured() def test_timeout_inline(self): """ timeout (inline) """ dropped_packet_indexes = set( index for (index, frags, _, _) in self.pkt_infos if len(frags) > 1 ) self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000, expire_walk_interval_ms=10000) self.pg_enable_capture() self.src_if.add_stream(self.fragments_400) self.pg_start() packets = self.dst_if.get_capture( len(self.pkt_infos) - len(dropped_packet_indexes)) self.verify_capture(packets, dropped_packet_indexes) self.src_if.assert_nothing_captured() def test_timeout_cleanup(self): """ timeout (cleanup) """ # whole packets + fragmented packets sans last fragment fragments = [ x for (_, frags_400, _, _) in self.pkt_infos for x in frags_400[:-1 if len(frags_400) > 1 else None] ] # last fragments for fragmented packets fragments2 = [frags_400[-1] for (_, frags_400, _, _) in self.pkt_infos if len(frags_400) > 1] dropped_packet_indexes = set( index for (index, frags_400, _, _) in self.pkt_infos if len(frags_400) > 1) self.vapi.ip_reassembly_set(timeout_ms=100, max_reassemblies=1000, expire_walk_interval_ms=50) self.pg_enable_capture() self.src_if.add_stream(fragments) self.pg_start() self.sleep(.25, "wait before sending rest of fragments") self.src_if.add_stream(fragments2) self.pg_start() packets = self.dst_if.get_capture( len(self.pkt_infos) - len(dropped_packet_indexes)) self.verify_capture(packets, dropped_packet_indexes) self.src_if.assert_nothing_captured() def test_disabled(self): """ reassembly disabled """ dropped_packet_indexes = set( index for (index, frags_400, _, _) in self.pkt_infos if len(frags_400) > 1) self.vapi.ip_reassembly_set(timeout_ms=1000, max_reassemblies=0, expire_walk_interval_ms=10000) self.pg_enable_capture() self.src_if.add_stream(self.fragments_400) self.pg_start() packets = self.dst_if.get_capture( len(self.pkt_infos) - len(dropped_packet_indexes)) self.verify_capture(packets, dropped_packet_indexes) self.src_if.assert_nothing_captured() class TestIPv6Reassembly(VppTestCase): """ IPv6 Reassembly """ @classmethod def setUpClass(cls): super(TestIPv6Reassembly, cls).setUpClass() cls.create_pg_interfaces([0, 1]) cls.src_if = cls.pg0 cls.dst_if = cls.pg1 # setup all interfaces for i in cls.pg_interfaces: i.admin_up() i.config_ip6() i.resolve_ndp() # packet sizes cls.packet_sizes = [64, 512, 1518, 9018] cls.padding = " abcdefghijklmn" cls.create_stream(cls.packet_sizes) cls.create_fragments() def setUp(self): """ Test setup - force timeout on existing reassemblies """ super(TestIPv6Reassembly, self).setUp() self.vapi.ip_reassembly_enable_disable( sw_if_index=self.src_if.sw_if_index, enable_ip6=True) self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000, expire_walk_interval_ms=10, is_ip6=1) self.sleep(.25) self.vapi.ip_reassembly_set(timeout_ms=1000000, max_reassemblies=1000, expire_walk_interval_ms=10000, is_ip6=1) self.logger.debug(self.vapi.ppcli("show ip6-reassembly details")) def tearDown(self): super(TestIPv6Reassembly, self).tearDown() self.logger.debug(self.vapi.ppcli("show ip6-reassembly details")) @classmethod def create_stream(cls, packet_sizes, packet_count=test_packet_count): """Create input packet stream for defined interface. :param list packet_sizes: Required packet sizes. """ for i in range(0, packet_count): info = cls.create_packet_info(cls.src_if, cls.src_if) payload = cls.info_to_payload(info) p = (Ether(dst=cls.src_if.local_mac, src=cls.src_if.remote_mac) / IPv6(src=cls.src_if.remote_ip6, dst=cls.dst_if.remote_ip6) / UDP(sport=1234, dport=5678) / Raw(payload)) size = packet_sizes[(i // 2) % len(packet_sizes)] cls.extend_packet(p, size, cls.padding) info.data = p @classmethod def create_fragments(cls): infos = cls._packet_infos cls.pkt_infos = [] for index, info in infos.iteritems(): p = info.data # cls.logger.debug(ppp("Packet:", p.__class__(str(p)))) fragments_400 = fragment_rfc8200(p, info.index, 400) fragments_300 = fragment_rfc8200(p, info.index, 300) cls.pkt_infos.append((index, fragments_400, fragments_300)) cls.fragments_400 = [ x for _, frags, _ in cls.pkt_infos for x in frags] cls.fragments_300 = [ x for _, _, frags in cls.pkt_infos for x in frags] cls.logger.debug("Fragmented %s packets into %s 400-byte fragments, " "and %s 300-byte fragments" % (len(infos), len(cls.fragments_400), len(cls.fragments_300))) def verify_capture(self, capture, dropped_packet_indexes=[]): """Verify captured packet strea . :param list capture: Captured packet stream. """ info = None seen = set() for packet in capture: try: self.logger.debug(ppp("Got packet:", packet)) ip = packet[IPv6] udp = packet[UDP] payload_info = self.payload_to_info(str(packet[Raw])) packet_index = payload_info.index self.assertTrue( packet_index not in dropped_packet_indexes, ppp("Packet received, but should be dropped:", packet)) if packet_index in seen: raise Exception(ppp("Duplicate packet received", packet)) seen.add(packet_index) self.assertEqual(payload_info.dst, self.src_if.sw_if_index) info = self._packet_infos[packet_index] self.assertTrue(info is not None) self.assertEqual(packet_index, info.index) saved_packet = info.data self.assertEqual(ip.src, saved_packet[IPv6].src) self.assertEqual(ip.dst, saved_packet[IPv6].dst) self.assertEqual(udp.payload, saved_packet[UDP].payload) except Exception: self.logger.error(ppp("Unexpected or invalid packet:", packet)) raise for index in self._packet_infos: self.assertTrue(index in seen or index in dropped_packet_indexes, "Packet with packet_index %d not received" % index) def test_reassembly(self): """ basic reassembly """ self.pg_enable_capture() self.src_if.add_stream(self.fragments_400) self.pg_start() packets = self.dst_if.get_capture(len(self.pkt_infos)) self.verify_capture(packets) self.src_if.assert_nothing_captured() # run it all again to verify correctness self.pg_enable_capture() self.src_if.add_stream(self.fragments_400) self.pg_start() packets = self.dst_if.get_capture(len(self.pkt_infos)) self.verify_capture(packets) self.src_if.assert_nothing_captured() def test_reversed(self): """ reverse order reassembly """ fragments = list(self.fragments_400) fragments.reverse() self.pg_enable_capture() self.src_if.add_stream(fragments) self.pg_start() packets = self.dst_if.get_capture(len(self.pkt_infos)) self.verify_capture(packets) self.src_if.assert_nothing_captured() # run it all again to verify correctness self.pg_enable_capture() self.src_if.add_stream(fragments) self.pg_start() packets = self.dst_if.get_capture(len(self.pkt_infos)) self.verify_capture(packets) self.src_if.assert_nothing_captured() def test_random(self): """ random order reassembly """ fragments = list(self.fragments_400) shuffle(fragments) self.pg_enable_capture() self.src_if.add_stream(fragments) self.pg_start() packets = self.dst_if.get_capture(len(self.pkt_infos)) self.verify_capture(packets) self.src_if.assert_nothing_captured() # run it all again to verify correctness self.pg_enable_capture() self.src_if.add_stream(fragments) self.pg_start() packets = self.dst_if.get_capture(len(self.pkt_infos)) self.verify_capture(packets) self.src_if.assert_nothing_captured() def test_duplicates(self): """ duplicate fragments """ fragments = [ x for (_, frags, _) in self.pkt_infos for x in frags for _ in range(0, min(2, len(frags))) ] self.pg_enable_capture() self.src_if.add_stream(fragments) self.pg_start() packets = self.dst_if.get_capture(len(self.pkt_infos)) self.verify_capture(packets) self.src_if.assert_nothing_captured() def test_overlap1(self): """ overlapping fragments case #1 """ fragments = [] for _, frags_400, frags_300 in self.pkt_infos: if len(frags_300) == 1: fragments.extend(frags_400) else: for i, j in zip(frags_300, frags_400): fragments.extend(i) fragments.extend(j) dropped_packet_indexes = set( index for (index, _, frags) in self.pkt_infos if len(frags) > 1 ) self.pg_enable_capture() self.src_if.add_stream(fragments) self.pg_start() packets = self.dst_if.get_capture( len(self.pkt_infos) - len(dropped_packet_indexes)) self.verify_capture(packets, dropped_packet_indexes) self.src_if.assert_nothing_captured() def test_overlap2(self): """ overlapping fragments case #2 """ fragments = [] for _, frags_400, frags_300 in self.pkt_infos: if len(frags_400) == 1: fragments.extend(frags_400) else: # care must be taken here so that there are no fragments # received by vpp after reassembly is finished, otherwise # new reassemblies will be started and packet generator will # freak out when it detects unfreed buffers zipped = zip(frags_400, frags_300) for i, j in zipped[:-1]: fragments.extend(i) fragments.extend(j) fragments.append(zipped[-1][0]) dropped_packet_indexes = set( index for (index, _, frags) in self.pkt_infos if len(frags) > 1 ) self.pg_enable_capture() self.src_if.add_stream(fragments) self.pg_start() packets = self.dst_if.get_capture( len(self.pkt_infos) - len(dropped_packet_indexes)) self.verify_capture(packets, dropped_packet_indexes) self.src_if.assert_nothing_captured() def test_timeout_inline(self): """ timeout (inline) """ dropped_packet_indexes = set( index for (index, frags, _) in self.pkt_infos if len(frags) > 1 ) self.vapi.ip_reassembly_set(timeout_ms=0, max_reassemblies=1000, expire_walk_interval_ms=10000, is_ip6=1) self.pg_enable_capture() self.src_if.add_stream(self.fragments_400) self.pg_start() packets = self.dst_if.get_capture( len(self.pkt_infos) - len(dropped_packet_indexes)) self.verify_capture(packets, dropped_packet_indexes) pkts = self.src_if.get_capture( expected_count=len(dropped_packet_indexes)) for icmp in pkts: self.assertIn(ICMPv6TimeExceeded, icmp) self.assertIn(IPv6ExtHdrFragment, icmp) self.assertIn(icmp[IPv6ExtHdrFragment].id, dropped_packet_indexes) dropped_packet_indexes.remove(icmp[IPv6ExtHdrFragment].id) def test_timeout_cleanup(self): """ timeout (cleanup) """ # whole pack
/*
 * Copyright (c) 2016 Cisco and/or its affiliates.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at:
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef __FIB_ENTRY_H__
#define __FIB_ENTRY_H__

#include <vnet/fib/fib_node.h>
#include <vnet/fib/fib_entry_delegate.h>
#include <vnet/adj/adj.h>
#include <vnet/ip/ip.h>
#include <vnet/dpo/dpo.h>

/**
 * The different sources that can create a route.
 * The sources are defined here the thier relative priority order.
 * The lower the value the higher the priority
 */
typedef enum fib_source_t_ {
    /**
     * Marker. Add new values after this one.
     */
    FIB_SOURCE_FIRST,
    /**
     * Special sources. These are for entries that are added to all
     * FIBs by default, and should never be over-ridden (hence they
     * are the highest priority)
     */
    FIB_SOURCE_SPECIAL = FIB_SOURCE_FIRST,
    /**
     * Classify. A route that links directly to a classify adj
     */
    FIB_SOURCE_CLASSIFY,
    /**
     * Route added as a result of interface configuration.
     * this will also come from the API/CLI, but the distinction is
     * that is from confiiguration on an interface, not a 'ip route' command
     */
    FIB_SOURCE_INTERFACE,
    /**
     * SRv6 and SR-MPLS
     */
    FIB_SOURCE_SR,
    /**
     * A high priority source a plugin can use
     */
    FIB_SOURCE_PLUGIN_HI,
    /**
     * From the control plane API
     */
    FIB_SOURCE_API,
    /**
     * From the CLI.
     */
    FIB_SOURCE_CLI,
    /**
     * LISP
     */
    FIB_SOURCE_LISP,
    /**
     * IPv[46] Mapping
     */
    FIB_SOURCE_MAP,
    /**
     * SIXRD
     */
    FIB_SOURCE_SIXRD,
    /**
     * DHCP
     */
    FIB_SOURCE_DHCP,
    /**
     * IPv6 Proxy ND
     */
    FIB_SOURCE_IP6_ND_PROXY,
    /**
     * Adjacency source.
     * routes created as a result of ARP/ND entries. This is lower priority
     * then the API/CLI. This is on purpose. trust me.
     */
    FIB_SOURCE_ADJ,