#!/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 from vpp_lo_interface import VppLoInterface 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 PORTS_RANGE_2 = 1 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 udp_sport_from_2 = 90 udp_sport_to_2 = udp_sport_from_2 + 5 udp_dport_from_2 = 30000 udp_dport_to_2 = udp_dport_from_2 + 5000 tcp_sport_from_2 = 130 tcp_sport_to_2 = tcp_sport_from_2 + 5 tcp_dport_from_2 = 20000 tcp_dport_to_2 = tcp_dport_from_2 + 5000 icmp4_type = 8 # echo request icmp4_code = 3 icmp6_type = 128 # echo request icmp6_code = 3 icmp4_type_2 = 8 icmp4_code_from_2 = 5 icmp4_code_to_2 = 20 icmp6_type_2 = 128 icmp6_code_from_2 = 8 icmp6_code_to_2 = 42 # 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() 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( rx_sw_if_index=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() count = 16 start = 0 n_int = len(cls.pg_interfaces) macs_per_if = count / n_int i = -1 for pg_if in cls.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 = cls.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) except Exception: super(TestACLplugin, cls).tearDownClass() raise @classmethod def tearDownClass(cls): super(TestACLplugin, cls).tearDownClass() 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: cli = "show vlib graph l2-input-feat-arc" self.logger.info(self.vapi.ppcli(cli)) cli = "show vlib graph l2-input-feat-arc-end" self.logger.info(self.vapi.ppcli(cli)) cli = "show vlib graph l2-output-feat-arc" self.logger.info(self.vapi.ppcli(cli)) cli = "show vlib graph l2-output-feat-arc-end" self.logger.info(self.vapi.ppcli(cli)) self.logger.info(self.vapi.ppcli("show l2fib verbose")) self.logger.info(self.vapi.ppcli("show acl-plugin acl")) self.logger.info(self.vapi.ppcli("show acl-plugin interface")) self.logger.info(self.vapi.ppcli("show acl-plugin tables")) self.logger.info(self.vapi.ppcli("show bridge-domain %s detail" % self.bd_id)) 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 elif ports == self.PORTS_RANGE_2: if proto == 1: sport_from = self.icmp4_type_2 sport_to = self.icmp4_type_2 dport_from = self.icmp4_code_from_2 dport_to = self.icmp4_code_to_2 elif proto == 58: sport_from = self.icmp6_type_2 sport_to = self.icmp6_type_2 dport_from = self.icmp6_code_from_2 dport_to = self.icmp6_code_to_2 elif proto == self.proto[self.IP][self.TCP]: sport_from = self.tcp_sport_from_2 sport_to = self.tcp_sport_to_2 dport_from = self.tcp_dport_from_2 dport_to = self.tcp_dport_to_2 elif proto == self.proto[self.IP][self.UDP]: sport_from = self.udp_sport_from_2 sport_to = self.udp_sport_to_2 dport_from = self.udp_dport_from_2 dport_to = self.udp_dport_to_2 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=b''): reply = self.vapi.acl_add_replace(acl_index=4294967295, r=rules, tag=tag) self.logger.info("Dumped ACL: " + str( self.vapi.acl_dump(reply.acl_index))) # Apply a ACL on the interface as inbound for i in self.pg_interfaces: self.vapi.acl_interface_set_acl_list(sw_if_index=i.sw_if_index, n_input=1, acls=[reply.acl_index]) return def apply_rules_to(self, rules, tag=b'', sw_if_index=0xFFFFFFFF): reply = self.vapi.acl_add_replace(acl_index=4294967295, r=rules, tag=tag) self.logger.info("Dumped ACL: " + str( self.vapi.acl_dump(reply.acl_index))) # Apply a ACL on the interface as inbound self.vapi.acl_interface_set_acl_list(sw_if_index=sw_if_index, n_input=1, acls=[reply.acl_index]) return def etype_whitelist(self, whitelist, n_input): # Apply whitelists on all the interfaces for i in self.pg_interfaces: # checkstyle can't read long names. Help them. fun = self.vapi.acl_interface_set_etype_whitelist fun(sw_if_index=i.sw_if_index, n_input=n_input, whitelist=whitelist) 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, etype=-1): """ 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 etype > 0: p = Ether(dst=dst_host.mac, src=src_host.mac, type=etype) 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, etype=-1): """ 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: if etype > 0: if packet[Ether].type
/*
 * Copyright (c) 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 <vnet/ip/ip.h>

/**
 * @file
 * @brief IP prefix management on interfaces
 */

u32
ip_interface_address_find (ip_lookup_main_t * lm,
			   void *addr_fib, u32 address_length)
{
  uword *p = mhash_get (&lm->address_to_if_address_index, addr_fib);

  if (p)
    return (p[0]);

  return (~0);
}

