#!/usr/bin/env python import binascii import socket import unittest from framework import VppTestCase, VppTestRunner from scapy.packet import Raw from scapy.layers.l2 import Ether from scapy.layers.inet import IP, UDP, TCP from util import ppp from vpp_ip_route import VppIpRoute, VppRoutePath from vpp_ip import INVALID_INDEX class TestClassifier(VppTestCase): """ Classifier Test Case """ @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(TestClassifier, cls).setUpClass() cls.acl_active_table = '' @classmethod def tearDownClass(cls): super(TestClassifier, cls).tearDownClass() def setUp(self): """ Perform test setup before test case. **Config:** - create 4 pg interfaces - untagged pg0/pg1/pg2 interface pg0 -------> pg1 (IP ACL) \ ---> pg2 (MAC ACL)) \ -> pg3 (PBR) - setup interfaces: - put it into UP state - set IPv4 addresses - resolve neighbor address using ARP :ivar list interfaces: pg interfaces. :ivar list pg_if_packet_sizes: packet sizes in test. :ivar dict acl_tbl_idx: ACL table index. :ivar int pbr_vrfid: VRF id for PBR test. """ self.reset_packet_infos() super(TestClassifier, self).setUp() # create 4 pg interfaces self.create_pg_interfaces(range(4)) # packet sizes to test self.pg_if_packet_sizes = [64, 9018] self.interfaces = list(self.pg_interfaces) # ACL & PBR vars self.acl_tbl_idx = {} self.pbr_vrfid = 200 # setup all interfaces for intf in self.interfaces: intf.admin_up() intf.config_ip4() intf.resolve_arp() def tearDown(self): """Run standard test teardown and acl related log.""" if not self.vpp_dead: if self.acl_active_table == 'ip_out': self.output_acl_set_interface( self.pg0, self.acl_tbl_idx.get(self.acl_active_table), 0) self.acl_active_table = '' elif self.acl_active_table != '': self.input_acl_set_interface( self.pg0, self.acl_tbl_idx.get(self.acl_active_table), 0) self.acl_active_table = '' for intf in self.interfaces: intf.unconfig_ip4() intf.admin_down() super(TestClassifier, self).tearDown() def show_commands_at_teardown(self): self.logger.info(self.vapi.ppcli("show inacl type ip4")) self.logger.info(self.vapi.ppcli("show outacl type ip4")) self.logger.info(self.vapi.cli("show classify table verbose")) self.logger.info(self.vapi.cli("show ip fib")) def create_stream(self, src_if, dst_if, packet_sizes, proto_l=UDP(sport=1234, dport=5678)): """Create input packet stream for defined interfaces. :param VppInterface src_if: Source Interface for packet stream. :param VppInterface dst_if: Destination Interface for packet stream. :param list packet_sizes: packet size to test. :param Scapy proto_l: Required IP protocol. Default protocol is UDP. """ pkts = [] for size in packet_sizes: info = self.create_packet_info(src_if, dst_if) payload = self.info_to_payload(info) p = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) / IP(src=src_if.remote_ip4, dst=dst_if.remote_ip4) / proto_l / Raw(payload)) info.data = p.copy() self.extend_packet(p, size) pkts.append(p) return pkts def verify_capture(self, dst_if, capture, proto_l=UDP): """Verify captured input packet stream for defined interface. :param VppInterface dst_if: Interface to verify captured packet stream. :param list capture: Captured packet stream. :param Scapy proto_l: Required IP protocol. Default protocol is UDP. """ self.logger.info("Verifying capture on interface %s" % dst_if.name) last_info = dict() for i in self.interfaces: last_info[i.sw_if_index] = None dst_sw_if_index = dst_if.sw_if_index for packet in capture: try: ip_received = packet[IP] proto_received = packet[proto_l] payload_info = self.payload_to_info(packet[Raw]) 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)" % (dst_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 ip_saved = saved_packet[IP] proto_saved = saved_packet[proto_l] # Check standard fields self.assertEqual(ip_received.src, ip_saved.src) self.assertEqual(ip_received.dst, ip_saved.dst) self.assertEqual(proto_received.sport, proto_saved.sport) self.assertEqual(proto_received.dport, proto_saved.dport) except: self.logger.error(ppp("Unexpected or invalid packet:", packet)) raise for i in self.interfaces: remaining_packet = self.get_next_packet_info_for_interface2( i.sw_if_index, dst_sw_if_index, last_info[i.sw_if_index]) self.assertTrue(remaining_packet is None, "Interface %s: Packet expected from interface %s " "didn't arrive" % (dst_if.name, i.name)) def verify_vrf(self, vrf_id): """ Check if the FIB table / VRF ID is configured. :param int vrf_id: The FIB table / VRF ID to be verified. :return: 1 if the FIB table / VRF ID is configured, otherwise return 0. """ ip_fib_dump = self.vapi.ip_route_dump(vrf_id, False) vrf_count = len(ip_fib_dump) if vrf_count == 0: self.logger.info("IPv4 VRF ID %d is not configured" % vrf_id) return 0 else: self.logger.info("IPv4 VRF ID %d is configured" % vrf_id) return 1 @staticmethod def build_ip_mask(proto='', src_ip='', dst_ip='', src_port='', dst_port=''): """Build IP ACL mask data with hexstring format. :param str proto: protocol number <0-ff> :param str src_ip: source ip address <0-ffffffff> :param str dst_ip: destination ip address <0-ffffffff> :param str src_port: source port number <0-ffff> :param str dst_port: destination port number <0-ffff> """ return ('{!s:0>20}{!s:0>12}{!s:0>8}{!s:0>4}{!s:0>4}'.format( proto, src_ip, dst_ip, src_port, dst_port)).rstrip('0') @staticmethod def build_ip_match(proto=0, src_ip='', dst_ip='', src_port=0, dst_port=0): """Build IP ACL match data with hexstring format. :param int proto: protocol number with valid option "x" :param str src_ip: source ip address with format of "x.x.x.x" :param str dst_ip: destination ip address with format of "x.x.x.x" :param int src_port: source port number "x" :param int dst_port: destination port number "x" """ if src_ip: src_ip = binascii.hexlify(socket.inet_aton(src_ip)) if dst_ip: dst_ip = binascii.hexlify(socket.inet_aton(dst_ip)) return ('{!s:0>20}{!s:0>12}{!s:0>8}{!s:0>4}{!s:0>4}'.format( hex(proto)[2:], src_ip, dst_ip, hex(src_port)[2:], hex(dst_port)[2:])).rstrip('0') @staticmethod def build_mac_mask(dst_mac='', src_mac='', ether_type=''): """Build MAC ACL mask data with hexstring format. :param str dst_mac: source MAC address <0-ffffffffffff> :param str src_mac: destination MAC address <0-ffffffffffff> :param str ether_type: ethernet type <0-ffff> """ return ('{!s:0>12}{!s:0>12}{!s:0>4}'.format( dst_mac, src_mac, ether_type)).rstrip('0') @staticmethod def build_mac_match(dst_mac='', src_mac='', ether_type=''): """Build MAC ACL match data with hexstring format. :param str dst_mac: source MAC address :param str src_mac: destination MAC address :param str ether_type: ethernet type <0-ffff> """ if dst_mac: dst_mac = dst_mac.replace(':', '') if src_mac: src_mac = src_mac.replace(':', '') return ('{!s:0>12}{!s:0>12}{!s:0>4}'.format( dst_mac, src_mac, ether_type)).rstrip('0') def create_classify_table(self, key, mask, data_offset=0): """Create Classify Table :param str key: key for classify table (ex, ACL name). :param str mask: mask value for interested traffic. :param int data_offset: """ r = self.vapi.classify_add_del_table( is_add=1, mask=binascii.unhexlify(mask), match_n_vectors=(len(mask) - 1) // 32 + 1, miss_next_index=0, current_data_flag=1, current_data_offset=data_offset) self.assertIsNotNone(r, 'No response msg for add_del_table') self.acl_tbl_idx[key] = r.new_table_index def create_classify_session(self, table_index, match, pbr_option=0, vrfid=0, is_add=1): """Create Classify Session :param int table_index: table index to identify classify table. :param str match: matched value for interested traffic. :param int pbr_option: enable/disable PBR feature. :param int vrfid: VRF id. :param int is_add: option to configure classify session. - create(1) or delete(0) """ r = self.vapi.classify_add_del_session( is_add, table_index, binascii.unhexlify(match), opaque_index=0, action=pbr_option, metadata=vrfid) self.assertIsNotNone(r, 'No response msg for add_del_session') def input_acl_set_interface(self, intf, table_index, is_add=1): """Configure Input ACL interface :param VppInterface intf: Interface to apply Input ACL feature. :param int table_index: table index to identify classify table. :param int is_add: option to configure classify session. - enable(1) or disable(0) """ r = self.vapi.input_acl_set_interface(
/*
 * Copyright (c) 2015 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.
 */
