#!/usr/bin/env python """ACL plugin Test Case HLD: """ import unittest import random from scapy.packet import Raw from scapy.layers.l2 import Ether from scapy.layers.inet import IP, TCP, UDP, ICMP from scapy.layers.inet6 import IPv6, ICMPv6EchoRequest from scapy.layers.inet6 import IPv6ExtHdrFragment from framework import VppTestCase, VppTestRunner from util import Host, ppp class TestACLplugin(VppTestCase): """ ACL plugin Test Case """ # traffic types IP = 0 ICMP = 1 # IP version IPRANDOM = -1 IPV4 = 0 IPV6 = 1 # rule types DENY = 0 PERMIT = 1 # supported protocols proto = [[6, 17], [1, 58]] proto_map = {1: 'ICMP', 58: 'ICMPv6EchoRequest', 6: 'TCP', 17: 'UDP'} ICMPv4 = 0 ICMPv6 = 1 TCP = 0 UDP = 1 PROTO_ALL = 0 # port ranges PORTS_ALL = -1 PORTS_RANGE = 0 udp_sport_from = 10 udp_sport_to = udp_sport_from + 5 udp_dport_from = 20000 udp_dport_to = udp_dport_from + 5000 tcp_sport_from = 30 tcp_sport_to = tcp_sport_from + 5 tcp_dport_from = 40000 tcp_dport_to = tcp_dport_from + 5000 icmp4_type = 8 # echo request icmp4_code = 3 icmp6_type = 128 # echo request icmp6_code = 3 # Test variables bd_id = 1 @classmethod def setUpClass(cls): """ Perform standard class setup (defined by class method setUpClass in class VppTestCase) before running the test case, set test case related variables and configure VPP. """ super(TestACLplugin, cls).setUpClass() random.seed() try: # Create 2 pg interfaces cls.create_pg_interfaces(range(2)) # Packet flows mapping pg0 -> pg1, pg2 etc. cls.flows = dict() cls.flows[cls.pg0] = [cls.pg1] # Packet sizes cls.pg_if_packet_sizes = [64, 512, 1518, 9018] # Create BD with MAC learning and unknown unicast flooding disabled # and put interfaces to this BD cls.vapi.bridge_domain_add_del(bd_id=cls.bd_id, uu_flood=1, learn=1) for pg_if in cls.pg_interfaces: cls.vapi.sw_interface_set_l2_bridge(pg_if.sw_if_index, bd_id=cls.bd_id) # Set up all interfaces for i in cls.pg_interfaces: i.admin_up() # Mapping between packet-generator index and lists of test hosts cls.hosts_by_pg_idx = dict() for pg_if in cls.pg_interfaces: cls.hosts_by_pg_idx[pg_if.sw_if_index] = [] # Create list of deleted hosts cls.deleted_hosts_by_pg_idx = dict() for pg_if in cls.pg_interfaces: cls.deleted_hosts_by_pg_idx[pg_if.sw_if_index] = [] # warm-up the mac address tables # self.warmup_test() except Exception: super(TestACLplugin, cls).tearDownClass() raise def setUp(self): super(TestACLplugin, self).setUp() self.reset_packet_infos() def tearDown(self): """ Show various debug prints after each test. """ super(TestACLplugin, self).tearDown() if not self.vpp_dead: self.logger.info(self.vapi.ppcli("show l2fib verbose")) self.logger.info(self.vapi.ppcli("show bridge-domain %s detail" % self.bd_id)) def create_hosts(self, count, start=0): """ Create required number of host MAC addresses and distribute them among interfaces. Create host IPv4 address for every host MAC address. :param int count: Number of hosts to create MAC/IPv4 addresses for. :param int start: Number to start numbering from. """ n_int = len(self.pg_interfaces) macs_per_if = count / n_int i = -1 for pg_if in self.pg_interfaces: i += 1 start_nr = macs_per_if * i + start end_nr = count + start if i == (n_int - 1) \ else macs_per_if * (i + 1) + start hosts = self.hosts_by_pg_idx[pg_if.sw_if_index] for j in range(start_nr, end_nr): host = Host( "00:00:00:ff:%02x:%02x" % (pg_if.sw_if_index, j), "172.17.1%02x.%u" % (pg_if.sw_if_index, j), "2017:dead:%02x::%u" % (pg_if.sw_if_index, j)) hosts.append(host) def create_rule(self, ip=0, permit_deny=0, ports=PORTS_ALL, proto=-1, s_prefix=0, s_ip='\x00\x00\x00\x00', d_prefix=0, d_ip='\x00\x00\x00\x00'): if proto == -1: return if ports == self.PORTS_ALL: sport_from = 0 dport_from = 0 sport_to = 65535 if proto != 1 and proto != 58 else 255 dport_to = sport_to elif ports == self.PORTS_RANGE: if proto == 1: sport_from = self.icmp4_type sport_to = self.icmp4_type dport_from = self.icmp4_code dport_to = self.icmp4_code elif proto == 58: sport_from = self.icmp6_type sport_to = self.icmp6_type dport_from = self.icmp6_code dport_to = self.icmp6_code elif proto == self.proto[self.IP][self.TCP]: sport_from = self.tcp_sport_from sport_to = self.tcp_sport_to dport_from = self.tcp_dport_from dport_to = self.tcp_dport_to elif proto == self.proto[self.IP][self.UDP]: sport_from = self.udp_sport_from sport_to = self.udp_sport_to dport_from = self.udp_dport_from dport_to = self.udp_dport_to else: sport_from = ports sport_to = ports dport_from = ports dport_to = ports rule = ({'is_permit': permit_deny, 'is_ipv6': ip, 'proto': proto, 'srcport_or_icmptype_first': sport_from, 'srcport_or_icmptype_last': sport_to, 'src_ip_prefix_len': s_prefix, 'src_ip_addr': s_ip, 'dstport_or_icmpcode_first': dport_from, 'dstport_or_icmpcode_last': dport_to, 'dst_ip_prefix_len': d_prefix, 'dst_ip_addr': d_ip}) return rule def apply_rules(self, rules, tag=''): reply = self.api_acl_add_replace(acl_index=4294967295, r=rules, count=len(rules), tag=tag) self.logger.info("Dumped ACL: " + str( self.api_acl_dump(reply.acl_index))) # Apply a ACL on the interface as inbound for i in self.pg_interfaces: self.api_acl_interface_set_acl_list(sw_if_index=i.sw_if_index, count=1, n_input=1, acls=[reply.acl_index]) return def create_upper_layer(self, packet_index, proto, ports=0): p = self.proto_map[proto] if p == 'UDP': if ports == 0: return UDP(sport=random.randint(self.udp_sport_from, self.udp_sport_to), dport=random.randint(self.udp_dport_from, self.udp_dport_to)) else: return UDP(sport=ports, dport=ports) elif p == 'TCP': if ports == 0: return TCP(sport=random.randint(self.tcp_sport_from, self.tcp_sport_to), dport=random.randint(self.tcp_dport_from, self.tcp_dport_to)) else: return TCP(sport=ports, dport=ports) return '' def create_stream(self, src_if, packet_sizes, traffic_type=0, ipv6=0, proto=-1, ports=0, fragments=False, pkt_raw=True): """ Create input packet stream for defined interface using hosts or deleted_hosts list. :param object src_if: Interface to create packet stream for. :param list packet_sizes: List of required packet sizes. :param traffic_type: 1: ICMP packet, 2: IPv6 with EH, 0: otherwise. :return: Stream of packets. """ pkts = [] if self.flows.__contains__(src_if): src_hosts = self.hosts_by_pg_idx[src_if.sw_if_index] for dst_if in self.flows[src_if]: dst_hosts = self.hosts_by_pg_idx[dst_if.sw_if_index] n_int = len(dst_hosts) * len(src_hosts) for i in range(0, n_int): dst_host = dst_hosts[i / len(src_hosts)] src_host = src_hosts[i % len(src_hosts)] pkt_info = self.create_packet_info(src_if, dst_if) if ipv6 == 1: pkt_info.ip = 1 elif ipv6 == 0: pkt_info.ip = 0 else: pkt_info.ip = random.choice([0, 1]) if proto == -1: pkt_info.proto = random.choice(self.proto[self.IP]) else: pkt_info.proto = proto payload = self.info_to_payload(pkt_info) p = Ether(dst=dst_host.mac, src=src_host.mac) if pkt_info.ip: p /= IPv6(dst=dst_host.ip6, src=src_host.ip6) if fragments: p /= IPv6ExtHdrFragment(offset=64, m=1) else: if fragments: p /= IP(src=src_host.ip4, dst=dst_host.ip4, flags=1, frag=64) else: p /= IP(src=src_host.ip4, dst=dst_host.ip4) if traffic_type == self.ICMP: if pkt_info.ip: p /= ICMPv6EchoRequest(type=self.icmp6_type, code=self.icmp6_code) else: p /= ICMP(type=self.icmp4_type, code=self.icmp4_code) else: p /= self.create_upper_layer(i, pkt_info.proto, ports) if pkt_raw: p /= Raw(payload) pkt_info.data = p.copy() if pkt_raw: size = random.choice(packet_sizes) self.extend_packet(p, size) pkts.append(p) return pkts def verify_capture(self, pg_if, capture, traffic_type=0, ip_type=0): """ Verify captured input packet stream for defined interface. :param object pg_if: Interface to verify captured packet stream for. :param list capture: Captured packet stream. :param traffic_type: 1: ICMP packet, 2: IPv6 with EH, 0: otherwise. """ last_info = dict() for i in self.pg_interfaces: last_info[i.sw_if_index] = None dst_sw_if_index = pg_if.sw_if_index for packet in capture: try: # Raw data for ICMPv6 are stored in ICMPv6EchoRequest.data if traffic_type == self.ICMP and ip_type == self.IPV6: payload_info = self.payload_to_info( packet[ICMPv6EchoRequest].data) payload = packet[ICMPv6EchoRequest] else: payload_info = self.payload_to_info(str(packet[Raw])) payload = packet[self.proto_map[payload_info.proto]] except: self.logger.error(ppp("Unexpected or invalid packet " "(outside network):", packet)) raise if ip_type != 0: self.assertEqual(payload_info.ip, ip_type) if traffic_type == self.ICMP: try: if payload_info.ip == 0: self.assertEqual(payload.type, self.icmp4_type) self.assertEqual(payload.code, self.icmp4_code) else: self.assertEqual(payload.type, self.icmp6_type) self.assertEqual(payload.code, self.icmp6_code) except: self.logger.error(ppp("Unexpected or invalid packet " "(outside network):", packet)) raise else: try: ip_version = IPv6 if payload_info.ip == 1 else IP ip = packet[ip_version] packet_index = payload_info.index self.assertEqual(payload_info.dst, dst_sw_if_index) self.logger.debug("Got packet on port %s: src=%u (id=%u)" % (pg_if.name, payload_info.src, packet_index)) next_info = self.get_next_packet_info_for_interface2( payload_info.src, dst_sw_if_index, last_info[payload_info.src]) last_info[payload_info.src] = next_info self.assertTrue(next_info is not None) self.assertEqual(packet_index, next_info.index) saved_packet = next_info.data # Check standard fields self.assertEqual(ip.src, saved_packet[ip_version].src) self.assertEqual(ip.dst, saved_packet[ip_version].dst) p = self.proto_map[payload_info.proto] if p == 'TCP': tcp = packet[TCP] self.assertEqual(tcp.sport, saved_packet[ TCP].sport) self.assertEqual(tcp.dport, saved_packet[ TCP].dport) elif p == 'UDP': udp = packet[UDP] self.assertEqual(udp.sport, saved_packet[ UDP].sport) self.assertEqual(udp.dport, saved_packet[ UDP].dport) except: self.logger.error(ppp("Unexpected or invalid
/*
 * Copyright (c) 2016,2020 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 <plugins/adl/adl.h>
#include <vnet/fib/ip6_fib.h>
#include <vnet/dpo/load_balance.h>

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

/* packet trace format function */
static u8 * format_ip6_adl_allowlist_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 *);
  ip6_adl_allowlist_trace_t * t = va_arg (*args, ip6_adl_allowlist_trace_t *);

  s = format (s, "IP6_ADL_ALLOWLIST: sw_if_index %d, next index %d",
              t->sw_if_index, t->next_index);
  return s;
}

#define foreach_ip6_adl_allowlist_error         \
_(ALLOWED, "ip6 allowlist allowed")             \
_(DROPPED, "ip6 allowlist dropped")

typedef enum {
#define _(sym,str) IP6_ADL_ALLOWLIST_ERROR_##sym,
  foreach_ip6_adl_allowlist_error
#undef _
  IP6_ADL_ALLOWLIST_N_ERROR,
} ip6_adl_allowlist_error_t;

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

VLIB_NODE_FN (ip6_adl_allowlist_node) (vlib_main_t * vm,
		  vlib_node_runtime_t * node,
		  vlib_frame_t * frame)
{
  u32 n_left_from, * from, * to_next;
  adl_feature_type_t next_index;
  adl_main_t *cm = &adl_main;
  vlib_combined_counter_main_t * vcm = &load_balance_main.lbm_via_counters;
  u32 thread_index = vm->thread_index;
  u32 allowed_packets;

  from = vlib_frame_vector_args (frame);
  n_left_from = frame->n_vectors;
  allowed_packets = n_left_from;
  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;
          ip6_header_t * ip0, * ip1;
          adl_config_main_t * ccm0, * ccm1;
          adl_config_data_t * c0, * c1;
          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->adl_config_mains + VNET_ADL_IP6;

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

          lb_index0 = ip6_fib_table_fwding_lookup (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[IP6_ADL_ALLOWLIST_ERROR_DROPPED];
              allowed_packets--;
              next0 = RX_ADL_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->adl_config_mains + VNET_ADL_IP6;

	  c1 = vnet_get_config_data
              (&ccm1->config_main,
               &adl_buffer (b1)->adl.current_config_index,
               &next1,
               sizeof (c1[0]));

          lb_index1 = ip6_fib_table_fwding_lookup (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[IP6_ADL_ALLOWLIST_ERROR_DROPPED];
              allowed_packets--;
              next1 = RX_ADL_DROP;