clib_error_t *
ip_interface_address_add (ip_lookup_main_t * lm,
			  u32 sw_if_index,
			  void *addr_fib,
			  u32 address_length, u32 * result_if_address_index)
{
  vnet_main_t *vnm = vnet_get_main ();
  ip_interface_address_t *a, *prev;
  u32 pi;			/* previous index */
  u32 ai;
  u32 hi;			/* head index */

  /* Verify given length. */
  if ((address_length == 0) ||
      (lm->is_ip6 && address_length > 128) ||
      (!lm->is_ip6 && address_length > 32))
    {
      vnm->api_errno = VNET_API_ERROR_ADDRESS_LENGTH_MISMATCH;
      return clib_error_create
	("%U wrong length for interface %U",
	 lm->format_address_and_length, addr_fib,
	 address_length, format_vnet_sw_if_index_name, vnm, sw_if_index);
    }

  vec_validate_init_empty (lm->if_address_pool_index_by_sw_if_index,
			   sw_if_index, ~0);

  pool_get_zero (lm->if_address_pool, a);

  ai = a - lm->if_address_pool;
  hi = pi = lm->if_address_pool_index_by_sw_if_index[sw_if_index];

  prev = 0;
  while (pi != (u32) ~ 0)
    {
      prev = pool_elt_at_index (lm->if_address_pool, pi);
      pi = prev->next_this_sw_interface;
    }
  pi = prev ? prev - lm->if_address_pool : (u32) ~ 0;

  a->address_key = mhash_set (&lm->address_to_if_address_index,
			      addr_fib, ai, /* old_value */ 0);
  a->address_length = address_length;
  a->sw_if_index = sw_if_index;
  a->flags = 0;
  a->prev_this_sw_interface = pi;
  a->next_this_sw_interface = ~0;
  if (prev)
    prev->next_this_sw_interface = ai;

  lm->if_address_pool_index_by_sw_if_index[sw_if_index] =
    (hi != ~0) ? hi : ai;

  *result_if_address_index = ai;

  return (NULL);
}

clib_error_t *
ip_interface_address_del (ip_lookup_main_t * lm,
			  vnet_main_t * vnm,
			  u32 address_index, void *addr_fib,
			  u32 address_length, u32 sw_if_index)
{
  ip_interface_address_t *a, *prev, *next;

  a = pool_elt_at_index (lm->if_address_pool, address_index);

  if (a->sw_if_index != sw_if_index)
    {
      vnm->api_errno = VNET_API_ERROR_ADDRESS_NOT_FOUND_FOR_INTERFACE;
      return clib_error_create ("%U not found for interface %U",
				lm->format_address_and_length,
				addr_fib, address_length,
				format_vnet_sw_if_index_name,
				vnet_get_main (), sw_if_index);
    }

  if (a->prev_this_sw_interface != ~0)
    {
      prev = pool_elt_at_index (lm->if_address_pool,
				a->prev_this_sw_interface);
      prev->next_this_sw_interface = a->next_this_sw_interface;
    }
  if (a->next_this_sw_interface != ~0)
    {
      next = pool_elt_at_index (lm->if_address_pool,
				a->next_this_sw_interface);
      next->prev_this_sw_interface = a->prev_this_sw_interface;

      if (a->prev_this_sw_interface == ~0)
	lm->if_address_pool_index_by_sw_if_index[a->sw_if_index] =
	  a->next_this_sw_interface;
    }

  if ((a->next_this_sw_interface == ~0) && (a->prev_this_sw_interface == ~0))
    lm->if_address_pool_index_by_sw_if_index[a->sw_if_index] = ~0;

  mhash_unset (&lm->address_to_if_address_index, addr_fib,
	       /* old_value */ 0);
  pool_put (lm->if_address_pool, a);
  return NULL;
}

u8
ip_interface_has_address (u32 sw_if_index, ip46_address_t * ip, u8 is_ip4)
{
  ip_interface_address_t *ia = 0;

  if (is_ip4)
    {
      ip_lookup_main_t *lm4 = &ip4_main.lookup_main;
      ip4_address_t *ip4;
      /* *INDENT-OFF* */
      foreach_ip_interface_address (lm4, ia, sw_if_index, 1 /* unnumbered */ ,
      ({
        ip4 = ip_interface_address_get_address (lm4, ia);
        if (ip4_address_compare (ip4, &ip->ip4) == 0)
          return 1;
      }));
      /* *INDENT-ON* */
    }
  else
    {
      ip_lookup_main_t *lm6 = &ip6_main.lookup_main;
      ip6_address_t *ip6;
      /* *INDENT-OFF* */
      foreach_ip_interface_address (lm6, ia, sw_if_index, 1 /* unnumbered */ ,
      ({
        ip6 = ip_interface_address_get_address (lm6, ia);
        if (ip6_address_compare (ip6, &ip->ip6) == 0)
          return 1;
      }));
      /* *INDENT-ON* */
    }
  return 0;
}