/*
 * snap.h: SNAP definitions
 *
 * Copyright (c) 2008 Eliot Dresselhaus
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 *  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 *  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 *  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 *  LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 *  OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 *  WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

#ifndef included_snap_h
#define included_snap_h

#include <vnet/vnet.h>
#include <vnet/pg/pg.h>

#define foreach_ieee_oui			\
  _ (0x000000, ethernet)			\
  _ (0x00000c, cisco)

typedef enum
{
#define _(n,f) IEEE_OUI_##f = n,
  foreach_ieee_oui
#undef _
} ieee_oui_t;

#define foreach_snap_cisco_protocol		\
  _ (0x0102, drip)				\
  _ (0x0104, port_aggregation_protocol)		\
  _ (0x0105, mls_hello)				\
  _ (0x010b, per_vlan_spanning_tree)		\
  _ (0x010c, vlan_bridge)			\
  _ (0x0111, unidirectional_link_detection)	\
  _ (0x2000, cdp)				\
  _ (0x2001, cgmp)				\
  _ (0x2003, vtp)				\
  _ (0x2004, dtp)				\
  _ (0x200a, stp_uplink_fast)

typedef enum
{
#define _(n,f) SNAP_cisco_##f = n,
  foreach_snap_cisco_protocol
#undef _
} snap_cisco_protocol_t;

typedef union
{
  /* *INDENT-OFF* */
  CLIB_PACKED (struct {
    /* OUI: organization unique identifier. */
    u8 oui[3];

    /* Per-OUI protocol. */
    u16 protocol;
  });
  /* *INDENT-ON* */

  u8 as_u8[5];
} snap_header_t;

typedef struct
{
  u32 oui;
  u32 protocol;
} snap_oui_and_protocol_t;

typedef struct
{
  /* Name vector string. */
  u8 *name;

  snap_oui_and_protocol_t oui_and_protocol;

  /* Node which handles this type. */
  u32 node_index;

  /* snap-input next index for this type. */
  u32 next_index;
} snap_protocol_info_t;

always_inline void
snap_header_set_protocol (snap_header_t * h, snap_oui_and_protocol_t * p)
{
  u16 protocol = p->protocol;
  u32 oui = p->oui;
  h->protocol = clib_host_to_net_u16 (protocol);
  h->oui[0] = (oui >> 16) & 0xff;
  h->oui[1] = (oui >> 8) & 0xff;
  h->oui[2] = (oui >> 0) & 0xff;
}

#define foreach_snap_error			\
  _ (NONE, "no error")				\
  _ (UNKNOWN_PROTOCOL, "unknown oui/snap protocol")

typedef enum
{
#define _(f,s) SNAP_ERROR_##f,
  foreach_snap_error
#undef _
    SNAP_N_ERROR,
} snap_error_t;

typedef struct
{
  vlib_main_t *vlib_main;

  /* Vector of known SNAP oui/protocol pairs. */
  snap_protocol_info_t *protocols;

  /* Hash table mapping oui/protocol to protocol index. */
  mhash_t protocol_hash;

  /* Hash table mapping protocol by name. */
  uword *protocol_info_by_name;
} snap_main_t;

always_inline u32
snap_header_get_oui (snap_header_t * h)
{
  return (h->oui[0] << 16) | (h->oui[1] << 8) | h->oui[2];
}

always_inline snap_protocol_info_t *
snap_get_protocol_info (snap_main_t * sm, snap_header_t * h)
{
  snap_oui_and_protocol_t key;
  uword *p;

  key.oui = snap_header_get_oui (h);
  key.protocol = h->protocol;

  p = mhash_get (&sm->protocol_hash, &key);
  return p ? vec_elt_at_index (sm->protocols, p[0]) : 0;
}

extern snap_main_t snap_main;

/* Register given node index to take input for given snap type. */
void
snap_register_input_protocol (vlib_main_t * vm,
			      char *name,
			      u32 ieee_oui, u16 protocol, u32 node_index);

format_function_t format_snap_protocol;
format_function_t format_snap_header;
format_function_t format_snap_header_with_length;

/* Parse snap protocol as 0xXXXX or protocol name. */
unformat_function_t unformat_snap_protocol;

/* Parse snap header. */
unformat_function_t unformat_snap_header;
unformat_function_t unformat_pg_snap_header;

always_inline void
snap_setup_node (vlib_main_t * vm, u32 node_index)
{
  vlib_node_t *n = vlib_get_node (vm, node_index);
  pg_node_t *pn = pg_get_node (node_index);

  n->format_buffer = format_snap_header_with_length;
  n->unformat_buffer = unformat_snap_header;
  pn->unformat_edit = unformat_pg_snap_header;
}

#endif /* included_snap_h */

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