#!/usr/bin/env python3 """IP{4,6} over IP{v,6} tunnel functional tests""" import unittest from scapy.layers.inet6 import IPv6, Ether, IP, UDP, IPv6ExtHdrFragment, Raw from scapy.contrib.mpls import MPLS from scapy.all import fragment, fragment6, RandShort, defragment6 from framework import VppTestCase, VppTestRunner from vpp_ip import DpoProto from vpp_ip_route import VppIpRoute, VppRoutePath, VppIpTable, FibPathProto, \ VppMplsLabel, VppMplsRoute, VppMplsTable from vpp_ipip_tun_interface import VppIpIpTunInterface from vpp_teib import VppTeib from vpp_papi import VppEnum from socket import AF_INET, AF_INET6, inet_pton from util import reassemble4 """ Testipip is a subclass of VPPTestCase classes. IPIP tests. """ def ipip_add_tunnel(test, src, dst, table_id=0, dscp=0x0, flags=0): """ Add a IPIP tunnel """ return test.vapi.ipip_add_tunnel( tunnel={ 'src': src, 'dst': dst, 'table_id': table_id, 'instance': 0xffffffff, 'dscp': dscp, 'flags': flags } ) # the number of packets to send when injecting traffic. # a multiple of 8 minus one, so we test all by 8/4/2/1 loops N_PACKETS = 64 - 1 class TestIPIP(VppTestCase): """ IPIP Test Case """ @classmethod def setUpClass(cls): super(TestIPIP, cls).setUpClass() cls.create_pg_interfaces(range(3)) cls.interfaces = list(cls.pg_interfaces) @classmethod def tearDownClass(cls): super(TestIPIP, cls).tearDownClass() def setUp(self): super(TestIPIP, self).setUp() self.table = VppIpTable(self, 1, register=False) self.table.add_vpp_config() for i in self.interfaces: i.admin_up() self.pg2.set_table_ip4(self.table.table_id) for i in self.interfaces: i.config_ip4() i.config_ip6() i.disable_ipv6_ra() i.resolve_arp() i.resolve_ndp() def tearDown(self): super(TestIPIP, self).tearDown() if not self.vpp_dead: for i in self.pg_interfaces: i.unconfig_ip4() i.unconfig_ip6() i.set_table_ip4(0) i.admin_down() self.table.remove_vpp_config() def validate(self, rx, expected): self.assertEqual(rx, expected.__class__(expected)) def generate_ip4_frags(self, payload_length, fragment_size): p_ether = Ether(src=self.pg1.remote_mac, dst=self.pg1.local_mac) p_payload = UDP(sport=1234, dport=1234) / self.payload(payload_length) p_ip4 = IP(src="1.2.3.4", dst=self.pg0.remote_ip4) outer_ip4 = (p_ether / IP(src=self.pg1.remote_ip4, id=RandShort(), dst=self.pg0.local_ip4) / p_ip4 / p_payload) frags = fragment(outer_ip4, fragment_size) p4_reply = (p_ip4 / p_payload) p4_reply.ttl -= 1 return frags, p4_reply def verify_ip4ip4_encaps(self, a, p_ip4s, p_ip4_encaps): for i, p_ip4 in enumerate(p_ip4s): p_ip4.dst = a p4 = (self.p_ether / p_ip4 / self.p_payload) p_ip4_inner = p_ip4 p_ip4_inner.ttl -= 1 p4_reply = (p_ip4_encaps[i] / p_ip4_inner / self.p_payload) p4_reply.ttl -= 1 p4_reply.id = 0 rx = self.send_and_expect(self.pg0, p4 * N_PACKETS, self.pg1) for p in rx: self.validate(p[1], p4_reply) self.assert_packet_checksums_valid(p) def verify_ip6ip4_encaps(self, a, p_ip6s, p_ip4_encaps): for i, p_ip6 in enumerate(p_ip6s): p_ip6.dst = a p6 = (self.p_ether / p_ip6 / self.p_payload) p_inner_ip6 = p_ip6 p_inner_ip6.hlim -= 1 p6_reply = (p_ip4_encaps[i] / p_inner_ip6 / self.p_payload) p6_reply.ttl -= 1 rx = self.send_and_expect(self.pg0, p6 * N_PACKETS, self.pg1) for p in rx: self.validate(p[1], p6_reply) self.assert_packet_checksums_valid(p) def test_ipip4(self): """ ip{v4,v6} over ip4 test """ self.pg1.generate_remote_hosts(5) self.pg1.configure_ipv4_neighbors() e = VppEnum.vl_api_tunnel_encap_decap_flags_t d = VppEnum.vl_api_ip_dscp_t self.p_ether = Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) self.p_payload = UDP(sport=1234, dport=1234) / Raw(b'X' * 100) # create a TOS byte by shifting a DSCP code point 2 bits. those 2 bits # are for the ECN. dscp = d.IP_API_DSCP_AF31 << 2 ecn = 3 dscp_ecn = d.IP_API_DSCP_AF31 << 2 | ecn # IPv4 transport that copies the DCSP from the payload tun_dscp = VppIpIpTunInterface( self, self.pg0, self.pg0.local_ip4, self.pg1.remote_hosts[0].ip4, flags=e.TUNNEL_API_ENCAP_DECAP_FLAG_ENCAP_COPY_DSCP) tun_dscp.add_vpp_config() # IPv4 transport that copies the DCSP and ECN from the payload tun_dscp_ecn = VppIpIpTunInterface( self, self.pg0, self.pg0.local_ip4, self.pg1.remote_hosts[1].ip4, flags=(e.TUNNEL_API_ENCAP_DECAP_FLAG_ENCAP_COPY_DSCP | e.TUNNEL_API_ENCAP_DECAP_FLAG_ENCAP_COPY_ECN)) tun_dscp_ecn.add_vpp_config() # IPv4 transport that copies the ECN from the payload and sets the # DF bit on encap. copies the ECN on decap tun_ecn = VppIpIpTunInterface( self, self.pg0, self.pg0.local_ip4, self.pg1.remote_hosts[2].ip4, flags=(e.TUNNEL_API_ENCAP_DECAP_FLAG_ENCAP_COPY_ECN | e.TUNNEL_API_ENCAP_DECAP_FLAG_ENCAP_SET_DF | e.TUNNEL_API_ENCAP_DECAP_FLAG_DECAP_COPY_ECN)) tun_ecn.add_vpp_config() # IPv4 transport that sets a fixed DSCP in the encap and copies # the DF bit tun = VppIpIpTunInterface( self, self.pg0, self.pg0.local_ip4, self.pg1.remote_hosts[3].ip4, dscp=d.IP_API_DSCP_AF11, flags=e.TUNNEL_API_ENCAP_DECAP_FLAG_ENCAP_COPY_DF) tun.add_vpp_config() # array of all the tunnels tuns = [tun_dscp, tun_dscp_ecn, tun_ecn, tun] # addresses for prefixes routed via each tunnel a4s = ["" for i in range(len(tuns))] a6s = ["" for i in range(len(tuns))] # IP headers with each combination of DSCp/ECN tested p_ip6s = [IPv6(src="1::1", dst="DEAD::1", nh='UDP', tc=dscp), IPv6(src="1::1", dst="DEAD::1", nh='UDP', tc=dscp_ecn), IPv6(src="1::1", dst="DEAD::1", nh='UDP', tc=ecn), IPv6(src="1::1", dst="DEAD::1", nh='UDP', tc=0xff)] p_ip4s = [IP(src="1.2.3.4", dst="130.67.0.1", tos=dscp, flags='DF'), IP(src="1.2.3.4", dst="130.67.0.1", tos=dscp_ecn), IP(src="1.2.3.4", dst="130.67.0.1", tos=ecn), IP(src="1.2.3.4", dst="130.67.0.1", tos=0xff)] # Configure each tunnel for i, t in enumerate(tuns): # Set interface up and enable IP on it self.vapi.sw_interface_set_flags(t.sw_if_index, 1) self.vapi.sw_interface_set_unnumbered( sw_if_index=self.pg0.sw_if_index, unnumbered_sw_if_index=t.sw_if_index) # prefix for route / destination address for packets a4s[i] = "130.67.%d.0" % i a6s[i] = "dead:%d::" % i # Add IPv4 and IPv6 routes via tunnel interface ip4_via_tunnel = VppIpRoute( self, a4s[i], 24, [VppRoutePath("0.0.0.0", t.sw_if_index, proto=FibPathProto.FIB_PATH_NH_PROTO_IP4)]) ip4_via_tunnel.add_vpp_config() ip6_via_tunnel = VppIpRoute( self, a6s[i], 64, [VppRoutePath("::", t.sw_if_index, proto=FibPathProto.FIB_PATH_NH_PROTO_IP6)]) ip6_via_tunnel.add_vpp_config() # # Encapsulation # # tun_dscp copies only the dscp # expected TC values are thus only the DCSP value is present from the # inner exp_tcs = [dscp, dscp, 0, 0xfc] p_ip44_encaps = [IP(src=self.pg0.local_ip4, dst=tun_dscp.dst, tos=tc) for tc in exp_tcs] p_ip64_encaps = [IP(src=self.pg0.local_ip4, dst=tun_dscp.dst, proto='ipv6', id=0, tos=tc) for tc in exp_tcs] # IPv4 in to IPv4 tunnel self.verify_ip4ip4_encaps(a4s[0], p_ip4s, p_ip44_encaps) # IPv6 in to IPv4 tunnel self.verify_ip6ip4_encaps(a6s[0], p_ip6s, p_ip64_encaps) # tun_dscp_ecn copies the dscp and the ecn exp_tcs = [dscp, dscp_ecn, ecn, 0xff] p_ip44_encaps = [IP(src=self.pg0.local_ip4, dst=tun_dscp_ecn.dst, tos=tc) for tc in exp_tcs] p_ip64_encaps = [IP(src=self.pg0.local_ip4, dst=tun_dscp_ecn.dst, proto='ipv6', id=0, tos=tc) for tc in exp_tcs] self.verify_ip4ip4_encaps(a4s[1], p_ip4s, p_ip44_encaps) self.verify_ip6ip4_encaps(a6s[1], p_ip6s, p_ip64_encaps) # tun_ecn copies only the ecn and always sets DF exp_tcs = [0, ecn, ecn, ecn] p_ip44_encaps = [IP(src=self.pg0.local_ip4, dst=tun_ecn.dst, flags='DF', tos=tc) for tc in exp_tcs] p_ip64_encaps = [IP(src=self.pg0.local_ip4, dst=tun_ecn.dst, flags='DF', proto='ipv6', id=0, tos=tc) for tc in exp_tcs] self.verify_ip4ip4_encaps(a4s[2], p_ip4s, p_ip44_encaps) self.verify_ip6ip4_encaps(a6s[2], p_ip6s, p_ip64_encaps) # tun sets a fixed dscp and copies DF fixed_dscp = tun.dscp << 2 flags = ['DF', 0, 0, 0] p_ip44_encaps = [IP(src=self.pg0.local_ip4, dst=tun.dst, flags=f, tos=fixed_dscp) for f in flags] p_ip64_encaps = [IP(src=self.pg0.local_ip4, dst=tun.dst, proto='ipv6', id=0, tos=fixed_dscp) for i in range(len(p_ip4s))] self.verify_ip4ip4_encaps(a4s[3], p_ip4s, p_ip44_encaps) self.verify_ip6ip4_encaps(a6s[3], p_ip6s, p_ip64_encaps) # # Decapsulation # n_packets_decapped = 0 self.p_ether = Ether(src=self.pg1.remote_mac, dst=self.pg1.local_mac) # IPv4 tunnel to IPv4 tcs = [0, dscp, dscp_ecn, ecn] # one overlay packet and all combinations of its encap p_ip4 = IP(src="1.2.3.4", dst=self.pg0.remote_ip4) p_ip4_encaps = [IP(src=tun.dst, dst=self.pg0.local_ip4, tos=tc) for tc in tcs] # for each encap tun will produce the same inner packet because it does # not copy up fields from the payload for p_ip4_encap in p_ip4_encaps: p4 = (self.p_ether / p_ip4_encap / p_ip4 / self.p_payload) p4_reply = (p_ip4 / self.p_payload) p4_reply.ttl -= 1 rx = self.send_and_expect(self.pg1, p4 * N_PACKETS, self.pg0) n_packets_decapped += N_PACKETS for p in rx: self.validate(p[1], p4_reply) self.assert_packet_checksums_valid(p) err = self.statistics.get_err_counter( '/err/ipip4-input/packets decapsulated') self.assertEqual(err, n_packets_decapped) # tun_ecn copies the ECN bits from the encap to the inner p_ip4_encaps = [IP(src=tun_ecn.dst, dst=self.pg0.local_ip4, tos=tc) for tc in tcs] p_ip4_replys = [p_ip4.copy() for i in range(len(p_ip4_encaps))] p_ip4_replys[2].tos = ecn p_ip4_replys[3].tos = ecn for i, p_ip4_encap in enumerate(p_ip4_encaps): p4 = (self.p_ether / p_ip4_encap / p_ip4 / self.p_payload) p4_reply = (p_ip4_replys[i] / self.p_payload) p4_reply.ttl -= 1 rx = self.send_and_expect(self.pg1, p4 * N_PACKETS, self.pg0) n_packets_decapped += N_PACKETS for p in rx: self.validate(p[1], p4_reply) self.assert_packet_checksums_valid(p) err = self.statistics.get_err_counter( '/err/ipip4-input/packets decapsulated') self.assertEqual(err, n_packets_decapped) # IPv4 tunnel to IPv6 # for each encap tun will produce the same inner packet because it does # not copy up fields from the payload p_ip4_encaps = [IP(src=tun.dst, dst=self.pg0.local_ip4, tos=tc) for tc in tcs] p_ip6 = IPv6(src="1:2:3::4", dst=self.pg0.remote_ip6) for p_ip4_encap in p_ip4_encaps: p6 = (self.p_ether / p_ip4_encap / p_ip6 / self.p_payload) p6_reply = (p_ip6 / self.p_payload) p6_reply.hlim = 63 rx = self.send_and_expect(self.pg1, p6 * N_PACKETS, self.pg0) n_packets_decapped += N_PACKETS for p in rx: self.validate(p[1], p6_reply) self.assert_packet_checksums_valid(p) err = self.statistics.get_err_counter( '/err/ipip4-input/packets decapsulated') self.assertEqual(err, n_packets_decapped) # IPv4 tunnel to IPv6 # tun_ecn copies the ECN bits from the encap to the inner p_ip4_encaps = [IP(src=tun_ecn.dst, dst=self.pg0.local_ip4, tos=tc) for tc in tcs] p_ip6 = IPv6(src="1:2:3::4", dst=self.pg0.remote_ip6) p_ip6_replys = [p_ip6.copy() for i in range(len(p_ip4_encaps))] p_ip6_replys[2].tc = ecn p_ip6_replys[3].tc = ecn for i, p_ip4_encap in enumerate(p_ip4_encaps): p6 = (self.p_ether / p_ip4_encap / p_ip6 / self.p_payload) p6_reply = (p_ip6_replys[i] / self.p_payload) p6_reply.hlim = 63 rx = self.send_and_expect(self.pg1, p6 * N_PACKETS, self.pg0) n_packets_decapped += N_PACKETS for p in rx: self.validate(p[1], p6_reply) self.assert_packet_checksums_valid(p) err = self.statistics.get_err_counter( '/err/ipip4-input/packets decapsulated') self.assertEqual(err, n_packets_decapped) # # Fragmentation / Reassembly and Re-fragmentation # rv = self.vapi.ip_reassembly_enable_disable( sw_if_index=self.pg1.sw_if_index, enable_ip4=1) self.vapi.ip_reassembly_set(timeout_ms=1000, max_reassemblies=1000, max_reassembly_length=1000, expire_walk_interval_ms=10000, is_ip6=0) # Send lots of fragments, verify reassembled packet frags, p4_reply = self.generate_ip4_frags(3131, 1400) f = [] for i in range(0, 1000): f.extend(frags) self.pg1.add_stream(f) self.pg_enable_capture() self.pg_start() rx = self.pg0.get_capture(1000) n_packets_decapped += 1000 for p in rx: self.validate(p[1], p4_reply)
/*
 * 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.
 */
#include <vnet/cop/cop.h>
#include <vnet/fib/ip4_fib.h>
#include <vnet/dpo/load_balance.h>

typedef struct {
  u32 next_index;
  u32 sw_if_index;
} ip4_cop_whitelist_trace_t;

/* packet trace format function */
static u8 * format_ip4_cop_whitelist_trace (u8 * s, va_list * args)
{
  CLIB_UNUSED (vlib_main_t * vm) = va_arg (*args, vlib_main_t *);
  CLIB_UNUSED (vlib_node_t * node) = va_arg (*args, vlib_node_t *);
  ip4_cop_whitelist_trace_t * t = va_arg (*args, ip4_cop_whitelist_trace_t *);
  
  s = format (s, "IP4_COP_WHITELIST: sw_if_index %d, next index %d",
              t->sw_if_index, t->next_index);
  return s;
}

vlib_node_registration_t ip4_cop_whitelist_node;

#define foreach_ip4_cop_whitelist_error                         \
_(DROPPED, "ip4 cop whitelist packets dropped")

typedef enum {
#define _(sym,str) IP4_COP_WHITELIST_ERROR_##sym,
  foreach_ip4_cop_whitelist_error
#undef _
  IP4_COP_WHITELIST_N_ERROR,
} ip4_cop_whitelist_error_t;

static char * ip4_cop_whitelist_error_strings[] = {
#define _(sym,string) string,
  foreach_ip4_cop_whitelist_error
#undef _
};

static uword
ip4_cop_whitelist_node_fn (vlib_main_t * vm,
		  vlib_node_runtime_t * node,
		  vlib_frame_t * frame)
{
  u32 n_left_from, * from, * to_next;
  cop_feature_type_t next_index;
  cop_main_t *cm = &cop_main;
  vlib_combined_counter_main_t * vcm = &load_balance_main.lbm_via_counters;
  u32 thread_index = vm->thread_index;

  from = vlib_frame_vector_args (frame);
  n_left_from = frame->n_vectors;
  next_index = node->cached_next_index;

  while (n_left_from > 0)
    {
      u32 n_left_to_next;

      vlib_get_next_frame (vm, node, next_index,
			   to_next, n_left_to_next);

      while (n_left_from >= 4 && n_left_to_next >= 2)
      	{
          u32 bi0, bi1;
          vlib_buffer_t * b0, * b1;
          u32 next0, next1;
          u32 sw_if_index0, sw_if_index1;
          ip4_header_t * ip0, * ip1;
          cop_config_main_t * ccm0, * ccm1;
          cop_config_data_t * c0, * c1;
      	  ip4_fib_mtrie_t * mtrie0, * mtrie1;
      	  ip4_fib_mtrie_leaf_t leaf0, leaf1;
          u32 lb_index0, lb_index1;
          const load_balance_t * lb0, *lb1;
          const dpo_id_t *dpo0, *dpo1;

      	  /* Prefetch next iteration. */
      	  {
      	    vlib_buffer_t * p2, * p3;
            
      	    p2 = vlib_get_buffer (vm, from[2]);
      	    p3 = vlib_get_buffer (vm, from[3]);
            
      	    vlib_prefetch_buffer_header (p2, LOAD);
      	    vlib_prefetch_buffer_header (p3, LOAD);

      	    CLIB_PREFETCH (p2->data, CLIB_CACHE_LINE_BYTES, STORE);
      	    CLIB_PREFETCH (p3->data, CLIB_CACHE_LINE_BYTES, STORE);
      	  }

          /* speculatively enqueue b0 and b1 to the current next frame */
      	  to_next[0] = bi0 = from[0];
      	  to_next[1] = bi1 = from[1];
      	  from += 2;
      	  to_next += 2;
      	  n_left_from -= 2;
      	  n_left_to_next -= 2;

      	  b0 = vlib_get_buffer (vm, bi0);
          sw_if_index0 = vnet_buffer(b0)->sw_if_index[VLIB_RX];

      	  ip0 = vlib_buffer_get_current (b0);

      	  ccm0 = cm->cop_config_mains + VNET_COP_IP4;

      	  c0 = vnet_get_config_data
              (&ccm0->config_main,
               &vnet_buffer (b0)->cop.current_config_index,
               &next0,
               sizeof (c0[0]));

	  mtrie0 = &ip4_fib_get (c0->fib_index)->mtrie;

          leaf0 = ip4_fib_mtrie_lookup_step_one (mtrie0, &ip0->src_address);

      	  leaf0 = ip4_fib_mtrie_lookup_step (mtrie0, leaf0,
                                             &ip0->src_address, 2);

      	  leaf0 = ip4_fib_mtrie_lookup_step (mtrie0, leaf0,
                                             &ip0->src_address, 3);

      	  lb_index0 = ip4_fib_mtrie_leaf_get_adj_index (leaf0);

	  ASSERT (lb_index0
                  == ip4_fib_table_lookup_lb (ip4_fib_get(c0->fib_index),
	  				       &ip0->src_address));
	  lb0 = load_balance_get (lb_index0);
          dpo0 = load_balance_get_bucket_i(lb0, 0);

          if (PREDICT_FALSE(dpo0->dpoi_type != DPO_RECEIVE))
            {
              b0->error = node->errors[IP4_COP_WHITELIST_ERROR_DROPPED];
              next0 = RX_COP_DROP;
            }

      	  b1 = vlib_get_buffer (vm, bi1);
          sw_if_index1 = vnet_buffer(b1)->sw_if_index[VLIB_RX];

      	  ip1 = vlib_buffer_get_current (b1);

      	  ccm1 = cm->cop_config_mains + VNET_COP_IP4;

      	  c1 = vnet_get_config_data
              (&ccm1->config_main,
               &vnet_buffer (b1)->cop.current_config_index,
               &next1,
               sizeof (c1[0]));
	  mtrie1 = &ip4_fib_get (c1->fib_index)->mtrie;

          leaf1 = ip4_fib_mtrie_lookup_step_one (mtrie1, &ip1->src_address);

      	  leaf1 = ip4_fib_mtrie_lookup_step (mtrie1, leaf1,
                                             &ip1->src_address, 2);

      	  leaf1 = ip4_fib_mtrie_lookup_step (mtrie1, leaf1,
                                             &ip1->src_address, 3);

      	  lb_index1 = ip4_fib_mtrie_leaf_get_adj_index (leaf1);
	  ASSERT (lb_index1
                  == ip4_fib_table_lookup_lb (ip4_fib_get(c1->fib_index),
	  				       &ip1->src_address));
	  lb1 = load_balance_get (lb_index1);
          dpo1 = load_balance_get_bucket_i(lb1, 0);

          vlib_increment_combined_counter
              (vcm, thread_index, lb_index0, 1,
               vlib_buffer_length_in_chain (vm, b0)
               + sizeof(ethernet_header_t));

          vlib_increment_combined_counter
              (vcm, thread_index, lb_index1, 1,
               vlib_buffer_length_in_chain (vm, b1)
               + sizeof(ethernet_header_t));


          if (PREDICT_FALSE(dpo1->dpoi_type != DPO_RECEIVE))
            {
              b1->error = node->errors[IP4_COP_WHITELIST_ERROR_DROPPED];
              next1 = RX_COP_DROP;