void *
ip_interface_get_first_ip (u32 sw_if_index, u8 is_ip4)
{
  ip_lookup_main_t *lm4 = &ip4_main.lookup_main;
  ip_lookup_main_t *lm6 = &ip6_main.lookup_main;
  ip_interface_address_t *ia = 0;

  if (is_ip4)
    {
      /* *INDENT-OFF* */
      foreach_ip_interface_address (lm4, ia, sw_if_index, 1 /* unnumbered */ ,
      ({
        return ip_interface_address_get_address (lm4, ia);
      }));
      /* *INDENT-ON* */
    }
  else
    {
      /* *INDENT-OFF* */
      foreach_ip_interface_address (lm6, ia, sw_if_index, 1 /* unnumbered */ ,
      ({
        ip6_address_t *rv;
        rv = ip_interface_address_get_address (lm6, ia);
        /* Trying to use a link-local ip6 src address is a fool's errand */
        if (!ip6_address_is_link_local_unicast (rv))
          return rv;
      }));
      /* *INDENT-ON* */
    }

  return 0;
}

static walk_rc_t
ip_interface_address_mark_one_interface (vnet_main_t * vnm,
					 vnet_sw_interface_t * si, void *ctx)
{
  ip_lookup_main_t *lm4 = &ip4_main.lookup_main;
  ip_lookup_main_t *lm6 = &ip6_main.lookup_main;
  ip_interface_address_t *ia = 0;

  /* *INDENT-OFF* */
  foreach_ip_interface_address (lm4, ia, si->sw_if_index, 1 /* unnumbered */ ,
  ({
    ia->flags |= IP_INTERFACE_ADDRESS_FLAG_STALE;
  }));
  foreach_ip_interface_address (lm6, ia, si->sw_if_index, 1 /* unnumbered */ ,
  ({
    ia->flags |= IP_INTERFACE_ADDRESS_FLAG_STALE;
  }));
  /* *INDENT-ON* */

  return (WALK_CONTINUE);
}

void
ip_interface_address_mark (void)
{
  vnet_sw_interface_walk (vnet_get_main (),
			  ip_interface_address_mark_one_interface, NULL);
}

static walk_rc_t
ip_interface_address_sweep_one_interface (vnet_main_t * vnm,
					  vnet_sw_interface_t * si, void *ctx)
{
  vlib_main_t *vm = vlib_get_main ();
  ip4_address_t *ip4_addrs = 0;
  ip6_address_t *ip6_addrs = 0;
  ip4_main_t *im4 = &ip4_main;
  ip6_main_t *im6 = &ip6_main;
  ip_interface_address_t *ia;
  u32 *ip6_masks = 0;
  u32 *ip4_masks = 0;
  int i;

  /* *INDENT-OFF* */
  foreach_ip_interface_address (&im4->lookup_main, ia, si->sw_if_index, 1,
  ({
    if (ia->flags & IP_INTERFACE_ADDRESS_FLAG_STALE)
      {
        ip4_address_t * x = (ip4_address_t *)
          ip_interface_address_get_address (&im4->lookup_main, ia);
        vec_add1 (ip4_addrs, x[0]);
        vec_add1 (ip4_masks, ia->address_length);
      }
  }));

  foreach_ip_interface_address (&im6->lookup_main, ia, si->sw_if_index, 1,
  ({
    if (ia->flags & IP_INTERFACE_ADDRESS_FLAG_STALE)
      {
        ip6_address_t * x = (ip6_address_t *)
          ip_interface_address_get_address (&im6->lookup_main, ia);
        vec_add1 (ip6_addrs, x[0]);
        vec_add1 (ip6_masks, ia->address_length);
      }
  }));
  /* *INDENT-ON* */

  for (i = 0; i < vec_len (ip4_addrs); i++)
    ip4_add_del_interface_address (vm, si->sw_if_index, &ip4_addrs[i],
				   ip4_masks[i], 1 /* is_del */ );
  for (i = 0; i < vec_len (ip6_addrs); i++)
    ip6_add_del_interface_address (vm, si->sw_if_index, &ip6_addrs[i],
				   ip6_masks[i], 1 /* is_del */ );

  vec_free (ip4_addrs);
  vec_free (ip4_masks);
  vec_free (ip6_addrs);
  vec_free (ip6_masks);

  return (WALK_CONTINUE);
}

void
ip_interface_address_sweep (void)
{
  vnet_sw_interface_walk (vnet_get_main (),
			  ip_interface_address_sweep_one_interface, NULL);
}

/*
 * fd.io coding-style-patch-verification: ON
 *
 * Local Variables:
 * eval: (c-set-style "gnu")
 * End: