F: src/plugins/cnat
+Plugin - NPol
+I: npol
+F: src/plugins/npol/
+
Plugin - Wireguard
I: wireguard
quic
cnat
+ npol
dev_armada
lcp
srv6/index
--- /dev/null
+../../../src/plugins/npol/npol.rst
\ No newline at end of file
ipsec
IPsec
ipsecmb
+ipset
+ipsets
iptables
ipv
iPv
noevaluate
nonaddress
nosyslog
+npol
npt
npt66
ns
--- /dev/null
+# SPDX-License-Identifier: Apache-2.0
+# Copyright(c) 2025 Cisco Systems, Inc.
+
+add_vpp_plugin(npol
+ SOURCES
+ npol.c
+ npol_api.c
+ npol_policy.c
+ npol_rule.c
+ npol_ipset.c
+ npol_interface.c
+ npol_format.c
+ npol_match.c
+
+ MULTIARCH_SOURCES
+ npol_match.c
+
+ API_FILES
+ npol.api
+)
--- /dev/null
+---
+name: Network Policy
+features:
+ - Interface-level policy configuration for RX and TX traffic
+ - Rule and IP set-based packet filtering for IPv4 and IPv6
+
+description: "This plugin provides a programmable network policy engine in VPP.
+ It allows creation of policies composed of rules and IP sets,
+ which can be applied to interfaces for controlling packet forwarding,
+ filtering by IP addresses, ports, and protocols. It supports both
+ inbound and outbound traffic with default behaviors and can integrate
+ into VPP's packet processing using ACL..."
+
+state: development
+properties: [CLI, API]
\ No newline at end of file
--- /dev/null
+/*
+ * 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.
+ */
+
+/** \file
+ This file defines the vpp control-plane API messages
+ used to configure Network policies
+*/
+
+option version = "0.1.0";
+import "vnet/ip/ip_types.api";
+import "vnet/fib/fib_types.api";
+
+/** \brief Get the plugin version
+ @param client_index - opaque cookie to identify the sender
+ @param context - sender context, to match reply w/ request
+*/
+
+define npol_get_version
+{
+ u32 client_index;
+ u32 context;
+};
+
+/** \brief Reply to get the plugin version
+ @param context - returned sender context, to match reply w/ request
+ @param major - Incremented every time a known breaking behavior change is introduced
+ @param minor - Incremented with small changes, may be used to avoid buggy versions
+*/
+
+define npol_get_version_reply
+{
+ u32 context;
+ u32 major;
+ u32 minor;
+};
+
+enum npol_ipset_type : u8 {
+ NPOL_IP = 0, /* Each member is an IP address */
+ NPOL_IP_AND_PORT = 1, /* Each member is "<IP>,(tcp|udp):port" (3-tuple) */
+ NPOL_NET = 2, /* Each member is a CIDR */
+};
+
+typedef npol_three_tuple {
+ vl_api_address_t address;
+ u8 l4_proto;
+ u16 port;
+};
+
+union npol_ipset_member_val {
+ vl_api_address_t address;
+ vl_api_prefix_t prefix;
+ vl_api_npol_three_tuple_t tuple;
+};
+
+typedef npol_ipset_member {
+ vl_api_npol_ipset_member_val_t val;
+};
+
+define npol_ipset_create
+{
+ u32 client_index;
+ u32 context;
+ vl_api_npol_ipset_type_t type;
+};
+
+define npol_ipset_create_reply
+{
+ u32 context;
+ i32 retval;
+ u32 set_id;
+};
+
+
+autoreply define npol_ipset_add_del_members
+{
+ u32 client_index;
+ u32 context;
+ u32 set_id;
+ bool is_add;
+ u32 len;
+ vl_api_npol_ipset_member_t members[len];
+};
+
+autoreply define npol_ipset_delete
+{
+ u32 client_index;
+ u32 context;
+ u32 set_id;
+};
+
+enum npol_rule_action : u8 {
+ NPOL_ALLOW = 0, // Accept packet
+ NPOL_DENY, // Drop / reject packet
+ NPOL_LOG, // Ignored for now
+ NPOL_PASS, // Skip following rules, resume evaluation at the policy
+ // with the id configured in npol_configure_policies
+};
+
+enum npol_entry_type : u8 {
+ NPOL_CIDR = 0, // simple prefix
+ NPOL_PORT_RANGE,
+ NPOL_PORT_IP_SET, // Points to an ip + proto + port set
+ NPOL_IP_SET, // Points to an ip only set
+};
+
+enum npol_policy_default : u8 {
+ NPOL_DEFAULT_ALLOW = 0, // allow per default
+ NPOL_DEFAULT_DENY, // deny per default
+ NPOL_DEFAULT_PASS, // pass to profiles per default
+};
+
+typedef npol_port_range {
+ u16 start;
+ u16 end; // Inclusive, for a single port start==end
+};
+
+typedef npol_entry_set_id {
+ u32 set_id;
+};
+
+union npol_entry_data {
+ vl_api_prefix_t cidr;
+ vl_api_npol_port_range_t port_range;
+ vl_api_npol_entry_set_id_t set_id;
+};
+
+// A rule contains several such entries, each belong to a category
+// categories are: [not_]{src,dst}_{cidr,port_range,port_ip_set,ip_set}
+// (defined byt the 3 first fields in the rule_entry)
+// A rule matches a packet iff:
+// - for every "not" category, the source / destination do not match any entry
+// - for every positive match category, the source / destination matches at
+// least one entry in each category EXCEPT for port ranges and port+ip sets,
+// where the packet only needs to match one entry in either category
+
+typedef npol_rule_entry {
+ bool is_src;
+ bool is_not;
+ vl_api_npol_entry_type_t type;
+ vl_api_npol_entry_data_t data;
+};
+
+enum npol_rule_filter_type : u8 {
+ NPOL_RULE_FILTER_NONE_TYPE = 0,
+ NPOL_RULE_FILTER_ICMP_TYPE,
+ NPOL_RULE_FILTER_ICMP_CODE,
+ NPOL_RULE_FILTER_L4_PROTO,
+};
+
+typedef npol_rule_filter {
+ u32 value;
+ vl_api_npol_rule_filter_type_t type;
+ u8 should_match;
+};
+
+typedef npol_rule {
+ vl_api_npol_rule_action_t action;
+ vl_api_npol_rule_filter_t filters[3];
+ u32 num_entries;
+ vl_api_npol_rule_entry_t matches[num_entries]; // List of other criteria
+};
+
+define npol_rule_create {
+ u32 client_index;
+ u32 context;
+ vl_api_npol_rule_t rule;
+};
+
+autoreply define npol_rule_update {
+ u32 client_index;
+ u32 context;
+ u32 rule_id;
+ vl_api_npol_rule_t rule;
+};
+
+define npol_rule_create_reply {
+ u32 context;
+ i32 retval;
+ u32 rule_id;
+};
+
+autoreply define npol_rule_delete {
+ u32 client_index;
+ u32 context;
+ u32 rule_id;
+};
+
+typedef npol_policy_item {
+ bool is_inbound; // 0 for outbound, 1 for is_inbound
+ u32 rule_id;
+};
+
+define npol_policy_create {
+ u32 client_index;
+ u32 context;
+ u32 num_items;
+ vl_api_npol_policy_item_t rules[num_items];
+};
+
+define npol_policy_create_reply {
+ u32 context;
+ i32 retval;
+ u32 policy_id;
+};
+
+autoreply define npol_policy_update {
+ u32 client_index;
+ u32 context;
+ u32 policy_id;
+ u32 num_items;
+ vl_api_npol_policy_item_t rules[num_items];
+};
+
+autoreply define npol_policy_delete {
+ u32 client_index;
+ u32 context;
+ u32 policy_id;
+};
+
+autoreply define npol_configure_policies {
+ u32 client_index;
+ u32 context;
+ u32 sw_if_index;
+ u32 num_rx_policies;
+ u32 num_tx_policies;
+ u32 total_ids;
+ u8 invert_rx_tx;
+ vl_api_npol_policy_default_t policy_default_rx;
+ vl_api_npol_policy_default_t policy_default_tx;
+ vl_api_npol_policy_default_t profile_default_rx;
+ vl_api_npol_policy_default_t profile_default_tx;
+ u32 policy_ids[total_ids]; // rx_policies, then tx_policies, then profiles
+};
--- /dev/null
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright(c) 2025 Cisco Systems, Inc.
+ */
+
+#include <npol/npol.h>
+#include <npol/npol_match.h>
+#include <npol/npol_format.h>
+
+static clib_error_t *
+npol_match_fn (vlib_main_t *vm, unformat_input_t *input,
+ vlib_cli_command_t *cmd)
+{
+ vnet_main_t *vnm = vnet_get_main ();
+ u32 sw_if_index = NPOL_INVALID_INDEX;
+ u8 _r_action = NPOL_ACTION_UNKNOWN, *r_action = &_r_action;
+ fa_5tuple_t _pkt_5tuple = { 0 }, *pkt_5tuple = &_pkt_5tuple;
+ clib_error_t *error = 0;
+ u32 is_inbound = 0;
+ int is_ip6 = 0;
+ u32 sport = 0, dport = 0, proto = 0;
+ int rv;
+
+ while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
+ {
+ if (unformat (input, "%U", unformat_vnet_sw_interface, vnm,
+ &sw_if_index))
+ ;
+ else if (unformat (input, "sw_if_index %u", &sw_if_index))
+ ;
+ else if (unformat (input, "inbound"))
+ is_inbound = 1;
+ else if (unformat (input, "outbound"))
+ is_inbound = 0;
+ else if (unformat (input, "ip6"))
+ is_ip6 = 1;
+ else if (unformat (input, "ip4"))
+ is_ip6 = 0;
+ else if (unformat (input, "%U;%u->%U;%u", unformat_ip4_address,
+ &pkt_5tuple->ip4_addr[SRC], &sport,
+ unformat_ip4_address, &pkt_5tuple->ip4_addr[DST],
+ &dport))
+ {
+ pkt_5tuple->l4.port[SRC] = sport;
+ pkt_5tuple->l4.port[DST] = dport;
+ }
+ else if (unformat (input, "%U;%u->%U;%u", unformat_ip6_address,
+ &pkt_5tuple->ip6_addr[SRC], &sport,
+ unformat_ip6_address, &pkt_5tuple->ip6_addr[DST],
+ &dport))
+ {
+ pkt_5tuple->l4.port[SRC] = sport;
+ pkt_5tuple->l4.port[DST] = dport;
+ }
+ else if (unformat (input, "%U", unformat_ip_protocol, &proto))
+ pkt_5tuple->l4.proto = proto;
+ else
+ {
+ error = clib_error_return (0, "unknown input '%U'",
+ format_unformat_error, input);
+ goto done;
+ }
+ }
+
+ if (sw_if_index == NPOL_INVALID_INDEX)
+ {
+ error = clib_error_return (0, "interface not specified");
+ goto done;
+ }
+
+ rv = npol_match_func (sw_if_index, is_inbound, pkt_5tuple, is_ip6, r_action);
+
+ vlib_cli_output (vm, "matched:%d action:%U", rv, format_npol_action,
+ *r_action);
+
+done:
+ return error;
+}
+
+VLIB_CLI_COMMAND (npol_match, static) = {
+ .path = "npol match",
+ .function = npol_match_fn,
+ .short_help = "npol match [<interface>|sw_if_index <idx>] [ip4|ip6] "
+ "[inbound|outbound] 1.1.1.1;65000->3.3.3.3;8080 tcp",
+};
--- /dev/null
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright(c) 2025 Cisco Systems, Inc.
+ */
+
+#ifndef included_npol_h
+#define included_npol_h
+
+#include <vnet/ip/ip.h>
+#include <vnet/ip/ip_types_api.h>
+#include <acl/public_inlines.h>
+
+#include <npol/npol.api_enum.h>
+#include <npol/npol.api_types.h>
+#include <npol/npol_interface.h>
+
+#define NPOL_INVALID_INDEX ((u32) ~0)
+
+#define SRC 0
+#define DST 1
+
+#define NPOL_ACTION_ALLOW 2
+#define NPOL_ACTION_UNKNOWN 1
+#define NPOL_ACTION_DENY 0
+
+typedef struct
+{
+ u16 start;
+ u16 end;
+} npol_port_range_t;
+
+typedef struct
+{
+ u32 calico_acl_user_id;
+
+ /* API message ID base */
+ u16 msg_id_base;
+
+} npol_main_t;
+
+extern npol_main_t npol_main;
+
+#endif
--- /dev/null
+=============================
+Network Policy (npol) Plugin
+=============================
+
+Overview
+--------
+
+The **Network Policy (npol)** plugin provides a programmable policy engine
+for applying packet filtering and forwarding rules in VPP.
+It allows you to:
+
+- Create and manage **IP sets** (collections of IPs, subnets, or IP:port entries).
+- Define **rules** to allow, deny, or log traffic based on IPs, prefixes, sets, ports, and direction.
+- Build **policies** from rules and apply them on interfaces in RX (inbound) and TX (outbound) directions.
+
+
+Quick Start
+-----------
+
+This example shows how to configure and apply a network policy on a loopback interface.
+
+1. **Create a loopback interface and configure an IP address**
+
+.. code-block:: console
+
+ DBGvpp# create loopback interface
+ loop0
+
+ DBGvpp# set interface state loop0 up
+
+ DBGvpp# set interface ip address loop0 10.0.0.1/32
+
+ DBGvpp# sh int addr
+ local0 (dn):
+ loop0 (up):
+ L3 10.0.0.1/32
+
+2. **Explore npol commands**
+
+.. code-block:: console
+
+ DBGvpp# npol ?
+ npol interface clear npol interface clear [interface | sw_if_index N]
+ npol interface configure npol interface configure [interface | sw_if_index N] rx <num_rx> tx <num_tx> <policy_id> ...
+ npol ipset add member npol ipset add member [id] [prefix]
+ npol ipset add npol ipset add [prefix|proto ip port|ip]
+ npol ipset del member npol ipset del member [id] [prefix]
+ npol ipset del npol ipset del [id]
+ npol policy add npol policy add [rx rule_id rule_id ...] [tx rule_id rule_id ...] [update [id]]
+ npol policy del npol policy del [id]
+ npol rule add npol rule add [ip4|ip6] [allow|deny|log|pass][filter[==|!=]value][[src|dst][==|!=][prefix|set ID|[port-port]]]
+ npol rule del npol rule del [id]
+
+3. **Create an IP set**
+
+.. code-block:: console
+
+ DBGvpp# npol ipset add 20.0.0.0/24
+ npol ipset 0 added
+
+ DBGvpp# sh npol ipsets
+ [ipset#0;prefix;20.0.0.0/24,]
+
+4. **Add rules**
+
+- Rule 0: Deny packets with a source IP in the created set.
+- Rule 1: Allow all other packets.
+
+.. code-block:: console
+
+ DBGvpp# npol rule add ip4 deny src==set0
+ npol rule 0 added
+
+ DBGvpp# npol rule add ip4 allow
+ npol rule 1 added
+
+ DBGvpp# sh npol rules
+ [rule#0;deny][src==[ipset#0;prefix;20.0.0.0/24,],]
+ [rule#1;allow][]
+
+5. **Create a policy**
+
+This policy applies Rule 0 and Rule 1 on RX,
+and Rule 1 on TX.
+
+.. code-block:: console
+
+ DBGvpp# npol policy add rx 0 1 tx 1
+ npol policy 0 added
+
+ DBGvpp# sh npol policies verbose
+ [policy#0]
+ tx:[rule#1;allow][]
+ rx:[rule#0;deny][src==[ipset#0;prefix;20.0.0.0/24,],]
+ rx:[rule#1;allow][]
+
+6. **Apply the policy to an interface**
+
+.. code-block:: console
+
+ DBGvpp# npol interface configure loop0 0
+ npol interface 1 configured
+
+ DBGvpp# sh npol interfaces
+ Interfaces with policies configured:
+ [loop0 sw_if_index=1 addr=10.0.0.1]
+ rx-policy-default:1 rx-profile-default:1
+ tx-policy-default:1 tx-profile-default:1
+ profiles:
+ [policy#0]
+ tx:[rule#1;allow][]
+ rx:[rule#0;deny][src==[ipset#0;prefix;20.0.0.0/24,],]
+ rx:[rule#1;allow][]
+
+Summary
+-------
+
+- **IP sets** define groups of IPs, prefixes, or IP:port pairs.
+- **Rules** define match conditions and actions (allow, deny, log, pass).
+- **Policies** group rules per direction (RX/TX).
+- **Interfaces** are configured with policies, enforcing filtering in the datapath.
+
+This modular design allows fine-grained policy enforcement
+directly in VPP with efficient data structures.
--- /dev/null
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright(c) 2025 Cisco Systems, Inc.
+ */
+
+#include <vnet/vnet.h>
+#include <vnet/plugin/plugin.h>
+#include <vlibapi/api.h>
+#include <vlibmemory/api.h>
+#include <vpp/app/version.h>
+#include <stdbool.h>
+
+#include <npol/npol.h>
+#include <npol/npol_rule.h>
+#include <npol/npol_policy.h>
+#include <npol/npol_ipset.h>
+#include <npol/npol_interface.h>
+
+#define REPLY_MSG_ID_BASE cpm->msg_id_base
+#include <vlibapi/api_helper_macros.h>
+
+#define CALICO_POLICY_VERSION_MAJOR 0
+#define CALICO_POLICY_VERSION_MINOR 0
+
+npol_main_t npol_main = { 0 };
+
+void
+npol_policy_rule_decode (const vl_api_npol_policy_item_t *in,
+ npol_policy_rule_t *out)
+{
+ out->rule_id = clib_net_to_host_u32 (in->rule_id);
+ out->direction = in->is_inbound ? VLIB_RX : VLIB_TX;
+}
+
+int
+npol_ipset_member_decode (npol_ipset_type_t type,
+ const vl_api_npol_ipset_member_t *in,
+ npol_ipset_member_t *out)
+{
+ switch (type)
+ {
+ case IPSET_TYPE_IP:
+ ip_address_decode2 (&in->val.address, &out->address);
+ break;
+ case IPSET_TYPE_IPPORT:
+ ip_address_decode2 (&in->val.tuple.address, &out->ipport.addr);
+ out->ipport.l4proto = in->val.tuple.l4_proto;
+ out->ipport.port = clib_net_to_host_u16 (in->val.tuple.port);
+ break;
+ case IPSET_TYPE_NET:
+ return ip_prefix_decode2 (&in->val.prefix, &out->prefix);
+ }
+ return 0;
+}
+
+void
+npol_port_range_decode (const vl_api_npol_port_range_t *in,
+ npol_port_range_t *out)
+{
+ out->start = clib_net_to_host_u16 (in->start);
+ out->end = clib_net_to_host_u16 (in->end);
+}
+
+int
+npol_rule_entry_decode (const vl_api_npol_rule_entry_t *in,
+ npol_rule_entry_t *out)
+{
+ out->flags = 0;
+ if (in->is_src)
+ out->flags |= NPOL_IS_SRC;
+ if (in->is_not)
+ out->flags |= NPOL_IS_NOT;
+ out->type = (npol_entry_type_t) in->type;
+ switch (in->type)
+ {
+ case NPOL_CIDR:
+ return ip_prefix_decode2 (&in->data.cidr, &out->data.cidr);
+ case NPOL_PORT_RANGE:
+ npol_port_range_decode (&in->data.port_range, &out->data.port_range);
+ return 0;
+ case NPOL_PORT_IP_SET:
+ case NPOL_IP_SET:
+ out->data.set_id = clib_net_to_host_u32 (in->data.set_id.set_id);
+ return 0;
+ default:
+ return -1;
+ }
+}
+
+void
+npol_rule_filter_decode (const vl_api_npol_rule_filter_t *in,
+ npol_rule_filter_t *out)
+{
+ out->type = (npol_rule_filter_type_t) in->type;
+ out->should_match = in->should_match;
+ out->value = clib_net_to_host_u32 (in->value);
+}
+
+static void
+vl_api_npol_get_version_t_handler (vl_api_npol_get_version_t *mp)
+{
+ npol_main_t *cpm = &npol_main;
+ vl_api_npol_get_version_reply_t *rmp;
+ int msg_size = sizeof (*rmp);
+ vl_api_registration_t *reg;
+
+ reg = vl_api_client_index_to_registration (mp->client_index);
+ if (!reg)
+ return;
+
+ rmp = vl_msg_api_alloc (msg_size);
+ clib_memset (rmp, 0, msg_size);
+ rmp->_vl_msg_id = ntohs (VL_API_NPOL_GET_VERSION_REPLY + cpm->msg_id_base);
+ rmp->context = mp->context;
+ rmp->major = htonl (CALICO_POLICY_VERSION_MAJOR);
+ rmp->minor = htonl (CALICO_POLICY_VERSION_MINOR);
+
+ vl_api_send_msg (reg, (u8 *) rmp);
+}
+
+/* NAME: ipset_create */
+static void
+vl_api_npol_ipset_create_t_handler (vl_api_npol_ipset_create_t *mp)
+{
+ npol_main_t *cpm = &npol_main;
+ vl_api_npol_ipset_create_reply_t *rmp;
+ int rv = 0;
+ u32 id;
+
+ id = npol_ipset_create ((npol_ipset_type_t) mp->type);
+
+ REPLY_MACRO2 (VL_API_NPOL_IPSET_CREATE_REPLY,
+ ({ rmp->set_id = clib_host_to_net_u32 (id); }));
+}
+
+/* NAME: ipset_add_del_members */
+static void
+vl_api_npol_ipset_add_del_members_t_handler (
+ vl_api_npol_ipset_add_del_members_t *mp)
+{
+ npol_main_t *cpm = &npol_main;
+ vl_api_npol_ipset_add_del_members_reply_t *rmp;
+ u32 set_id, i, n_members;
+ npol_ipset_type_t type;
+ int rv = 0;
+
+ set_id = clib_net_to_host_u32 (mp->set_id);
+ n_members = clib_net_to_host_u32 (mp->len);
+
+ rv = npol_ipset_get_type (set_id, &type);
+ if (rv)
+ goto done;
+
+ for (i = 0; i < n_members; i++)
+ {
+ npol_ipset_member_t _m, *member = &_m;
+ rv = npol_ipset_member_decode (type, &mp->members[i], member);
+ if (rv)
+ break;
+ if (mp->is_add)
+ rv = npol_ipset_add_member (set_id, member);
+ else
+ rv = npol_ipset_del_member (set_id, member);
+ if (rv)
+ break;
+ }
+
+done:
+ REPLY_MACRO (VL_API_NPOL_IPSET_ADD_DEL_MEMBERS_REPLY);
+}
+
+/* NAME: ipset_delete */
+static void
+vl_api_npol_ipset_delete_t_handler (vl_api_npol_ipset_delete_t *mp)
+{
+ npol_main_t *cpm = &npol_main;
+ vl_api_npol_ipset_delete_reply_t *rmp;
+ u32 set_id;
+ int rv;
+
+ set_id = clib_net_to_host_u32 (mp->set_id);
+ rv = npol_ipset_delete (set_id);
+
+ REPLY_MACRO (VL_API_NPOL_IPSET_DELETE_REPLY);
+}
+
+static int
+vl_api_npol_rule_update_create_handler (u32 *id, vl_api_npol_rule_t *rule)
+{
+ npol_rule_filter_t *filters = 0, *filter;
+ npol_rule_entry_t *entries = 0, *entry;
+ npol_rule_action_t action;
+ int rv;
+ u32 n_matches;
+ u32 i;
+
+ action = (npol_rule_action_t) rule->action;
+
+ for (i = 0; i < ARRAY_LEN (rule->filters); i++)
+ {
+ vec_add2 (filters, filter, 1);
+ npol_rule_filter_decode (&rule->filters[i], filter);
+ }
+
+ n_matches = clib_net_to_host_u32 (rule->num_entries);
+ for (i = 0; i < n_matches; i++)
+ {
+ vec_add2 (entries, entry, 1);
+ if ((rv = npol_rule_entry_decode (&rule->matches[i], entry)))
+ goto done;
+ }
+
+ rv = npol_rule_update (id, action, filters, entries);
+
+done:
+ vec_free (filters);
+ vec_free (entries);
+ return rv;
+}
+
+/* NAME: rule_create */
+static void
+vl_api_npol_rule_create_t_handler (vl_api_npol_rule_create_t *mp)
+{
+ vl_api_npol_rule_create_reply_t *rmp;
+ npol_main_t *cpm = &npol_main;
+ u32 id = NPOL_INVALID_INDEX;
+ int rv;
+
+ rv = vl_api_npol_rule_update_create_handler (&id, &mp->rule);
+
+ REPLY_MACRO2 (VL_API_NPOL_RULE_CREATE_REPLY,
+ ({ rmp->rule_id = clib_host_to_net_u32 (id); }));
+}
+
+/* NAME: rule_update */
+static void
+vl_api_npol_rule_update_t_handler (vl_api_npol_rule_update_t *mp)
+{
+ vl_api_npol_rule_update_reply_t *rmp;
+ npol_main_t *cpm = &npol_main;
+ u32 id;
+ int rv;
+
+ id = clib_net_to_host_u32 (mp->rule_id);
+ rv = vl_api_npol_rule_update_create_handler (&id, &mp->rule);
+
+ REPLY_MACRO (VL_API_NPOL_RULE_UPDATE_REPLY);
+}
+
+/* NAME: rule_delete */
+static void
+vl_api_npol_rule_delete_t_handler (vl_api_npol_rule_delete_t *mp)
+{
+ vl_api_npol_rule_delete_reply_t *rmp;
+ npol_main_t *cpm = &npol_main;
+ u32 id;
+ int rv;
+
+ id = clib_net_to_host_u32 (mp->rule_id);
+ rv = npol_rule_delete (id);
+
+ REPLY_MACRO (VL_API_NPOL_RULE_DELETE_REPLY);
+}
+
+static int
+vl_api_npol_policy_update_create_handler (u32 *id, u32 n_rules,
+ vl_api_npol_policy_item_t *api_rules)
+{
+ npol_policy_rule_t *rules = 0, *rule;
+ int rv;
+
+ for (u32 i = 0; i < n_rules; i++)
+ {
+ vec_add2 (rules, rule, 1);
+ npol_policy_rule_decode (&api_rules[i], rule);
+ }
+
+ rv = npol_policy_update (id, rules);
+
+ vec_free (rules);
+ return rv;
+}
+
+/* NAME: policy_create */
+static void
+vl_api_npol_policy_create_t_handler (vl_api_npol_policy_create_t *mp)
+{
+ vl_api_npol_policy_create_reply_t *rmp;
+ npol_main_t *cpm = &npol_main;
+ u32 id = NPOL_INVALID_INDEX, n_rules;
+ int rv;
+
+ n_rules = clib_net_to_host_u32 (mp->num_items);
+ rv = vl_api_npol_policy_update_create_handler (&id, n_rules, mp->rules);
+
+ REPLY_MACRO2 (VL_API_NPOL_POLICY_CREATE_REPLY,
+ ({ rmp->policy_id = clib_host_to_net_u32 (id); }));
+}
+
+/* NAME: policy_update */
+static void
+vl_api_npol_policy_update_t_handler (vl_api_npol_policy_update_t *mp)
+{
+ vl_api_npol_policy_update_reply_t *rmp;
+ npol_main_t *cpm = &npol_main;
+ u32 id, n_rules;
+ int rv;
+
+ id = clib_net_to_host_u32 (mp->policy_id);
+ n_rules = clib_net_to_host_u32 (mp->num_items);
+ rv = vl_api_npol_policy_update_create_handler (&id, n_rules, mp->rules);
+
+ REPLY_MACRO (VL_API_NPOL_POLICY_UPDATE_REPLY);
+}
+
+/* NAME: policy_delete */
+static void
+vl_api_npol_policy_delete_t_handler (vl_api_npol_policy_delete_t *mp)
+{
+ vl_api_npol_policy_delete_reply_t *rmp;
+ npol_main_t *cpm = &npol_main;
+ u32 id;
+ int rv = 0;
+
+ id = clib_net_to_host_u32 (mp->policy_id);
+ rv = npol_policy_delete (id);
+
+ REPLY_MACRO (VL_API_NPOL_POLICY_DELETE_REPLY);
+}
+
+static void
+npol_interface_config_decode (const vl_api_npol_configure_policies_t *in,
+ npol_interface_config_t *out)
+{
+ u32 num_rx_policies, num_tx_policies, total_ids, num_profiles;
+ int i = 0;
+
+ num_rx_policies = clib_net_to_host_u32 (in->num_rx_policies);
+ num_tx_policies = clib_net_to_host_u32 (in->num_tx_policies);
+ total_ids = clib_net_to_host_u32 (in->total_ids);
+ num_profiles = total_ids - num_rx_policies - num_tx_policies;
+
+ out->invert_rx_tx = in->invert_rx_tx;
+ out->policy_default_rx = in->policy_default_rx;
+ out->policy_default_tx = in->policy_default_tx;
+ out->profile_default_rx = in->profile_default_rx;
+ out->profile_default_tx = in->profile_default_tx;
+ vec_resize (out->rx_policies, num_rx_policies);
+ for (i = 0; i < num_rx_policies; i++)
+ out->rx_policies[i] = clib_net_to_host_u32 (in->policy_ids[i]);
+ vec_resize (out->tx_policies, num_tx_policies);
+ for (i = 0; i < num_tx_policies; i++)
+ out->tx_policies[i] =
+ clib_net_to_host_u32 (in->policy_ids[num_rx_policies + i]);
+ vec_resize (out->profiles, num_profiles);
+ for (i = 0; i < num_profiles; i++)
+ out->profiles[i] = clib_net_to_host_u32 (
+ in->policy_ids[num_rx_policies + num_tx_policies + i]);
+}
+
+/* NAME: configure_policies */
+static void
+vl_api_npol_configure_policies_t_handler (vl_api_npol_configure_policies_t *mp)
+{
+ npol_main_t *cpm = &npol_main;
+ npol_interface_config_t _conf = { 0 }, *conf = &_conf;
+ vl_api_npol_configure_policies_reply_t *rmp;
+ u32 sw_if_index;
+ int rv = -1;
+
+ sw_if_index = clib_net_to_host_u32 (mp->sw_if_index);
+ npol_interface_config_decode (mp, conf);
+
+ rv = npol_configure_policies (sw_if_index, conf);
+
+ REPLY_MACRO (VL_API_NPOL_CONFIGURE_POLICIES_REPLY);
+}
+
+/* Set up the API message handling tables */
+#include <vnet/format_fns.h>
+#include <npol/npol.api.c>
+
+#include <vat/vat.h>
+#include <vlibapi/vat_helper_macros.h>
+
+/* Declare message IDs */
+#include <acl/acl.api_enum.h>
+#include <acl/acl.api_types.h>
+#undef vl_print
+#define vl_print(handle, ...)
+#undef vl_print
+#define vl_endianfun /* define message structures */
+#include <acl/acl.api.h>
+#undef vl_endianfun
+
+static clib_error_t *
+calpol_init (vlib_main_t *vm)
+{
+ npol_main_t *cpm = &npol_main;
+
+ cpm->msg_id_base = setup_message_id_table ();
+
+ return (NULL);
+}
+
+static clib_error_t *
+calpol_plugin_config (vlib_main_t *vm, unformat_input_t *input)
+{
+ return NULL;
+}
+
+VLIB_PLUGIN_REGISTER () = {
+ .version = VPP_BUILD_VER,
+ .description = "Network Policy",
+};
+
+VLIB_CONFIG_FUNCTION (calpol_plugin_config, "calico-policy-plugin");
+
+VLIB_INIT_FUNCTION (calpol_init) = {
+ .runs_after = VLIB_INITS ("acl_init"),
+};
--- /dev/null
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright(c) 2025 Cisco Systems, Inc.
+ */
+
+#include <npol/npol.h>
+#include <npol/npol_rule.h>
+#include <npol/npol_policy.h>
+#include <npol/npol_ipset.h>
+
+u8 *
+format_npol_action (u8 *s, va_list *args)
+{
+ int action = va_arg (*args, int);
+ switch (action)
+ {
+ case NPOL_ACTION_ALLOW:
+ return format (s, "ALLOW");
+ case NPOL_ACTION_DENY:
+ return format (s, "DENY");
+ default:
+ return format (s, "unknown type %d", action);
+ }
+}
+
+u8 *
+format_npol_ipport (u8 *s, va_list *args)
+{
+ npol_ipport_t *ipport = va_arg (*args, npol_ipport_t *);
+ return format (s, "%U %U;%u", format_ip_protocol, ipport->l4proto,
+ format_ip_address, &ipport->addr, ipport->port);
+}
+
+u8 *
+format_npol_ipset_member (u8 *s, va_list *args)
+{
+ npol_ipset_member_t *member = va_arg (*args, npol_ipset_member_t *);
+ npol_ipset_type_t type = va_arg (*args, npol_ipset_type_t);
+ switch (type)
+ {
+ case IPSET_TYPE_IP:
+ return format (s, "%U", format_ip_address, &member->address);
+ case IPSET_TYPE_IPPORT:
+ return format (s, "%U", format_npol_ipport, &member->ipport);
+ case IPSET_TYPE_NET:
+ return format (s, "%U", format_ip_prefix, &member->prefix);
+ default:
+ return format (s, "unknown type");
+ }
+}
+
+uword
+unformat_npol_ipport (unformat_input_t *input, va_list *args)
+{
+ npol_ipport_t *ipport = va_arg (*args, npol_ipport_t *);
+ u32 proto;
+ u32 port;
+ if (unformat (input, "%U %U %d", unformat_ip_protocol, &proto,
+ unformat_ip_address, &ipport->addr, &port))
+ ;
+ else
+ return 0;
+
+ ipport->port = port;
+ ipport->l4proto = (u8) proto;
+ return 1;
+}
+
+u8 *
+format_npol_ipset_type (u8 *s, va_list *args)
+{
+ npol_ipset_type_t type = va_arg (*args, npol_ipset_type_t);
+ switch (type)
+ {
+ case IPSET_TYPE_IP:
+ return format (s, "ip");
+ case IPSET_TYPE_IPPORT:
+ return format (s, "ip+port");
+ case IPSET_TYPE_NET:
+ return format (s, "prefix");
+ default:
+ return format (s, "unknownipsettype");
+ }
+}
+
+uword
+unformat_npol_ipset_member (unformat_input_t *input, va_list *args)
+{
+ npol_ipset_member_t *member = va_arg (*args, npol_ipset_member_t *);
+ npol_ipset_type_t *type = va_arg (*args, npol_ipset_type_t *);
+ if (unformat_user (input, unformat_ip_prefix, &member->prefix))
+ *type = IPSET_TYPE_NET;
+ else if (unformat_user (input, unformat_ip_address, &member->address))
+ *type = IPSET_TYPE_IP;
+ else if (unformat_user (input, unformat_npol_ipport, &member->ipport))
+ *type = IPSET_TYPE_IPPORT;
+ else
+ return 0;
+
+ return 1;
+}
+
+u8 *
+format_npol_ipset (u8 *s, va_list *args)
+{
+ npol_ipset_t *ipset = va_arg (*args, npol_ipset_t *);
+ npol_ipset_member_t *member;
+
+ if (ipset == NULL)
+ return format (s, "deleted ipset");
+
+ s = format (s, "[ipset#%d;%U;", ipset - npol_ipsets, format_npol_ipset_type,
+ ipset->type);
+
+ pool_foreach (member, ipset->members)
+ s = format (s, "%U,", format_npol_ipset_member, member, ipset->type);
+
+ s = format (s, "]");
+
+ return (s);
+}
+
+u8 *
+format_npol_rule_action (u8 *s, va_list *args)
+{
+ npol_rule_action_t action = va_arg (*args, int);
+ switch (action)
+ {
+ case NPOL_ALLOW:
+ return format (s, "allow");
+ case NPOL_DENY:
+ return format (s, "deny");
+ case NPOL_LOG:
+ return format (s, "log");
+ case NPOL_PASS:
+ return format (s, "pass");
+ default:
+ return format (s, "unknownaction");
+ }
+}
+
+uword
+unformat_npol_rule_action (unformat_input_t *input, va_list *args)
+{
+ npol_rule_action_t *action = va_arg (*args, npol_rule_action_t *);
+ if (unformat (input, "allow"))
+ *action = NPOL_ALLOW;
+ else if (unformat (input, "deny"))
+ *action = NPOL_DENY;
+ else if (unformat (input, "log"))
+ *action = NPOL_LOG;
+ else if (unformat (input, "pass"))
+ *action = NPOL_PASS;
+ else
+ return 0;
+ return 1;
+}
+
+u8 *
+format_npol_rule_port_range (u8 *s, va_list *args)
+{
+ npol_port_range_t *port_range = va_arg (*args, npol_port_range_t *);
+
+ if (port_range->start != port_range->end)
+ s = format (s, "[%u-%u]", port_range->start, port_range->end);
+ else
+ s = format (s, "%u", port_range->start);
+
+ return (s);
+}
+
+u8 *
+format_npol_rule_entry (u8 *s, va_list *args)
+{
+ npol_rule_entry_t *entry = va_arg (*args, npol_rule_entry_t *);
+ npol_ipset_t *ipset;
+
+ s = format (s, "%s", entry->flags & NPOL_IS_SRC ? "src" : "dst");
+ s = format (s, "%s", entry->flags & NPOL_IS_NOT ? "!=" : "==");
+ switch (entry->type)
+ {
+ case NPOL_CIDR:
+ s = format (s, "%U", format_ip_prefix, &entry->data.cidr);
+ break;
+ case NPOL_PORT_RANGE:
+ s =
+ format (s, "%U", format_npol_rule_port_range, &entry->data.port_range);
+ break;
+ case NPOL_IP_SET:
+ ipset = npol_ipsets_get_if_exists (entry->data.set_id);
+ s = format (s, "%U", format_npol_ipset, ipset);
+ break;
+ case NPOL_PORT_IP_SET:
+ ipset = npol_ipsets_get_if_exists (entry->data.set_id);
+ s = format (s, "%U", format_npol_ipset, ipset);
+ break;
+ default:
+ s = format (s, "unknown");
+ break;
+ }
+ return (s);
+}
+
+uword
+unformat_rule_key_flag (unformat_input_t *input, va_list *args)
+{
+ npol_rule_key_flag_t *flags = va_arg (*args, npol_rule_key_flag_t *);
+ if (unformat (input, "src=="))
+ *flags = NPOL_IS_SRC;
+ else if (unformat (input, "src!="))
+ *flags = NPOL_IS_SRC | NPOL_IS_NOT;
+ else if (unformat (input, "dst!="))
+ *flags = NPOL_IS_NOT;
+ else if (unformat (input, "dst=="))
+ *flags = 0;
+ else
+ return 0;
+ return 1;
+}
+
+uword
+unformat_npol_port_range (unformat_input_t *input, va_list *args)
+{
+ npol_port_range_t *port_range = va_arg (*args, npol_port_range_t *);
+ u32 start, end;
+ if (unformat (input, "[%d-%d]", &start, &end))
+ {
+ port_range->start = (u16) start;
+ port_range->end = (u16) end;
+ }
+ else
+ return 0;
+ return 1;
+}
+
+uword
+unformat_npol_rule_entry (unformat_input_t *input, va_list *args)
+{
+ npol_rule_entry_t *entry = va_arg (*args, npol_rule_entry_t *);
+ if (unformat (input, "%U %U", unformat_rule_key_flag, &entry->flags,
+ unformat_ip_prefix, &entry->data.cidr))
+ entry->type = NPOL_CIDR;
+ else if (unformat (input, "%U %U", unformat_rule_key_flag, &entry->flags,
+ unformat_npol_port_range, &entry->data.port_range))
+ entry->type = NPOL_PORT_RANGE;
+ else if (unformat (input, "%Uset %u", unformat_rule_key_flag, &entry->flags,
+ &entry->data.set_id))
+ entry->type = NPOL_PORT_IP_SET;
+ else
+ return 0;
+ return 1;
+}
+
+u8 *
+format_npol_rule_filter (u8 *s, va_list *args)
+{
+ npol_rule_filter_t *filter = va_arg (*args, npol_rule_filter_t *);
+ switch (filter->type)
+ {
+ case NPOL_RULE_FILTER_NONE_TYPE:
+ return format (s, "<no filter>");
+ case NPOL_RULE_FILTER_ICMP_TYPE:
+ return format (s, "icmp-type%s=%d", filter->should_match ? "=" : "!",
+ filter->value);
+ case NPOL_RULE_FILTER_ICMP_CODE:
+ return format (s, "icmp-code%s=%d", filter->should_match ? "=" : "!",
+ filter->value);
+ case NPOL_RULE_FILTER_L4_PROTO:
+ return format (s, "proto%s=%U", filter->should_match ? "=" : "!",
+ format_ip_protocol, filter->value);
+ default:
+ return format (s, "unknown");
+ }
+}
+
+uword
+unformat_npol_should_match (unformat_input_t *input, va_list *args)
+{
+ u8 *should_match = va_arg (*args, u8 *);
+ if (unformat (input, "=="))
+ *should_match = 1;
+ else if (unformat (input, "!="))
+ *should_match = 0;
+ else
+ return 0;
+ return 1;
+}
+
+uword
+unformat_npol_rule_filter (unformat_input_t *input, va_list *args)
+{
+ u8 tmp_value;
+ npol_rule_filter_t *filter = va_arg (*args, npol_rule_filter_t *);
+ if (unformat (input, "icmp-type%U%d", unformat_npol_should_match,
+ &filter->should_match, &filter->value))
+ filter->type = NPOL_RULE_FILTER_ICMP_TYPE;
+ else if (unformat (input, "icmp-code%U%d", unformat_npol_should_match,
+ &filter->should_match, &filter->value))
+ filter->type = NPOL_RULE_FILTER_ICMP_CODE;
+ else if (unformat (input, "proto%U%U", unformat_npol_should_match,
+ &filter->should_match, unformat_ip_protocol, &tmp_value))
+ {
+ filter->value = tmp_value;
+ filter->type = NPOL_RULE_FILTER_L4_PROTO;
+ }
+ else
+ return 0;
+ return 1;
+}
+
+u8 *
+format_npol_rule (u8 *s, va_list *args)
+{
+ npol_rule_t *rule = va_arg (*args, npol_rule_t *);
+ npol_rule_filter_t *filter;
+ npol_rule_entry_t *entry, *entries;
+
+ if (rule == NULL)
+ return format (s, "deleted rule");
+
+ s = format (s, "[rule#%d;%U][", rule - npol_rules, format_npol_rule_action,
+ rule->action);
+
+ /* filters */
+ vec_foreach (filter, rule->filters)
+ {
+ if (filter->type != NPOL_RULE_FILTER_NONE_TYPE)
+ s = format (s, "%U,", format_npol_rule_filter, filter);
+ }
+
+ entries = npol_rule_get_entries (rule);
+ vec_foreach (entry, entries)
+ s = format (s, "%U,", format_npol_rule_entry, entry);
+ vec_free (entries);
+ s = format (s, "]");
+
+ return (s);
+}
+
+u8 *
+format_npol_policy (u8 *s, va_list *args)
+{
+ npol_policy_t *policy = va_arg (*args, npol_policy_t *);
+ int indent = va_arg (*args, int);
+ int verbose = va_arg (*args, int);
+ int invert_rx_tx = va_arg (*args, int);
+ u32 *rule_id;
+
+ if (policy == NULL)
+ return format (s, "deleted policy");
+
+ if (verbose)
+ {
+ s = format (s, "[policy#%u]\n", policy - npol_policies);
+ npol_rule_t *rule;
+ if (verbose != NPOL_POLICY_ONLY_RX)
+ vec_foreach (rule_id, policy->rule_ids[VLIB_TX ^ invert_rx_tx])
+ {
+ rule = npol_rule_get_if_exists (*rule_id);
+ s = format (s, "%Utx:%U\n", format_white_space, indent + 2,
+ format_npol_rule, rule);
+ }
+ if (verbose != NPOL_POLICY_ONLY_TX)
+ vec_foreach (rule_id, policy->rule_ids[VLIB_RX ^ invert_rx_tx])
+ {
+ rule = npol_rule_get_if_exists (*rule_id);
+ s = format (s, "%Urx:%U\n", format_white_space, indent + 2,
+ format_npol_rule, rule);
+ }
+ }
+ else
+ {
+ s = format (s, "[policy#%u] rx-rules:%d tx-rules:%d\n",
+ policy - npol_policies,
+ vec_len (policy->rule_ids[VLIB_RX ^ invert_rx_tx]),
+ vec_len (policy->rule_ids[VLIB_TX ^ invert_rx_tx]));
+ }
+
+ return (s);
+}
+
+u8 *
+format_npol_interface (u8 *s, va_list *args)
+{
+ u32 sw_if_index = va_arg (*args, u32);
+ npol_interface_config_t *conf = va_arg (*args, npol_interface_config_t *);
+ vnet_main_t *vnm = vnet_get_main ();
+ npol_policy_t *policy = NULL;
+ u32 *rx_policies = conf->rx_policies;
+ u32 *tx_policies = conf->tx_policies;
+ u32 i;
+
+ s = format (s, "[%U sw_if_index=%u ", format_vnet_sw_if_index_name, vnm,
+ sw_if_index, sw_if_index);
+ if (conf->invert_rx_tx)
+ {
+ s = format (s, "inverted");
+ rx_policies = conf->tx_policies;
+ tx_policies = conf->rx_policies;
+ }
+ ip4_address_t *ip4 = 0;
+ ip4 = ip4_interface_first_address (&ip4_main, sw_if_index, 0);
+ if (ip4)
+ s = format (s, " addr=%U", format_ip4_address, ip4);
+ ip6_address_t *ip6 = 0;
+ ip6 = ip6_interface_first_address (&ip6_main, sw_if_index);
+ if (ip6)
+ s = format (s, " addr6=%U", format_ip6_address, ip6);
+ s = format (s, "]\n");
+ if (vec_len (rx_policies))
+ {
+ s = format (s, " rx:\n");
+ }
+ s = format (s, " rx-policy-default:%d rx-profile-default:%d \n",
+ conf->policy_default_rx, conf->profile_default_rx);
+ vec_foreach_index (i, rx_policies)
+ {
+ policy = npol_policy_get_if_exists (rx_policies[i]);
+ s = format (s, " %U", format_npol_policy, policy, 4 /* indent */,
+ NPOL_POLICY_ONLY_RX, conf->invert_rx_tx);
+ }
+ if (vec_len (tx_policies))
+ {
+ s = format (s, " tx:\n");
+ }
+ s = format (s, " tx-policy-default:%d tx-profile-default:%d \n",
+ conf->policy_default_tx, conf->profile_default_tx);
+ vec_foreach_index (i, tx_policies)
+ {
+ policy = npol_policy_get_if_exists (tx_policies[i]);
+ s = format (s, " %U", format_npol_policy, policy, 4 /* indent */,
+ NPOL_POLICY_ONLY_TX, conf->invert_rx_tx);
+ }
+ if (vec_len (conf->profiles))
+ s = format (s, " profiles:\n");
+ vec_foreach_index (i, conf->profiles)
+ {
+ policy = npol_policy_get_if_exists (conf->profiles[i]);
+ s = format (s, " %U", format_npol_policy, policy, 4 /* indent */,
+ NPOL_POLICY_VERBOSE, conf->invert_rx_tx);
+ }
+ return s;
+}
--- /dev/null
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright(c) 2025 Cisco Systems, Inc.
+ */
+
+#ifndef included_npol_format_h
+#define included_npol_format_h
+
+u8 *format_npol_interface (u8 *s, va_list *args);
+u8 *format_npol_policy (u8 *s, va_list *args);
+u8 *format_npol_ipset (u8 *s, va_list *args);
+u8 *format_npol_rule (u8 *s, va_list *args);
+uword unformat_npol_ipset_member (unformat_input_t *input, va_list *args);
+uword unformat_npol_rule_entry (unformat_input_t *input, va_list *args);
+uword unformat_npol_rule_action (unformat_input_t *input, va_list *args);
+uword unformat_npol_rule_filter (unformat_input_t *input, va_list *args);
+u8 *format_npol_rule_filter (u8 *s, va_list *args);
+u8 *format_npol_action (u8 *s, va_list *args);
+
+#endif
\ No newline at end of file
--- /dev/null
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright(c) 2025 Cisco Systems, Inc.
+ */
+
+#include <npol/npol.h>
+#include <npol/npol_match.h>
+#include <npol/npol_policy.h>
+#include <npol/npol_format.h>
+
+uword unformat_sw_if_index (unformat_input_t *input, va_list *args);
+
+npol_interface_config_t *npol_interface_configs;
+
+int
+npol_unconfigure_policies (u32 sw_if_index)
+{
+ npol_interface_config_t *conf;
+ conf = vec_elt_at_index (npol_interface_configs, sw_if_index);
+ if (!conf->enabled)
+ return 0;
+
+ conf = vec_elt_at_index (npol_interface_configs, sw_if_index);
+ vec_free (conf->rx_policies);
+ vec_free (conf->tx_policies);
+ vec_free (conf->profiles);
+
+ conf->enabled = 0;
+ return 0;
+}
+
+int
+npol_configure_policies (u32 sw_if_index, npol_interface_config_t *new_conf)
+{
+ npol_interface_config_t *conf;
+ u32 *idx;
+
+ if (!vnet_sw_interface_is_valid (vnet_get_main (), sw_if_index))
+ return VNET_API_ERROR_INVALID_SW_IF_INDEX;
+
+ vec_validate_init_empty (npol_interface_configs, sw_if_index,
+ (npol_interface_config_t){ 0 });
+ conf = vec_elt_at_index (npol_interface_configs, sw_if_index);
+
+ vec_foreach (idx, new_conf->rx_policies)
+ if (pool_is_free_index (npol_policies, *idx))
+ goto error;
+ vec_foreach (idx, new_conf->tx_policies)
+ if (pool_is_free_index (npol_policies, *idx))
+ goto error;
+ vec_foreach (idx, new_conf->profiles)
+ if (pool_is_free_index (npol_policies, *idx))
+ goto error;
+
+ if (conf->enabled)
+ {
+ vec_free (conf->rx_policies);
+ vec_free (conf->tx_policies);
+ vec_free (conf->profiles);
+ }
+ *conf = *new_conf;
+ conf->enabled = 1;
+ return 0;
+
+error:
+ vec_free (new_conf->rx_policies);
+ vec_free (new_conf->tx_policies);
+ vec_free (new_conf->profiles);
+ return 1;
+}
+
+static clib_error_t *
+npol_sw_interface_add_del (vnet_main_t *vnm, u32 sw_if_index, u32 is_add)
+{
+ int rv;
+ if (is_add)
+ vec_validate_init_empty (npol_interface_configs, sw_if_index,
+ (npol_interface_config_t){ 0 });
+ else
+ {
+ rv = npol_unconfigure_policies (sw_if_index);
+ if (rv)
+ return clib_error_return (
+ 0, "Error calling npol_unconfigure_policies %d", rv);
+ }
+ return NULL;
+}
+
+VNET_SW_INTERFACE_ADD_DEL_FUNCTION (npol_sw_interface_add_del);
+
+static clib_error_t *
+npol_interface_show_cmd_fn (vlib_main_t *vm, unformat_input_t *input,
+ vlib_cli_command_t *cmd)
+{
+ u32 sw_if_index;
+ npol_interface_config_t *conf;
+ vlib_cli_output (vm, "Interfaces with policies configured:");
+ vec_foreach_index (sw_if_index, npol_interface_configs)
+ {
+ conf = &npol_interface_configs[sw_if_index];
+ if (conf->enabled)
+ {
+ vlib_cli_output (vm, "%U", format_npol_interface, sw_if_index, conf);
+ }
+ }
+ return NULL;
+}
+
+VLIB_CLI_COMMAND (npol_policies_show_cmd, static) = {
+ .path = "show npol interfaces",
+ .function = npol_interface_show_cmd_fn,
+ .short_help = "show npol interfaces",
+};
+
+static clib_error_t *
+npol_interface_clear_cmd_fn (vlib_main_t *vm, unformat_input_t *input,
+ vlib_cli_command_t *cmd)
+{
+ u32 sw_if_index = NPOL_INVALID_INDEX;
+ clib_error_t *error = 0;
+ int rv;
+
+ while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
+ {
+ if (unformat (input, "%U", unformat_sw_if_index, NULL, &sw_if_index))
+ ;
+ else if (unformat (input, "sw_if_index %d", &sw_if_index))
+ ;
+ else
+ {
+ error = clib_error_return (0, "unknown input '%U'",
+ format_unformat_error, input);
+ goto done;
+ }
+ }
+
+ if (sw_if_index == NPOL_INVALID_INDEX)
+ {
+ error = clib_error_return (0, "interface not specified");
+ goto done;
+ }
+
+ rv = npol_unconfigure_policies (sw_if_index);
+ if (rv)
+ error =
+ clib_error_return (0, "npol_unconfigure_policies errored with %d", rv);
+ else
+ vlib_cli_output (vm, "npol interface %d cleared", sw_if_index);
+
+done:
+ return error;
+}
+
+VLIB_CLI_COMMAND (npol_interface_clear_cmd, static) = {
+ .path = "npol interface clear",
+ .function = npol_interface_clear_cmd_fn,
+ .short_help = "npol interface clear [interface | sw_if_index N]",
+};
+
+static clib_error_t *
+npol_interface_configure_cmd_fn (vlib_main_t *vm, unformat_input_t *input,
+ vlib_cli_command_t *cmd)
+{
+ npol_interface_config_t _conf = { 0 }, *conf = &_conf;
+ clib_error_t *error = 0;
+ u32 sw_if_index = NPOL_INVALID_INDEX;
+ u32 tmp;
+ int rv;
+
+ while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
+ {
+ if (unformat (input, "%U", unformat_sw_if_index, NULL, &sw_if_index))
+ ;
+ else if (unformat (input, "sw_if_index %d", &sw_if_index))
+ ;
+ else if (unformat (input, "rx %d", &tmp))
+ vec_add1 (conf->rx_policies, tmp);
+ else if (unformat (input, "tx %d", &tmp))
+ vec_add1 (conf->tx_policies, tmp);
+ else if (unformat (input, "profiles %d", &tmp))
+ vec_add1 (conf->profiles, tmp);
+ else if (unformat (input, "rx-policy-def %d", &tmp))
+ conf->policy_default_rx = tmp;
+ else if (unformat (input, "rx-profile-def %d", &tmp))
+ conf->profile_default_rx = tmp;
+ else if (unformat (input, "tx-policy-def %d", &tmp))
+ conf->policy_default_tx = tmp;
+ else if (unformat (input, "tx-profile-def %d", &tmp))
+ conf->profile_default_tx = tmp;
+ else if (unformat (input, "invert"))
+ conf->invert_rx_tx = 1;
+ else
+ {
+ error = clib_error_return (0, "unknown input '%U'",
+ format_unformat_error, input);
+ goto done;
+ }
+ }
+
+ if (sw_if_index == NPOL_INVALID_INDEX)
+ {
+ error = clib_error_return (0, "interface not specified");
+ goto done;
+ }
+
+ rv = npol_configure_policies (sw_if_index, conf);
+ if (rv)
+ error =
+ clib_error_return (0, "npol_configure_policies errored with %d", rv);
+ else
+ vlib_cli_output (vm, "npol interface %d configured", sw_if_index);
+
+done:
+ return error;
+}
+
+VLIB_CLI_COMMAND (npol_interface_configure_cmd, static) = {
+ .path = "npol interface configure",
+ .function = npol_interface_configure_cmd_fn,
+ .short_help = "npol interface configure [interface | sw_if_index N] rx "
+ "<num_rx> tx <num_tx> rx-policy-def <rx-policy-def> "
+ "tx-policy-def <tx-policy-def> "
+ "rx-profile-def <rx-profile-def> tx-profile-def "
+ "<tx-profile-def> [invert] <policy_id> ...",
+};
--- /dev/null
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright(c) 2025 Cisco Systems, Inc.
+ */
+
+#ifndef included_npol_interface_h
+#define included_npol_interface_h
+
+#include <vppinfra/clib.h>
+
+typedef struct
+{
+ /*
+ * vec of policies indexes to apply on rx
+ */
+ u32 *rx_policies;
+ /*
+ * vec of policies indexes to apply on tx
+ */
+ u32 *tx_policies;
+ /*
+ *vec of policies indexes to use as profiles
+ */
+ u32 *profiles;
+ /* set to 1 when policy is used for interface
+ * as policy confs are stored in a sw_if_index
+ * indexed vector, initialized to zero
+ */
+ u8 enabled;
+ /*
+ * Should we invert RX and TX
+ */
+ u8 invert_rx_tx;
+ /*
+ * Default action to apply after all policies on RX
+ */
+ u8 policy_default_rx;
+ /*
+ * Default action to apply after all policies on TX
+ */
+ u8 policy_default_tx;
+ /*
+ * Default action to apply after profiles on RX
+ */
+ u8 profile_default_rx;
+ /*
+ * Default action to apply after profiles on TX
+ */
+ u8 profile_default_tx;
+} npol_interface_config_t;
+
+extern npol_interface_config_t *npol_interface_configs;
+
+int npol_configure_policies (u32 sw_if_index, npol_interface_config_t *conf);
+
+#endif
--- /dev/null
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright(c) 2025 Cisco Systems, Inc.
+ */
+
+#include <npol/npol.h>
+#include <npol/npol_ipset.h>
+#include <npol/npol_format.h>
+
+npol_ipset_t *npol_ipsets;
+
+npol_ipset_t *
+npol_ipsets_get_if_exists (u32 index)
+{
+ if (pool_is_free_index (npol_ipsets, index))
+ return (NULL);
+ return pool_elt_at_index (npol_ipsets, index);
+}
+
+u32
+npol_ipset_create (npol_ipset_type_t type)
+{
+ npol_ipset_t *ipset;
+ pool_get (npol_ipsets, ipset);
+ ipset->type = type;
+ ipset->members = NULL;
+ return ipset - npol_ipsets;
+}
+
+int
+npol_ipset_delete (u32 id)
+{
+ npol_ipset_t *ipset;
+ ipset = npol_ipsets_get_if_exists (id);
+ if (NULL == ipset)
+ return VNET_API_ERROR_NO_SUCH_ENTRY;
+
+ pool_free (ipset->members);
+ pool_put (npol_ipsets, ipset);
+ return 0;
+}
+
+int
+npol_ipset_get_type (u32 id, npol_ipset_type_t *type)
+{
+ npol_ipset_t *ipset;
+ ipset = npol_ipsets_get_if_exists (id);
+ if (NULL == ipset)
+ return VNET_API_ERROR_NO_SUCH_ENTRY;
+
+ *type = ipset->type;
+ return 0;
+}
+
+int
+npol_ipset_add_member (u32 ipset_id, npol_ipset_member_t *member)
+{
+ npol_ipset_member_t *m;
+ npol_ipset_t *ipset = &npol_ipsets[ipset_id];
+
+ if (pool_is_free (npol_ipsets, ipset))
+ {
+ return 1;
+ }
+
+ /* zero so that we can memcmp later */
+ pool_get_zero (ipset->members, m);
+ clib_memcpy (m, member, sizeof (*m));
+ return 0;
+}
+
+static size_t
+npol_ipset_member_cmp (npol_ipset_member_t *m1, npol_ipset_member_t *m2,
+ npol_ipset_type_t type)
+{
+ switch (type)
+ {
+ case IPSET_TYPE_IP:
+ return ip_address_cmp (&m1->address, &m2->address);
+ case IPSET_TYPE_IPPORT:
+ return ((m1->ipport.port == m2->ipport.port) &&
+ (m1->ipport.l4proto == m2->ipport.l4proto) &&
+ ip_address_cmp (&m1->ipport.addr, &m2->ipport.addr));
+ case IPSET_TYPE_NET:
+ return ip_prefix_cmp (&m1->prefix, &m2->prefix);
+ default:
+ return 1;
+ }
+}
+
+int
+npol_ipset_del_member (u32 id, npol_ipset_member_t *member)
+{
+ index_t *index, *indexes = NULL;
+ npol_ipset_member_t *m;
+ npol_ipset_t *ipset;
+
+ ipset = npol_ipsets_get_if_exists (id);
+ if (NULL == ipset)
+ return VNET_API_ERROR_NO_SUCH_ENTRY;
+
+ pool_foreach (m, ipset->members)
+ {
+ if (!npol_ipset_member_cmp (m, member, ipset->type))
+ vec_add1 (indexes, m - ipset->members);
+ }
+
+ vec_foreach (index, indexes)
+ pool_put_index (ipset->members, *index);
+ vec_free (indexes);
+
+ return 0;
+}
+
+static clib_error_t *
+npol_ipsets_show_cmd_fn (vlib_main_t *vm, unformat_input_t *input,
+ vlib_cli_command_t *cmd)
+{
+ npol_ipset_t *ipset;
+
+ pool_foreach (ipset, npol_ipsets)
+ vlib_cli_output (vm, "%U", format_npol_ipset, ipset);
+
+ return 0;
+}
+
+VLIB_CLI_COMMAND (npol_ipsets_show_cmd, static) = {
+ .path = "show npol ipsets",
+ .function = npol_ipsets_show_cmd_fn,
+ .short_help = "show npol ipsets",
+};
+
+static clib_error_t *
+npol_ipsets_add_cmd_fn (vlib_main_t *vm, unformat_input_t *input,
+ vlib_cli_command_t *cmd)
+{
+ npol_ipset_member_t tmp, *members = 0, *member;
+ clib_error_t *error = 0;
+ npol_ipset_type_t type;
+ npol_ipset_t *ipset;
+ u32 id;
+ int rv;
+
+ id = npol_ipset_create ((npol_ipset_type_t) ~0);
+ vlib_cli_output (vm, "npol ipset %d added", id);
+
+ while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
+ {
+ if (unformat (input, "%U", unformat_npol_ipset_member, &tmp, &type))
+ vec_add1 (members, tmp);
+ else
+ {
+ error = clib_error_return (0, "unknown input '%U'",
+ format_unformat_error, input);
+ goto done;
+ }
+ }
+
+ ipset = pool_elt_at_index (npol_ipsets, id);
+ ipset->type = type;
+
+ vec_foreach (member, members)
+ {
+ rv = npol_ipset_add_member (id, member);
+ if (rv)
+ error = clib_error_return (0, "npol_ipset_add_member error %d", rv);
+ }
+
+done:
+ vec_free (members);
+ return error;
+}
+
+VLIB_CLI_COMMAND (npol_ipsets_add_cmd, static) = {
+ .path = "npol ipset add",
+ .function = npol_ipsets_add_cmd_fn,
+ .short_help = "npol ipset add [prefix|proto ip port|ip]",
+};
+
+static clib_error_t *
+npol_ipsets_del_cmd_fn (vlib_main_t *vm, unformat_input_t *input,
+ vlib_cli_command_t *cmd)
+{
+ clib_error_t *error = 0;
+ u32 id = NPOL_INVALID_INDEX;
+ int rv;
+
+ while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
+ {
+ if (unformat (input, "%u", &id))
+ ;
+ else
+ {
+ error = clib_error_return (0, "unknown input '%U'",
+ format_unformat_error, input);
+ goto done;
+ }
+ }
+
+ if (NPOL_INVALID_INDEX == id)
+ {
+ error = clib_error_return (0, "missing ipset id");
+ goto done;
+ }
+
+ rv = npol_ipset_delete (id);
+ if (rv)
+ error = clib_error_return (0, "npol_ipset_delete errored with %d", rv);
+
+done:
+ return error;
+}
+
+VLIB_CLI_COMMAND (npol_ipsets_del_cmd, static) = {
+ .path = "npol ipset del",
+ .function = npol_ipsets_del_cmd_fn,
+ .short_help = "npol ipset del [id]",
+};
+
+static clib_error_t *
+npol_ipsets_add_member_cmd_fn (vlib_main_t *vm, unformat_input_t *input,
+ vlib_cli_command_t *cmd)
+{
+ npol_ipset_member_t tmp, *members = 0, *member;
+ u32 id = NPOL_INVALID_INDEX;
+ clib_error_t *error = 0;
+ npol_ipset_type_t type;
+ npol_ipset_t *ipset;
+ int rv;
+
+ while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
+ {
+ if (unformat (input, "id %u", &id))
+ ;
+ else if (unformat (input, "%U", unformat_npol_ipset_member, &tmp, &type))
+ vec_add1 (members, tmp);
+ else
+ {
+ error = clib_error_return (0, "unknown input '%U'",
+ format_unformat_error, input);
+ goto done;
+ }
+ }
+
+ if (NPOL_INVALID_INDEX == id)
+ {
+ error = clib_error_return (0, "missing ipset id");
+ goto done;
+ }
+
+ ipset = npol_ipsets_get_if_exists (id);
+ if (NULL == ipset)
+ return clib_error_return (0, "ipset not found");
+ if (ipset->type != type && ~0 != ipset->type)
+ {
+ error = clib_error_return (0, "cannot change ipset type");
+ goto done;
+ }
+ ipset->type = type;
+
+ vec_foreach (member, members)
+ {
+ rv = npol_ipset_add_member (id, member);
+ if (rv)
+ error = clib_error_return (0, "npol_ipset_add_member error %d", rv);
+ }
+
+done:
+ vec_free (members);
+ return error;
+}
+
+VLIB_CLI_COMMAND (npol_ipsets_add_member_cmd, static) = {
+ .path = "npol ipset add member",
+ .function = npol_ipsets_add_member_cmd_fn,
+ .short_help = "npol ipset add member [id] [prefix]",
+};
+
+static clib_error_t *
+npol_ipsets_del_member_cmd_fn (vlib_main_t *vm, unformat_input_t *input,
+ vlib_cli_command_t *cmd)
+{
+ clib_error_t *error = 0;
+ u32 id = NPOL_INVALID_INDEX;
+ npol_ipset_type_t type;
+ npol_ipset_member_t tmp, *members = 0, *member;
+ npol_ipset_t *ipset;
+ int rv;
+
+ while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
+ {
+ if (unformat (input, "id %u", &id))
+ ;
+ else if (unformat (input, "%U", unformat_npol_ipset_member, &tmp, &type))
+ vec_add1 (members, tmp);
+ else
+ {
+ error = clib_error_return (0, "unknown input '%U'",
+ format_unformat_error, input);
+ goto done;
+ }
+ }
+
+ if (NPOL_INVALID_INDEX == id)
+ {
+ error = clib_error_return (0, "missing ipset id");
+ goto done;
+ }
+
+ ipset = npol_ipsets_get_if_exists (id);
+ if (NULL == ipset)
+ return clib_error_return (0, "ipset not found");
+ if (ipset->type != type)
+ {
+ error = clib_error_return (0, "wrong member type");
+ goto done;
+ }
+
+ vec_foreach (member, members)
+ {
+ rv = npol_ipset_del_member (id, member);
+ if (rv)
+ error =
+ clib_error_return (0, "npol_ipset_del_member errored with %d", rv);
+ }
+
+done:
+ vec_free (members);
+ return error;
+}
+
+VLIB_CLI_COMMAND (npol_ipsets_del_member_cmd, static) = {
+ .path = "npol ipset del member",
+ .function = npol_ipsets_del_member_cmd_fn,
+ .short_help = "npol ipset del member [id] [prefix]",
+};
--- /dev/null
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright(c) 2025 Cisco Systems, Inc.
+ */
+
+#ifndef included_npol_ipset_h
+#define included_npol_ipset_h
+
+#include <npol/npol.h>
+
+typedef enum
+{
+ IPSET_TYPE_IP = 0,
+ IPSET_TYPE_IPPORT = 1,
+ IPSET_TYPE_NET = 2
+} npol_ipset_type_t;
+
+typedef struct
+{
+ ip_address_t addr;
+ u16 port;
+ u8 l4proto;
+} npol_ipport_t;
+
+typedef union
+{
+ ip_address_t address;
+ npol_ipport_t ipport;
+ ip_prefix_t prefix;
+} npol_ipset_member_t;
+
+typedef struct
+{
+ npol_ipset_type_t type;
+ npol_ipset_member_t *members;
+} npol_ipset_t;
+
+u32 npol_ipset_create (npol_ipset_type_t type);
+int npol_ipset_delete (u32 id);
+
+int npol_ipset_add_member (u32 ipset_id, npol_ipset_member_t *member);
+int npol_ipset_del_member (u32 ipset_id, npol_ipset_member_t *member);
+
+int npol_ipset_get_type (u32 id, npol_ipset_type_t *type);
+npol_ipset_t *npol_ipsets_get_if_exists (u32 index);
+
+extern npol_ipset_t *npol_ipsets;
+
+#endif
--- /dev/null
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright(c) 2025 Cisco Systems, Inc.
+ */
+
+#include <vnet/ip/ip.h>
+
+#include <npol/npol.h>
+#include <npol/npol_match.h>
+
+always_inline u8
+ip_ipset_contains_ip4 (npol_ipset_t *ipset, ip4_address_t *addr)
+{
+ ASSERT (ipset->type == IPSET_TYPE_IP);
+ npol_ipset_member_t *member;
+ pool_foreach (member, ipset->members)
+ {
+ if (member->address.version != AF_IP4)
+ continue;
+ if (!ip4_address_compare (addr, &ip_addr_v4 (&member->address)))
+ return 1;
+ }
+ return 0;
+}
+
+always_inline u8
+ip_ipset_contains_ip6 (npol_ipset_t *ipset, ip6_address_t *addr)
+{
+ ASSERT (ipset->type == IPSET_TYPE_IP);
+ npol_ipset_member_t *member;
+ pool_foreach (member, ipset->members)
+ {
+ if (member->address.version != AF_IP6)
+ continue;
+ if (!ip6_address_compare (addr, &ip_addr_v6 (&member->address)))
+ return 1;
+ }
+ return 0;
+}
+
+always_inline u8
+net_ipset_contains_ip4 (npol_ipset_t *ipset, ip4_address_t *addr)
+{
+ ASSERT (ipset->type == IPSET_TYPE_NET);
+ npol_ipset_member_t *member;
+ pool_foreach (member, ipset->members)
+ {
+ if (member->prefix.addr.version != AF_IP4)
+ continue;
+ if (ip4_destination_matches_route (&ip4_main, addr,
+ &ip_addr_v4 (&member->prefix.addr),
+ member->prefix.len))
+ {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+always_inline u8
+net_ipset_contains_ip6 (npol_ipset_t *ipset, ip6_address_t *addr)
+{
+ ASSERT (ipset->type == IPSET_TYPE_NET);
+ npol_ipset_member_t *member;
+ pool_foreach (member, ipset->members)
+ {
+ if (member->prefix.addr.version != AF_IP6)
+ continue;
+ if (ip6_destination_matches_route (&ip6_main, addr,
+ &ip_addr_v6 (&member->prefix.addr),
+ member->prefix.len))
+ {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+always_inline u8
+ipset_contains_ip4 (npol_ipset_t *ipset, ip4_address_t *addr)
+{
+ switch (ipset->type)
+ {
+ case IPSET_TYPE_IP:
+ return ip_ipset_contains_ip4 (ipset, addr);
+ case IPSET_TYPE_NET:
+ return net_ipset_contains_ip4 (ipset, addr);
+ default:
+ clib_warning ("Wrong ipset type");
+ }
+ return 0;
+}
+
+always_inline u8
+ipset_contains_ip6 (npol_ipset_t *ipset, ip6_address_t *addr)
+{
+ switch (ipset->type)
+ {
+ case IPSET_TYPE_IP:
+ return ip_ipset_contains_ip6 (ipset, addr);
+ case IPSET_TYPE_NET:
+ return net_ipset_contains_ip6 (ipset, addr);
+ default:
+ clib_warning ("Wrong ipset type");
+ }
+ return 0;
+}
+
+always_inline u8
+ipport_ipset_contains_ip4 (npol_ipset_t *ipset, ip4_address_t *addr,
+ u8 l4proto, u16 port)
+{
+ ASSERT (ipset->type == IPSET_TYPE_IPPORT);
+ npol_ipset_member_t *member;
+ pool_foreach (member, ipset->members)
+ {
+ if (member->ipport.addr.version != AF_IP4)
+ continue;
+ if (l4proto == member->ipport.l4proto && port == member->ipport.port &&
+ !ip4_address_compare (addr, &ip_addr_v4 (&member->ipport.addr)))
+ {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+always_inline u8
+ipport_ipset_contains_ip6 (npol_ipset_t *ipset, ip6_address_t *addr,
+ u8 l4proto, u16 port)
+{
+ ASSERT (ipset->type == IPSET_TYPE_IPPORT);
+ npol_ipset_member_t *member;
+ pool_foreach (member, ipset->members)
+ {
+ if (member->ipport.addr.version != AF_IP6)
+ continue;
+ if (l4proto == member->ipport.l4proto && port == member->ipport.port &&
+ !ip6_address_compare (addr, &ip_addr_v6 (&member->ipport.addr)))
+ {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+always_inline int
+npol_match_rule (npol_rule_t *rule, u32 is_ip6, fa_5tuple_t *pkt_5tuple)
+{
+ ip4_address_t *src_ip4 = &pkt_5tuple->ip4_addr[SRC];
+ ip4_address_t *dst_ip4 = &pkt_5tuple->ip4_addr[DST];
+ ip6_address_t *src_ip6 = &pkt_5tuple->ip6_addr[SRC];
+ ip6_address_t *dst_ip6 = &pkt_5tuple->ip6_addr[DST];
+ u8 l4proto = pkt_5tuple->l4.proto;
+ u16 src_port = pkt_5tuple->l4.port[SRC];
+ u16 dst_port = pkt_5tuple->l4.port[DST];
+ u16 type = pkt_5tuple->l4.port[0];
+ u16 code = pkt_5tuple->l4.port[1];
+
+ npol_rule_filter_t *filter;
+ vec_foreach (filter, rule->filters)
+ {
+ switch (filter->type)
+ {
+ case NPOL_RULE_FILTER_NONE_TYPE:
+ break;
+ case NPOL_RULE_FILTER_L4_PROTO:
+ if (filter->should_match && filter->value != l4proto)
+ return -1;
+ if (!filter->should_match && filter->value == l4proto)
+ return -2;
+ break;
+ case NPOL_RULE_FILTER_ICMP_TYPE:
+ if (l4proto == IP_PROTOCOL_ICMP || l4proto == IP_PROTOCOL_ICMP6)
+ {
+ if (filter->should_match && filter->value != type)
+ return -3;
+ if (!filter->should_match && filter->value == type)
+ return -4;
+ }
+ else
+ /* A rule with an ICMP type / code specified doesn't match a
+ * non-icmp packet
+ */
+ return -5;
+ break;
+ case NPOL_RULE_FILTER_ICMP_CODE:
+ if (l4proto == IP_PROTOCOL_ICMP || l4proto == IP_PROTOCOL_ICMP6)
+ {
+ if (filter->should_match && filter->value != code)
+ return -6;
+ if (!filter->should_match && filter->value == code)
+ return -7;
+ }
+ else
+ /* A rule with an ICMP type / code specified doesn't match a
+ * non-icmp packet
+ */
+ return -8;
+ break;
+ default:
+ break;
+ }
+ }
+
+ /* prefixes */
+ if (rule->prefixes[NPOL_SRC])
+ {
+ ip_prefix_t *prefix;
+ u8 found = 0;
+ vec_foreach (prefix, rule->prefixes[NPOL_SRC])
+ {
+ u8 pfx_af = ip_prefix_version (prefix);
+ if (is_ip6 && pfx_af == AF_IP6)
+ {
+ if (ip6_destination_matches_route (&ip6_main, src_ip6,
+ &ip_addr_v6 (&prefix->addr),
+ prefix->len))
+ {
+ found = 1;
+ break;
+ }
+ }
+ else if (!is_ip6 && pfx_af == AF_IP4)
+ {
+ if (ip4_destination_matches_route (&ip4_main, src_ip4,
+ &ip_addr_v4 (&prefix->addr),
+ prefix->len))
+ {
+ found = 1;
+ break;
+ }
+ }
+ }
+ if (!found)
+ {
+ return -9;
+ }
+ }
+
+ if (rule->prefixes[NPOL_NOT_SRC])
+ {
+ ip_prefix_t *prefix;
+ vec_foreach (prefix, rule->prefixes[NPOL_NOT_SRC])
+ {
+ u8 pfx_af = ip_prefix_version (prefix);
+ if (is_ip6 && pfx_af == AF_IP6)
+ {
+ if (ip6_destination_matches_route (&ip6_main, src_ip6,
+ &ip_addr_v6 (&prefix->addr),
+ prefix->len))
+ {
+ return -10;
+ }
+ }
+ else if (!is_ip6 && pfx_af == AF_IP4)
+ {
+ if (ip4_destination_matches_route (&ip4_main, src_ip4,
+ &ip_addr_v4 (&prefix->addr),
+ prefix->len))
+ {
+ return -11;
+ }
+ }
+ }
+ }
+
+ if (rule->prefixes[NPOL_DST])
+ {
+ ip_prefix_t *prefix;
+ u8 found = 0;
+ vec_foreach (prefix, rule->prefixes[NPOL_DST])
+ {
+ u8 pfx_af = ip_prefix_version (prefix);
+ if (is_ip6 && pfx_af == AF_IP6)
+ {
+ if (ip6_destination_matches_route (&ip6_main, dst_ip6,
+ &ip_addr_v6 (&prefix->addr),
+ prefix->len))
+ {
+ found = 1;
+ break;
+ }
+ }
+ else if (!is_ip6 && pfx_af == AF_IP4)
+ {
+ if (ip4_destination_matches_route (&ip4_main, dst_ip4,
+ &ip_addr_v4 (&prefix->addr),
+ prefix->len))
+ {
+ found = 1;
+ break;
+ }
+ }
+ }
+ if (!found)
+ {
+ return -12;
+ }
+ }
+
+ if (rule->prefixes[NPOL_NOT_DST])
+ {
+ ip_prefix_t *prefix;
+ vec_foreach (prefix, rule->prefixes[NPOL_NOT_DST])
+ {
+ u8 pfx_af = ip_prefix_version (prefix);
+ if (is_ip6 && pfx_af == AF_IP6)
+ {
+ if (ip6_destination_matches_route (&ip6_main, dst_ip6,
+ &ip_addr_v6 (&prefix->addr),
+ prefix->len))
+ {
+ return -13;
+ }
+ }
+ else if (!is_ip6 && pfx_af == AF_IP4)
+ {
+ if (ip4_destination_matches_route (&ip4_main, dst_ip4,
+ &ip_addr_v4 (&prefix->addr),
+ prefix->len))
+ {
+ return -14;
+ }
+ }
+ }
+ }
+
+ /* IP ipsets */
+ if (rule->ip_ipsets[NPOL_SRC])
+ {
+ u32 *ipset;
+ u8 found = 0;
+ vec_foreach (ipset, rule->ip_ipsets[NPOL_SRC])
+ {
+ if (is_ip6)
+ {
+ if (ipset_contains_ip6 (&npol_ipsets[*ipset], src_ip6))
+ {
+ found = 1;
+ break;
+ }
+ }
+ else
+ {
+ if (ipset_contains_ip4 (&npol_ipsets[*ipset], src_ip4))
+ {
+ found = 1;
+ break;
+ }
+ }
+ }
+ if (!found)
+ {
+ return -15;
+ }
+ }
+
+ if (rule->ip_ipsets[NPOL_NOT_SRC])
+ {
+ u32 *ipset;
+ vec_foreach (ipset, rule->ip_ipsets[NPOL_NOT_SRC])
+ {
+ if (is_ip6)
+ {
+ if (ipset_contains_ip6 (&npol_ipsets[*ipset], src_ip6))
+ {
+ return -16;
+ }
+ }
+ else
+ {
+ if (ipset_contains_ip4 (&npol_ipsets[*ipset], src_ip4))
+ {
+ return -17;
+ }
+ }
+ }
+ }
+
+ if (rule->ip_ipsets[NPOL_DST])
+ {
+ u32 *ipset;
+ u8 found = 0;
+ vec_foreach (ipset, rule->ip_ipsets[NPOL_DST])
+ {
+ if (is_ip6)
+ {
+ if (ipset_contains_ip6 (&npol_ipsets[*ipset], dst_ip6))
+ {
+ found = 1;
+ break;
+ }
+ }
+ else
+ {
+ if (ipset_contains_ip4 (&npol_ipsets[*ipset], dst_ip4))
+ {
+ found = 1;
+ break;
+ }
+ }
+ }
+ if (!found)
+ {
+ return -18;
+ }
+ }
+
+ if (rule->ip_ipsets[NPOL_NOT_DST])
+ {
+ u32 *ipset;
+ vec_foreach (ipset, rule->ip_ipsets[NPOL_NOT_DST])
+ {
+ if (is_ip6)
+ {
+ if (ipset_contains_ip6 (&npol_ipsets[*ipset], dst_ip6))
+ {
+ return -19;
+ }
+ }
+ else
+ {
+ if (ipset_contains_ip4 (&npol_ipsets[*ipset], dst_ip4))
+ {
+ return -20;
+ }
+ }
+ }
+ }
+
+ /* Special treatment for src / dst ports: they need to be in either the port
+ ranges or the port + ip ipsets / */
+ u8 src_port_found = 0;
+ u8 dst_port_found = 0;
+
+ /* port ranges */
+ if (rule->port_ranges[NPOL_SRC])
+ {
+ npol_port_range_t *range;
+ vec_foreach (range, rule->port_ranges[NPOL_SRC])
+ {
+ if (range->start <= src_port && src_port <= range->end)
+ {
+ src_port_found = 1;
+ break;
+ }
+ }
+ }
+
+ if (rule->port_ranges[NPOL_NOT_SRC])
+ {
+ npol_port_range_t *range;
+ vec_foreach (range, rule->port_ranges[NPOL_NOT_SRC])
+ {
+ if (range->start <= src_port && src_port <= range->end)
+ {
+ return -21;
+ }
+ }
+ }
+
+ if (rule->port_ranges[NPOL_DST])
+ {
+ npol_port_range_t *range;
+ vec_foreach (range, rule->port_ranges[NPOL_DST])
+ {
+ if (range->start <= dst_port && dst_port <= range->end)
+ {
+ dst_port_found = 1;
+ break;
+ }
+ }
+ }
+
+ if (rule->port_ranges[NPOL_NOT_DST])
+ {
+ npol_port_range_t *range;
+ vec_foreach (range, rule->port_ranges[NPOL_NOT_DST])
+ {
+ if (range->start <= dst_port && dst_port <= range->end)
+ {
+ return -22;
+ }
+ }
+ }
+
+ /* ipport ipsets */
+ if (rule->ipport_ipsets[NPOL_SRC])
+ {
+ u32 *ipset;
+ vec_foreach (ipset, rule->ipport_ipsets[NPOL_SRC])
+ {
+ if (is_ip6)
+ {
+ if (ipport_ipset_contains_ip6 (&npol_ipsets[*ipset], src_ip6,
+ l4proto, src_port))
+ {
+ src_port_found = 1;
+ break;
+ }
+ }
+ else
+ {
+ if (ipport_ipset_contains_ip4 (&npol_ipsets[*ipset], src_ip4,
+ l4proto, src_port))
+ {
+ src_port_found = 1;
+ break;
+ }
+ }
+ }
+ }
+
+ if (rule->ipport_ipsets[NPOL_NOT_SRC])
+ {
+ u32 *ipset;
+ vec_foreach (ipset, rule->ipport_ipsets[NPOL_NOT_SRC])
+ {
+ if (is_ip6)
+ {
+ if (ipport_ipset_contains_ip6 (&npol_ipsets[*ipset], src_ip6,
+ l4proto, src_port))
+ {
+ return -23;
+ }
+ }
+ else
+ {
+ if (ipport_ipset_contains_ip4 (&npol_ipsets[*ipset], src_ip4,
+ l4proto, src_port))
+ {
+ return -24;
+ }
+ }
+ }
+ }
+
+ if (rule->ipport_ipsets[NPOL_DST])
+ {
+ u32 *ipset;
+ vec_foreach (ipset, rule->ipport_ipsets[NPOL_DST])
+ {
+ if (is_ip6)
+ {
+ if (ipport_ipset_contains_ip6 (&npol_ipsets[*ipset], dst_ip6,
+ l4proto, dst_port))
+ {
+ dst_port_found = 1;
+ break;
+ }
+ }
+ else
+ {
+ if (ipport_ipset_contains_ip4 (&npol_ipsets[*ipset], dst_ip4,
+ l4proto, dst_port))
+ {
+ dst_port_found = 1;
+ break;
+ }
+ }
+ }
+ }
+
+ if (rule->ipport_ipsets[NPOL_NOT_DST])
+ {
+ u32 *ipset;
+ vec_foreach (ipset, rule->ipport_ipsets[NPOL_NOT_DST])
+ {
+ if (is_ip6)
+ {
+ if (ipport_ipset_contains_ip6 (&npol_ipsets[*ipset], dst_ip6,
+ l4proto, dst_port))
+ {
+ return -25;
+ }
+ }
+ else
+ {
+ if (ipport_ipset_contains_ip4 (&npol_ipsets[*ipset], dst_ip4,
+ l4proto, dst_port))
+ {
+ return -26;
+ }
+ }
+ }
+ }
+
+ if ((rule->port_ranges[NPOL_SRC] || rule->ipport_ipsets[NPOL_SRC]) &&
+ (!src_port_found))
+ {
+ return -27;
+ }
+ if ((rule->port_ranges[NPOL_DST] || rule->ipport_ipsets[NPOL_DST]) &&
+ (!dst_port_found))
+ {
+ return -28;
+ }
+
+ return rule->action;
+}
+
+always_inline int
+npol_match_policy (npol_policy_t *policy, u32 is_inbound, u32 is_ip6,
+ fa_5tuple_t *pkt_5tuple)
+{
+ /* packets RX/TX from VPP perspective */
+ u32 *rules =
+ is_inbound ? policy->rule_ids[VLIB_RX] : policy->rule_ids[VLIB_TX];
+ u32 *rule_id;
+ npol_rule_t *rule;
+ int r;
+
+ vec_foreach (rule_id, rules)
+ {
+ rule = &npol_rules[*rule_id];
+ r = npol_match_rule (rule, is_ip6, pkt_5tuple);
+ if (r >= 0)
+ {
+ return r;
+ }
+ }
+ return -1;
+}
+
+/*
+ * npol_match_func evalutes policies on the packet for which the 5tuple is
+ * passed This packet can be :
+ * - is_inbound = 1 : received on interface sw_if_index
+ * - is_inbound = 0 : to be txed on interface sw_if_index
+ * The function sets r_action to NPOL_ACTION_ALLOW or NPOL_ACTION_DENY
+ * It returns 1 if a rule was matched, 0 otherwise
+ */
+CLIB_MARCH_FN (npol_match, int, u32 sw_if_index, u32 is_inbound,
+ fa_5tuple_t *pkt_5tuple, int is_ip6, u8 *r_action)
+{
+ npol_interface_config_t *if_config;
+ npol_policy_t *policy;
+ u32 *policies;
+ int r;
+ u32 i;
+ u8 policy_default;
+ u8 profile_default;
+
+ if_config = vec_elt_at_index (npol_interface_configs, sw_if_index);
+ if (!if_config->enabled)
+ {
+ /* no config for this interface found, allow */
+ *r_action = NPOL_ACTION_ALLOW;
+ return 0;
+ }
+ policies = is_inbound ^ if_config->invert_rx_tx ? if_config->rx_policies :
+ if_config->tx_policies;
+ policy_default =
+ is_inbound ? if_config->policy_default_rx : if_config->policy_default_tx;
+ profile_default =
+ is_inbound ? if_config->profile_default_rx : if_config->profile_default_tx;
+
+ vec_foreach_index (i, policies)
+ {
+ policy = &npol_policies[policies[i]];
+ r = npol_match_policy (policy, is_inbound ^ if_config->invert_rx_tx,
+ is_ip6, pkt_5tuple);
+ switch (r)
+ {
+ case NPOL_ALLOW:
+ *r_action = NPOL_ACTION_ALLOW; /* allow */
+ return 1;
+ case NPOL_DENY:
+ *r_action = NPOL_ACTION_DENY;
+ return 1;
+ case NPOL_PASS:
+ goto profiles;
+ case NPOL_LOG:
+ /* TODO: support LOG action */
+ break;
+ default:
+ break;
+ }
+ };
+ /* nothing matched, or no policies */
+ switch (policy_default)
+ {
+ case NPOL_ALLOW:
+ *r_action = NPOL_ACTION_ALLOW;
+ return 1;
+ case NPOL_DEFAULT_DENY:
+ *r_action = NPOL_ACTION_DENY;
+ return 1;
+ case NPOL_DEFAULT_PASS:
+ goto profiles;
+ default:
+ break;
+ }
+
+profiles:
+
+ vec_foreach_index (i, if_config->profiles)
+ {
+ policy = &npol_policies[if_config->profiles[i]];
+ r = npol_match_policy (policy, is_inbound ^ if_config->invert_rx_tx,
+ is_ip6, pkt_5tuple);
+ switch (r)
+ {
+ case NPOL_ALLOW:
+ *r_action = NPOL_ACTION_ALLOW;
+ return 1;
+ case NPOL_DENY:
+ *r_action = NPOL_ACTION_DENY;
+ return 1;
+ case NPOL_PASS:
+ clib_warning ("error: pass in profile %u", if_config->profiles[i]);
+ return 1;
+ case NPOL_LOG:
+ /* TODO: support LOG action */
+ break;
+ default:
+ break;
+ }
+ };
+
+ /* nothing matched, or no profiles */
+ switch (profile_default)
+ {
+ case NPOL_ALLOW:
+ *r_action = NPOL_ACTION_ALLOW;
+ return 1;
+ case NPOL_DEFAULT_DENY:
+ *r_action = NPOL_ACTION_DENY;
+ return 1;
+ case NPOL_DEFAULT_PASS:
+ clib_warning ("error: default pass in profile %u",
+ if_config->profiles[i]);
+ return 1;
+ default:
+ break;
+ }
+ return 1;
+}
+
+#ifndef CLIB_MARCH_VARIANT
+int
+npol_match_func (u32 sw_if_index, u32 is_inbound, fa_5tuple_t *pkt_5tuple,
+ int is_ip6, u8 *r_action)
+{
+ return CLIB_MARCH_FN_SELECT (npol_match) (sw_if_index, is_inbound,
+ pkt_5tuple, is_ip6, r_action);
+}
+
+#endif /* CLIB_MARCH_VARIANT */
--- /dev/null
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright(c) 2025 Cisco Systems, Inc.
+ */
+
+#ifndef included_npol_match_h
+#define included_npol_match_h
+
+#include <acl/acl.h>
+#include <acl/fa_node.h>
+
+#include <npol/npol_ipset.h>
+#include <npol/npol_policy.h>
+#include <npol/npol_rule.h>
+
+int npol_match_func (u32 sw_if_index, u32 is_inbound, fa_5tuple_t *pkt_5tuple,
+ int is_ip6, u8 *r_action);
+
+#endif
--- /dev/null
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright(c) 2025 Cisco Systems, Inc.
+ */
+
+#include <npol/npol.h>
+#include <npol/npol_policy.h>
+#include <npol/npol_rule.h>
+#include <npol/npol_format.h>
+
+npol_policy_t *npol_policies;
+
+static npol_policy_t *
+npol_policy_alloc ()
+{
+ npol_policy_t *policy;
+ pool_get_zero (npol_policies, policy);
+ return policy;
+}
+
+npol_policy_t *
+npol_policy_get_if_exists (u32 index)
+{
+ if (pool_is_free_index (npol_policies, index))
+ return (NULL);
+ return pool_elt_at_index (npol_policies, index);
+}
+
+static void
+npol_policy_cleanup (npol_policy_t *policy)
+{
+ for (int i = 0; i < VLIB_N_RX_TX; i++)
+ vec_free (policy->rule_ids[i]);
+}
+
+int
+npol_policy_update (u32 *id, npol_policy_rule_t *rules)
+{
+ npol_policy_t *policy;
+ npol_policy_rule_t *rule;
+
+ policy = npol_policy_get_if_exists (*id);
+ if (policy)
+ npol_policy_cleanup (policy);
+ else
+ policy = npol_policy_alloc ();
+
+ vec_foreach (rule, rules)
+ vec_add1 (policy->rule_ids[rule->direction], rule->rule_id);
+
+ *id = policy - npol_policies;
+ return 0;
+}
+
+int
+npol_policy_delete (u32 id)
+{
+ npol_policy_t *policy;
+ policy = npol_policy_get_if_exists (id);
+ if (NULL == policy)
+ return VNET_API_ERROR_NO_SUCH_ENTRY;
+
+ npol_policy_cleanup (policy);
+ pool_put (npol_policies, policy);
+
+ return 0;
+}
+
+static clib_error_t *
+npol_policies_show_cmd_fn (vlib_main_t *vm, unformat_input_t *input,
+ vlib_cli_command_t *cmd)
+{
+ clib_error_t *error = 0;
+ npol_policy_t *policy;
+ u8 verbose = 0;
+
+ while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
+ {
+ if (unformat (input, "verbose"))
+ verbose = 1;
+ else
+ {
+ error = clib_error_return (0, "unknown input '%U'",
+ format_unformat_error, input);
+ goto done;
+ }
+ }
+
+ pool_foreach (policy, npol_policies)
+ vlib_cli_output (vm, "%U", format_npol_policy, policy, 0, /* indent */
+ verbose, 0 /* invert_rx_tx */);
+
+done:
+ return error;
+}
+
+VLIB_CLI_COMMAND (npol_policies_show_cmd, static) = {
+ .path = "show npol policies",
+ .function = npol_policies_show_cmd_fn,
+ .short_help = "show npol policies [verbose]",
+};
+
+static clib_error_t *
+npol_policies_add_cmd_fn (vlib_main_t *vm, unformat_input_t *input,
+ vlib_cli_command_t *cmd)
+{
+ clib_error_t *error = 0;
+ u32 id = NPOL_INVALID_INDEX, rule_id;
+ npol_policy_rule_t *policy_rules = 0, *policy_rule;
+ int direction = VLIB_RX;
+ int rv;
+
+ while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
+ {
+ if (unformat (input, "update %u", &id))
+ ;
+ else if (unformat (input, "%U", unformat_vlib_rx_tx, &direction))
+ ;
+ else if (unformat (input, "%u", &rule_id))
+ {
+ vec_add2 (policy_rules, policy_rule, 1);
+ policy_rule->rule_id = rule_id;
+ policy_rule->direction = direction;
+ }
+ else
+ {
+ error = clib_error_return (0, "unknown input '%U'",
+ format_unformat_error, input);
+ goto done;
+ }
+ }
+
+ rv = npol_policy_update (&id, policy_rules);
+ if (rv)
+ error = clib_error_return (0, "npol_policy_delete errored with %d", rv);
+ else
+ vlib_cli_output (vm, "npol policy %d added", id);
+
+done:
+ vec_free (policy_rules);
+ return error;
+}
+
+VLIB_CLI_COMMAND (npol_policies_add_cmd, static) = {
+ .path = "npol policy add",
+ .function = npol_policies_add_cmd_fn,
+ .short_help = "npol policy add [rx rule_id rule_id ...] [tx rule_id rule_id "
+ "...] [update [id]]",
+};
+
+static clib_error_t *
+npol_policies_del_cmd_fn (vlib_main_t *vm, unformat_input_t *input,
+ vlib_cli_command_t *cmd)
+{
+ clib_error_t *error = 0;
+ u32 id = NPOL_INVALID_INDEX;
+ int rv;
+
+ while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
+ {
+ if (unformat (input, "%u", &id))
+ ;
+ else
+ {
+ error = clib_error_return (0, "unknown input '%U'",
+ format_unformat_error, input);
+ goto done;
+ }
+ }
+
+ if (NPOL_INVALID_INDEX == id)
+ {
+ error = clib_error_return (0, "missing policy id");
+ goto done;
+ }
+
+ rv = npol_policy_delete (id);
+ if (rv)
+ error = clib_error_return (0, "npol_policy_delete errored with %d", rv);
+
+done:
+ return error;
+}
+
+VLIB_CLI_COMMAND (npol_policies_del_cmd, static) = {
+ .path = "npol policy del",
+ .function = npol_policies_del_cmd_fn,
+ .short_help = "npol policy del [id]",
+};
--- /dev/null
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright(c) 2025 Cisco Systems, Inc.
+ */
+
+#ifndef included_npol_policy_h
+#define included_npol_policy_h
+
+#include <npol/npol.h>
+
+typedef struct
+{
+ /* VLIB_RX for inbound
+ VLIB_TX for outbound */
+ u32 *rule_ids[VLIB_N_RX_TX];
+} npol_policy_t;
+
+typedef struct
+{
+ u32 rule_id;
+ /* VLIB_RX or VLIB_TX */
+ u8 direction;
+} npol_policy_rule_t;
+
+typedef enum
+{
+ NPOL_POLICY_QUIET,
+ NPOL_POLICY_VERBOSE,
+ NPOL_POLICY_ONLY_RX,
+ NPOL_POLICY_ONLY_TX,
+} npol_policy_format_type_t;
+
+extern npol_policy_t *npol_policies;
+
+int npol_policy_update (u32 *id, npol_policy_rule_t *rules);
+int npol_policy_delete (u32 id);
+npol_policy_t *npol_policy_get_if_exists (u32 index);
+
+#endif
--- /dev/null
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright(c) 2025 Cisco Systems, Inc.
+ */
+
+#include <npol/npol.h>
+#include <npol/npol_rule.h>
+#include <npol/npol_ipset.h>
+#include <npol/npol_format.h>
+
+npol_rule_t *npol_rules;
+
+npol_rule_entry_t *
+npol_rule_get_entries (npol_rule_t *rule)
+{
+ npol_rule_entry_t *entries = NULL, *entry;
+ npol_port_range_t *pr;
+ ip_prefix_t *pfx;
+ u32 *set_id;
+ for (int i = 0; i < NPOL_RULE_MAX_FLAGS; i++)
+ {
+ vec_foreach (pfx, rule->prefixes[i])
+ {
+ vec_add2 (entries, entry, 1);
+ entry->type = NPOL_CIDR;
+ entry->flags = i;
+ clib_memcpy (&entry->data.cidr, pfx, sizeof (*pfx));
+ }
+ vec_foreach (pr, rule->port_ranges[i])
+ {
+ vec_add2 (entries, entry, 1);
+ entry->type = NPOL_PORT_RANGE;
+ entry->flags = i;
+ clib_memcpy (&entry->data.port_range, pr, sizeof (*pr));
+ }
+ vec_foreach (set_id, rule->ip_ipsets[i])
+ {
+ vec_add2 (entries, entry, 1);
+ entry->type = NPOL_IP_SET;
+ entry->flags = i;
+ entry->data.set_id = *set_id;
+ }
+ vec_foreach (set_id, rule->ipport_ipsets[i])
+ {
+ vec_add2 (entries, entry, 1);
+ entry->type = NPOL_PORT_IP_SET;
+ entry->flags = i;
+ entry->data.set_id = *set_id;
+ }
+ }
+ return entries;
+}
+
+npol_rule_t *
+npol_rule_alloc ()
+{
+ npol_rule_t *rule;
+ pool_get_zero (npol_rules, rule);
+ return rule;
+}
+
+npol_rule_t *
+npol_rule_get_if_exists (u32 index)
+{
+ if (pool_is_free_index (npol_rules, index))
+ return (NULL);
+ return pool_elt_at_index (npol_rules, index);
+}
+
+static void
+npol_rule_cleanup (npol_rule_t *rule)
+{
+ int i;
+ vec_free (rule->filters);
+ for (i = 0; i < NPOL_RULE_MAX_FLAGS; i++)
+ {
+ vec_free (rule->prefixes[i]);
+ vec_free (rule->port_ranges[i]);
+ vec_free (rule->ip_ipsets[i]);
+ vec_free (rule->ipport_ipsets[i]);
+ }
+}
+
+int
+npol_rule_update (u32 *id, npol_rule_action_t action,
+ npol_rule_filter_t *filters, npol_rule_entry_t *entries)
+{
+ npol_rule_filter_t *filter;
+ npol_rule_entry_t *entry;
+ npol_rule_t *rule;
+ int rv;
+
+ rule = npol_rule_get_if_exists (*id);
+ if (rule)
+ npol_rule_cleanup (rule);
+ else
+ rule = npol_rule_alloc ();
+
+ rule->action = action;
+ vec_foreach (filter, filters)
+ vec_add1 (rule->filters, *filter);
+
+ vec_foreach (entry, entries)
+ {
+ u8 flags = entry->flags;
+ switch (entry->type)
+ {
+ case NPOL_CIDR:
+ vec_add1 (rule->prefixes[flags], entry->data.cidr);
+ break;
+ case NPOL_PORT_RANGE:
+ vec_add1 (rule->port_ranges[flags], entry->data.port_range);
+ break;
+ case NPOL_PORT_IP_SET:
+ vec_add1 (rule->ipport_ipsets[flags], entry->data.set_id);
+ break;
+ case NPOL_IP_SET:
+ vec_add1 (rule->ip_ipsets[flags], entry->data.set_id);
+ break;
+ default:
+ rv = 1;
+ goto error;
+ }
+ }
+ *id = rule - npol_rules;
+ return 0;
+error:
+ npol_rule_cleanup (rule);
+ pool_put (npol_rules, rule);
+ return rv;
+}
+
+int
+npol_rule_delete (u32 id)
+{
+ npol_rule_t *rule;
+ rule = npol_rule_get_if_exists (id);
+ if (NULL == rule)
+ return VNET_API_ERROR_NO_SUCH_ENTRY;
+
+ npol_rule_cleanup (rule);
+ pool_put (npol_rules, rule);
+
+ return 0;
+}
+
+static clib_error_t *
+npol_rules_show_cmd_fn (vlib_main_t *vm, unformat_input_t *input,
+ vlib_cli_command_t *cmd)
+{
+ npol_rule_t *rule;
+
+ pool_foreach (rule, npol_rules)
+ {
+ vlib_cli_output (vm, "%U", format_npol_rule, rule);
+ }
+
+ return 0;
+}
+
+VLIB_CLI_COMMAND (npol_rules_show_cmd, static) = {
+ .path = "show npol rules",
+ .function = npol_rules_show_cmd_fn,
+ .short_help = "show npol rules",
+};
+
+static clib_error_t *
+npol_rules_add_cmd_fn (vlib_main_t *vm, unformat_input_t *input,
+ vlib_cli_command_t *cmd)
+{
+ npol_rule_filter_t tmp_filter, *filters = 0;
+ npol_rule_entry_t tmp_entry, *entries = 0;
+ clib_error_t *error = 0;
+ npol_rule_action_t action;
+ u32 id = NPOL_INVALID_INDEX;
+ int rv;
+
+ while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
+ {
+ if (unformat (input, "update %u", &id))
+ ;
+ else if (unformat_user (input, unformat_npol_rule_action, &action))
+ ;
+ else if (unformat_user (input, unformat_npol_rule_entry, &tmp_entry))
+ vec_add1 (entries, tmp_entry);
+ else if (unformat_user (input, unformat_npol_rule_filter, &tmp_filter))
+ {
+ vec_add1 (filters, tmp_filter);
+ vlib_cli_output (vm, "%U", format_npol_rule_filter, &tmp_filter);
+ }
+ else
+ {
+ error = clib_error_return (0, "unknown input '%U'",
+ format_unformat_error, input);
+ goto done;
+ }
+ }
+
+ rv = npol_rule_update (&id, action, filters, entries);
+ if (rv)
+ error = clib_error_return (0, "npol_rule_update error %d", rv);
+ else
+ vlib_cli_output (vm, "npol rule %d added", id);
+
+done:
+ vec_free (filters);
+ vec_free (entries);
+ return error;
+}
+
+VLIB_CLI_COMMAND (npol_rules_add_cmd, static) = {
+ .path = "npol rule add",
+ .function = npol_rules_add_cmd_fn,
+ .short_help = "npol rule add [allow|deny|log|pass]"
+ "[filter[==|!=]value]"
+ "[[src|dst][==|!=][prefix|set ID|[port-port]]]",
+ .long_help = "Add a rule, with given filters and entries\n"
+ "filters can be `icmp-type`, `icmp-code` and `proto`",
+};
+
+static clib_error_t *
+npol_rules_del_cmd_fn (vlib_main_t *vm, unformat_input_t *input,
+ vlib_cli_command_t *cmd)
+{
+ clib_error_t *error = 0;
+ u32 id = NPOL_INVALID_INDEX;
+ int rv;
+
+ while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
+ {
+ if (unformat (input, "%u", &id))
+ ;
+ else
+ {
+ error = clib_error_return (0, "unknown input '%U'",
+ format_unformat_error, input);
+ goto done;
+ }
+ }
+
+ if (NPOL_INVALID_INDEX == id)
+ {
+ error = clib_error_return (0, "missing rule id");
+ goto done;
+ }
+
+ rv = npol_rule_delete (id);
+ if (rv)
+ error = clib_error_return (0, "npol_rule_delete errored with %d", rv);
+
+done:
+ return error;
+}
+
+VLIB_CLI_COMMAND (npol_rules_del_cmd, static) = {
+ .path = "npol rule del",
+ .function = npol_rules_del_cmd_fn,
+ .short_help = "npol rule del [id]",
+};
--- /dev/null
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright(c) 2025 Cisco Systems, Inc.
+ */
+
+#ifndef included_npol_rule_h
+#define included_npol_rule_h
+
+#include <npol/npol.h>
+
+typedef vl_api_npol_rule_action_t npol_rule_action_t;
+typedef vl_api_npol_entry_type_t npol_entry_type_t;
+typedef vl_api_npol_rule_filter_type_t npol_rule_filter_type_t;
+
+typedef struct npol_rule_filter_
+{
+ npol_rule_filter_type_t type;
+ /* Content to filter against */
+ u32 value;
+ /* If true match packet.property == opaque, else packet.property != opaque */
+ u8 should_match;
+} npol_rule_filter_t;
+
+typedef union npol_entry_data_t_
+{
+ ip_prefix_t cidr;
+ npol_port_range_t port_range;
+ u32 set_id;
+} npol_entry_data_t;
+
+typedef enum npol_rule_key_flag_
+{
+ NPOL_IS_SRC = 1 << 0,
+ NPOL_IS_NOT = 1 << 1,
+ NPOL_RULE_MAX_FLAGS = 1 << 2,
+} npol_rule_key_flag_t;
+
+#define NPOL_SRC NPOL_IS_SRC
+#define NPOL_NOT_SRC (NPOL_IS_SRC | NPOL_IS_NOT)
+#define NPOL_DST 0
+#define NPOL_NOT_DST NPOL_IS_NOT
+
+typedef struct npol_rule_entry_t_
+{
+ npol_entry_type_t type;
+ npol_entry_data_t data;
+ npol_rule_key_flag_t flags;
+} npol_rule_entry_t;
+
+typedef struct npol_rule_
+{
+ npol_rule_action_t action;
+
+ npol_rule_filter_t *filters;
+
+ /* Indexed by npol_rule_key_flag_t */
+ ip_prefix_t *prefixes[NPOL_RULE_MAX_FLAGS];
+ u32 *ip_ipsets[NPOL_RULE_MAX_FLAGS];
+ npol_port_range_t *port_ranges[NPOL_RULE_MAX_FLAGS];
+ u32 *ipport_ipsets[NPOL_RULE_MAX_FLAGS];
+} npol_rule_t;
+
+extern npol_rule_t *npol_rules;
+
+int npol_rule_delete (u32 id);
+int npol_rule_update (u32 *id, npol_rule_action_t action,
+ npol_rule_filter_t *filters, npol_rule_entry_t *entries);
+npol_rule_t *npol_rule_get_if_exists (u32 index);
+npol_rule_entry_t *npol_rule_get_entries (npol_rule_t *rule);
+
+#endif
--- /dev/null
+#!/usr/bin/env python3
+
+import random
+import unittest
+from ipaddress import (
+ IPv4Address,
+ IPv4Network,
+ IPv6Address,
+ IPv6Network,
+ ip_address,
+ ip_network,
+)
+from itertools import product
+
+from framework import VppTestCase
+from asfframework import VppTestRunner
+from scapy.layers.inet import (
+ ICMP,
+ IP,
+ TCP,
+ UDP,
+ ICMPerror,
+ IPerror,
+ TCPerror,
+ UDPerror,
+)
+from scapy.layers.inet6 import (
+ ICMPv6DestUnreach,
+ ICMPv6EchoReply,
+ ICMPv6EchoRequest,
+ IPerror6,
+ IPv6,
+)
+from scapy.layers.l2 import Ether
+from scapy.packet import Raw
+from vpp_ip import INVALID_INDEX, DpoProto
+from vpp_object import VppObject
+from vpp_papi import VppEnum
+
+icmp4_type = 8 # echo request
+icmp4_code = 3
+icmp6_type = 128 # echo request
+icmp6_code = 3
+tcp_protocol = 6
+icmp_protocol = 1
+icmp6_protocol = 58
+udp_protocol = 17
+src_l4 = 1234
+dst_l4 = 4321
+
+
+def random_payload():
+ return Raw(load=bytearray(random.getrandbits(8) for _ in range(20)))
+
+
+class VppNpolPolicyItem:
+ def __init__(self, is_inbound, rule_id):
+ self._is_inbound = is_inbound
+ self._rule_id = rule_id
+
+ def encode(self):
+ return {"rule_id": self._rule_id, "is_inbound": self._is_inbound}
+
+
+class VppNpolPolicy(VppObject):
+ def __init__(self, test, rules):
+ self._test = test
+ self._rules = rules
+ self.encoded_rules = []
+ self.init_rules()
+
+ def init_rules(self):
+ self.encoded_rules = []
+ for rule in self._rules:
+ self.encoded_rules.append(rule.encode())
+
+ def add_vpp_config(self):
+ r = self._test.vapi.npol_policy_create(
+ len(self.encoded_rules), self.encoded_rules
+ )
+ self._test.assertEqual(0, r.retval)
+ self._test.registry.register(self, self._test.logger)
+ self._test.logger.info("npol_policy_create retval=" + str(r.retval))
+ self._policy_id = r.policy_id
+ self._test.logger.info(self._test.vapi.cli("show npol policies verbose"))
+
+ def npol_policy_update(self, rules):
+ self._rules = rules
+ self.init_rules()
+ r = self._test.vapi.npol_policy_update(
+ self._policy_id, len(self.encoded_rules), self.encoded_rules
+ )
+ self._test.assertEqual(0, r.retval)
+
+ def npol_policy_delete(self):
+ r = self._test.vapi.npol_policy_delete(self._policy_id)
+ self._test.assertEqual(0, r.retval)
+ self._test.logger.info(self._test.vapi.cli("show npol policies"))
+
+ def remove_vpp_config(self):
+ self.npol_policy_delete()
+
+ def query_vpp_config(self):
+ self._test.logger.info("query vpp config")
+ self._test.logger.info(self._test.vapi.cli("show npol policies verbose"))
+
+
+class VppNpolFilter:
+ def __init__(self, type=None, value=0, should_match=0):
+ self._filter_type = type if type != None else FILTER_TYPE_NONE
+ self._filter_value = value
+ self._should_match = should_match
+
+ def encode(self):
+ return {
+ "type": self._filter_type,
+ "value": self._filter_value,
+ "should_match": self._should_match,
+ }
+
+
+class VppNpolRule(VppObject):
+ def __init__(self, test, is_v6, action, filters=[], matches=[]):
+ self._test = test
+ # This is actually unused
+ self._af = 255
+ self.init_rule(action, filters, matches)
+
+ def vpp_id(self):
+ return self._rule_id
+
+ def init_rule(self, action, filters=[], matches=[]):
+ self._action = action
+ self._filters = filters
+ self._matches = matches
+ self.encoded_filters = []
+ for filter in self._filters:
+ self.encoded_filters.append(filter.encode())
+ while len(self.encoded_filters) < 3:
+ self.encoded_filters.append(VppNpolFilter().encode())
+ self._test.assertEqual(len(self.encoded_filters), 3)
+
+ def add_vpp_config(self):
+ r = self._test.vapi.npol_rule_create(
+ {
+ "af": self._af,
+ "action": self._action,
+ "filters": self.encoded_filters,
+ "num_entries": len(self._matches),
+ "matches": self._matches,
+ }
+ )
+ self._test.assertEqual(0, r.retval)
+ self._test.registry.register(self, self._test.logger)
+ self._test.logger.info("npol_rule_create retval=" + str(r.retval))
+ self._rule_id = r.rule_id
+ self._test.logger.info("rules id : " + str(self._rule_id))
+ self._test.logger.info(self._test.vapi.cli("show npol rules"))
+
+ def npol_rule_update(self, filters, matches):
+ self.init_rule(self._action, filters, matches)
+ r = self._test.vapi.npol_rule_update(
+ self._rule_id,
+ {
+ "af": self._af,
+ "action": self._action,
+ "filters": self.encoded_filters,
+ "num_entries": len(self._matches),
+ "matches": self._matches,
+ },
+ )
+ self._test.assertEqual(0, r.retval)
+ self._test.registry.register(self, self._test.logger)
+ self._test.logger.info("npol rule update")
+ self._test.logger.info(self._test.vapi.cli("show npol rules"))
+
+ def npol_rule_delete(self):
+ r = self._test.vapi.npol_rule_delete(self._rule_id)
+ self._test.assertEqual(0, r.retval)
+ self._test.logger.info(self._test.vapi.cli("show npol rules"))
+
+ def remove_vpp_config(self):
+ self.npol_rule_delete()
+
+ def query_vpp_config(self):
+ self._test.logger.info("query vpp config")
+ self._test.logger.info(self._test.vapi.cli("show npol rules"))
+
+
+class VppNpolIpset(VppObject):
+ def __init__(self, test, type, members):
+ self.test = test
+ self.type = type
+ self.members = members
+
+ def add_vpp_config(self):
+ r = self.test.vapi.npol_ipset_create(self.type)
+ self.test.assertEqual(0, r.retval)
+ self.vpp_id = r.set_id
+ encoded_members = []
+ for m in self.members:
+ if self.type == IPSET_TYPE_IP:
+ encoded_members.append({"val": {"address": m}})
+ elif self.type == IPSET_TYPE_IP_PORT:
+ encoded_members.append({"val": {"tuple": m}})
+ elif self.type == IPSET_TYPE_NET:
+ encoded_members.append({"val": {"prefix": m}})
+ r = self.test.vapi.npol_ipset_add_del_members(
+ set_id=self.vpp_id,
+ is_add=True,
+ len=len(encoded_members),
+ members=encoded_members,
+ )
+ self.test.assertEqual(0, r.retval)
+
+ def query_vpp_config(self):
+ pass
+
+ def remove_vpp_config(self):
+ r = self.test.vapi.npol_ipset_delete(set_id=self.vpp_id)
+ self.test.assertEqual(0, r.retval)
+
+
+ACTION_ALLOW = VppEnum.vl_api_npol_rule_action_t.NPOL_ALLOW
+ACTION_DENY = VppEnum.vl_api_npol_rule_action_t.NPOL_DENY
+ACTION_PASS = VppEnum.vl_api_npol_rule_action_t.NPOL_PASS
+ACTION_LOG = VppEnum.vl_api_npol_rule_action_t.NPOL_LOG
+DEFAULT_ALLOW = VppEnum.vl_api_npol_policy_default_t.NPOL_DEFAULT_ALLOW
+DEFAULT_DENY = VppEnum.vl_api_npol_policy_default_t.NPOL_DEFAULT_DENY
+DEFAULT_PASS = VppEnum.vl_api_npol_policy_default_t.NPOL_DEFAULT_PASS
+FILTER_TYPE_NONE = VppEnum.vl_api_npol_rule_filter_type_t.NPOL_RULE_FILTER_NONE_TYPE
+FILTER_TYPE_L4_PROTO = VppEnum.vl_api_npol_rule_filter_type_t.NPOL_RULE_FILTER_L4_PROTO
+FILTER_TYPE_ICMP_CODE = (
+ VppEnum.vl_api_npol_rule_filter_type_t.NPOL_RULE_FILTER_ICMP_CODE
+)
+FILTER_TYPE_ICMP_TYPE = (
+ VppEnum.vl_api_npol_rule_filter_type_t.NPOL_RULE_FILTER_ICMP_TYPE
+)
+ENTRY_CIDR = VppEnum.vl_api_npol_entry_type_t.NPOL_CIDR
+ENTRY_PORT_RANGE = VppEnum.vl_api_npol_entry_type_t.NPOL_PORT_RANGE
+ENTRY_PORT_IP_SET = VppEnum.vl_api_npol_entry_type_t.NPOL_PORT_IP_SET
+ENTRY_IP_SET = VppEnum.vl_api_npol_entry_type_t.NPOL_IP_SET
+IPSET_TYPE_IP = VppEnum.vl_api_npol_ipset_type_t.NPOL_IP
+IPSET_TYPE_IP_PORT = VppEnum.vl_api_npol_ipset_type_t.NPOL_IP_AND_PORT
+IPSET_TYPE_NET = VppEnum.vl_api_npol_ipset_type_t.NPOL_NET
+
+
+class BaseNpolTest(VppTestCase):
+ @classmethod
+ def setUpClass(self):
+ super(BaseNpolTest, self).setUpClass()
+ # We can't define these before the API is loaded, so here they are...
+
+ self.create_pg_interfaces(range(2))
+ for i in self.pg_interfaces:
+ i.admin_up()
+ # Add one additional neighbor on each side
+ # for tests with different addresses
+ i.generate_remote_hosts(2)
+ i.config_ip4()
+ i.configure_ipv4_neighbors()
+ i.config_ip6()
+ i.configure_ipv6_neighbors()
+
+ @classmethod
+ def tearDownClass(self):
+ for i in self.pg_interfaces:
+ i.unconfig_ip4()
+ i.unconfig_ip6()
+ i.admin_down()
+ super(BaseNpolTest, self).tearDownClass()
+
+ def setUp(self):
+ super(BaseNpolTest, self).setUp()
+
+ def tearDown(self):
+ super(BaseNpolTest, self).tearDown()
+
+ def configure_policies(
+ self,
+ interface,
+ ingress,
+ egress,
+ profiles,
+ defrxpolicy=1,
+ deftxpolicy=1,
+ defrxprofile=1,
+ deftxprofile=1,
+ ):
+ id_list = []
+ for policy in ingress + egress + profiles:
+ id_list.append(policy._policy_id)
+ r = self.vapi.npol_configure_policies(
+ interface.sw_if_index,
+ len(ingress),
+ len(egress),
+ len(ingress) + len(egress) + len(profiles),
+ 1,
+ defrxpolicy,
+ deftxpolicy,
+ defrxprofile,
+ deftxprofile,
+ id_list,
+ )
+ self.assertEqual(0, r.retval)
+
+ def base_ip_packet(self, is_v6=False, src_ip2=False, dst_ip2=False):
+ IP46 = IPv6 if is_v6 else IP
+ src_host = self.pg0.remote_hosts[1 if src_ip2 else 0]
+ dst_host = self.pg1.remote_hosts[1 if dst_ip2 else 0]
+ src_addr = src_host.ip6 if is_v6 else src_host.ip4
+ dst_addr = dst_host.ip6 if is_v6 else dst_host.ip4
+ return Ether(src=src_host.mac, dst=self.pg0.local_mac) / IP46(
+ src=src_addr, dst=dst_addr
+ )
+
+ def do_test_one_rule(
+ self, filters, matches, matching_packets, not_matching_packets
+ ):
+ # Caution: because of how vpp works, packets may be reordered
+ # (v4 first, v6 next) which may break the check on received packets
+ # Therefore, in matching packets, all v4 packets must be before
+ # all v6 packets
+ self.rule.npol_rule_update(filters, matches)
+ self.send_test_packets(
+ self.pg0, self.pg1, matching_packets, not_matching_packets
+ )
+
+ def vapi_npol_match(self, sw_if_index, pkt, direction):
+ if pkt.haslayer(IP):
+ ipv = "ip4"
+ src_addr = pkt.getlayer(IP).src
+ dst_addr = pkt.getlayer(IP).dst
+ if pkt.haslayer(IPv6):
+ ipv = "ip6"
+ src_addr = pkt.getlayer(IPv6).src
+ dst_addr = pkt.getlayer(IPv6).dst
+ if pkt.haslayer(UDP):
+ proto = "udp"
+ src_port = pkt.getlayer(UDP).sport
+ dst_port = pkt.getlayer(UDP).dport
+ if pkt.haslayer(TCP):
+ proto = "tcp"
+ src_port = pkt.getlayer(TCP).sport
+ dst_port = pkt.getlayer(TCP).dport
+ if pkt.haslayer(ICMP):
+ proto = "icmp"
+ src_port = pkt.getlayer(ICMP).type
+ dst_port = pkt.getlayer(ICMP).code
+ if pkt.haslayer(ICMPv6EchoRequest):
+ proto = "icmp6"
+ src_port = pkt.getlayer(ICMPv6EchoRequest).type
+ dst_port = pkt.getlayer(ICMPv6EchoRequest).code
+
+ return self.vapi.cli(
+ f"npol match {sw_if_index} {direction} {ipv} "
+ f"{proto} " + f"{src_addr};{src_port}->{dst_addr};{dst_port}"
+ )
+
+ def send_test_packets(self, from_if, to_if, passing_packets, drop_packets):
+ for pkt in passing_packets:
+ self.assertTrue(
+ self.vapi_npol_match(from_if, pkt, "inbound")
+ .strip()
+ .endswith("action:ALLOW")
+ )
+ self.assertTrue(
+ self.vapi_npol_match(to_if, pkt, "outbound")
+ .strip()
+ .endswith("action:ALLOW")
+ )
+ for pkt in drop_packets:
+ self.assertTrue(
+ self.vapi_npol_match(from_if, pkt, "inbound")
+ .strip()
+ .endswith("action:DENY")
+ or self.vapi_npol_match(to_if, pkt, "outbound")
+ .strip()
+ .endswith("action:DENY")
+ )
+
+ # if len(passing_packets) > 0:
+ # rxl = self.send_and_expect(from_if, passing_packets, to_if)
+ # self.assertEqual(len(rxl), len(passing_packets))
+ # for i in range(len(passing_packets)):
+ # rx = rxl[i].payload
+ # tx = passing_packets[i].payload
+ # tx = tx.__class__(bytes(tx)) # Compute all fields
+ # # Remove IP[v6] TTL / checksum that are changed on forwarding
+ # if IP in tx:
+ # del tx.chksum, tx.ttl, rx.chksum, rx.ttl
+ # elif IPv6 in tx:
+ # del tx.hlim, rx.hlim
+ # self.assertEqual(rx, tx)
+ # if len(drop_packets) > 0:
+ # self.send_and_assert_no_replies(
+ # from_if, drop_packets, to_if, timeout=0.1
+ # )
+ # self.vapi.cli("clear acl-plugin sessions")
+
+
+class TestNpolMatches(BaseNpolTest):
+ """Network Policies rule matching tests"""
+
+ @classmethod
+ def setUpClass(self):
+ super(TestNpolMatches, self).setUpClass()
+
+ @classmethod
+ def tearDownClass(self):
+ super(TestNpolMatches, self).tearDownClass()
+
+ def setUp(self):
+ super(TestNpolMatches, self).setUp()
+ self.rule = VppNpolRule(self, is_v6=False, action=ACTION_ALLOW)
+ self.rule.add_vpp_config()
+ self.policy = VppNpolPolicy(
+ self, [VppNpolPolicyItem(is_inbound=1, rule_id=self.rule.vpp_id())]
+ )
+ self.policy.add_vpp_config()
+ self.configure_policies(self.pg1, [self.policy], [], [])
+ self.src_ip_ipset = VppNpolIpset(
+ self, IPSET_TYPE_IP, [self.pg0.remote_ip4, self.pg0.remote_ip6]
+ )
+ self.src_ip_ipset.add_vpp_config()
+ self.dst_ip_ipset = VppNpolIpset(
+ self, IPSET_TYPE_IP, [self.pg1.remote_ip4, self.pg1.remote_ip6]
+ )
+ self.dst_ip_ipset.add_vpp_config()
+ self.src_net_ipset = VppNpolIpset(
+ self,
+ IPSET_TYPE_NET,
+ [self.pg0.remote_ip4 + "/32", self.pg0.remote_ip6 + "/128"],
+ )
+ self.src_net_ipset.add_vpp_config()
+ self.dst_net_ipset = VppNpolIpset(
+ self,
+ IPSET_TYPE_NET,
+ [self.pg1.remote_ip4 + "/32", self.pg1.remote_ip6 + "/128"],
+ )
+ self.dst_net_ipset.add_vpp_config()
+ self.src_ipport_ipset = VppNpolIpset(
+ self,
+ IPSET_TYPE_IP_PORT,
+ [
+ {
+ "address": self.pg0.remote_ip4,
+ "l4_proto": tcp_protocol,
+ "port": src_l4,
+ },
+ {
+ "address": self.pg0.remote_ip6,
+ "l4_proto": tcp_protocol,
+ "port": src_l4,
+ },
+ ],
+ )
+ self.src_ipport_ipset.add_vpp_config()
+ self.dst_ipport_ipset = VppNpolIpset(
+ self,
+ IPSET_TYPE_IP_PORT,
+ [
+ {
+ "address": self.pg1.remote_ip4,
+ "l4_proto": tcp_protocol,
+ "port": dst_l4,
+ },
+ {
+ "address": self.pg1.remote_ip6,
+ "l4_proto": tcp_protocol,
+ "port": dst_l4,
+ },
+ ],
+ )
+ self.dst_ipport_ipset.add_vpp_config()
+
+ def tearDown(self):
+ self.vapi.cli("clear acl-plugin sessions")
+ self.configure_policies(self.pg1, [], [], [])
+ self.policy.npol_policy_delete()
+ self.rule.npol_rule_delete()
+ super(TestNpolMatches, self).tearDown()
+
+ def test_empty_rule(self):
+ # Empty rule matches everything
+ valid = [
+ self.base_ip_packet(False)
+ / TCP(sport=src_l4, dport=dst_l4)
+ / random_payload(),
+ self.base_ip_packet(False)
+ / UDP(sport=src_l4, dport=dst_l4)
+ / random_payload(),
+ self.base_ip_packet(False)
+ / ICMP(type=icmp4_type, code=icmp4_code)
+ / random_payload(),
+ self.base_ip_packet(True)
+ / TCP(sport=src_l4, dport=dst_l4)
+ / random_payload(),
+ self.base_ip_packet(True)
+ / UDP(sport=src_l4, dport=dst_l4)
+ / random_payload(),
+ self.base_ip_packet(True)
+ / ICMPv6EchoRequest(type=icmp6_type, code=icmp6_code)
+ / random_payload(),
+ ]
+ self.do_test_one_rule([], [], valid, [])
+
+ def npol_test_icmp(self, is_v6):
+ ICMP46 = ICMPv6EchoRequest if is_v6 else ICMP
+ icmp_type = icmp6_type if is_v6 else icmp4_type
+ icmp_code = icmp6_code if is_v6 else icmp4_code
+
+ # Define filter on ICMP type
+ filters = [
+ VppNpolFilter(FILTER_TYPE_ICMP_TYPE, value=icmp_type, should_match=1)
+ ]
+ valid = (
+ self.base_ip_packet(is_v6)
+ / ICMP46(type=icmp_type, code=icmp_code)
+ / random_payload()
+ )
+ invalid = (
+ self.base_ip_packet(is_v6) / ICMP46(type=11, code=22) / random_payload()
+ )
+ self.do_test_one_rule(filters, [], [valid], [invalid])
+
+ # Define filter on ICMP type / should match = 0
+ filters = [VppNpolFilter(FILTER_TYPE_ICMP_TYPE, value=11, should_match=0)]
+ invalid = (
+ self.base_ip_packet(is_v6)
+ / ICMP46(type=11, code=icmp_code)
+ / random_payload()
+ )
+ valid = (
+ self.base_ip_packet(is_v6)
+ / ICMP46(type=icmp_type, code=icmp_code)
+ / random_payload()
+ )
+ self.do_test_one_rule(filters, [], [valid], [invalid])
+
+ def test_icmp4_type(self):
+ self.npol_test_icmp(is_v6=False)
+
+ def test_icmp6_type(self):
+ self.npol_test_icmp(is_v6=True)
+
+ def npol_test_icmp_code(self, is_v6):
+ ICMP46 = ICMPv6EchoRequest if is_v6 else ICMP
+ icmp_type = 1 if is_v6 else 3 # Destination unreachable
+ icmp_code = 9 # admin prohibited
+
+ # Define filter on ICMP type
+ filters = [
+ VppNpolFilter(FILTER_TYPE_ICMP_CODE, value=icmp_code, should_match=1)
+ ]
+ valid = (
+ self.base_ip_packet(is_v6)
+ / ICMP46(type=icmp_type, code=icmp_code)
+ / random_payload()
+ )
+ invalid = (
+ self.base_ip_packet(is_v6)
+ / ICMP46(type=icmp_type, code=icmp_code - 1)
+ / random_payload()
+ )
+ self.do_test_one_rule(filters, [], [valid], [invalid])
+
+ # Define filter on ICMP type / should match = 0
+ filters = [
+ VppNpolFilter(FILTER_TYPE_ICMP_CODE, value=icmp_code, should_match=0)
+ ]
+ valid = (
+ self.base_ip_packet(is_v6)
+ / ICMP46(type=icmp_type, code=icmp_code + 1)
+ / random_payload()
+ )
+ invalid = (
+ self.base_ip_packet(is_v6)
+ / ICMP46(type=icmp_type, code=icmp_code)
+ / random_payload()
+ )
+ self.do_test_one_rule(filters, [], [valid], [invalid])
+
+ def test_icmp4_code(self):
+ self.npol_test_icmp(is_v6=False)
+
+ def test_icmp6_code(self):
+ self.npol_test_icmp(is_v6=True)
+
+ def npol_test_l4proto(self, is_v6, l4proto):
+ filter_value = 0
+ if l4proto == TCP:
+ filter_value = tcp_protocol
+ elif l4proto == UDP:
+ filter_value = udp_protocol
+
+ # Define filter on l4proto type
+ filters = [
+ VppNpolFilter(FILTER_TYPE_L4_PROTO, value=filter_value, should_match=1)
+ ]
+
+ # Send tcp pg0 -> pg1
+ valid = (
+ self.base_ip_packet(is_v6)
+ / l4proto(sport=src_l4, dport=dst_l4)
+ / random_payload()
+ )
+ # send icmp packet (different l4proto) and expect packet is filtered
+ invalid = self.base_ip_packet(is_v6) / ICMP(type=8, code=3) / random_payload()
+ self.do_test_one_rule(filters, [], [valid], [invalid])
+
+ # Define filter on l4proto / should match = 0
+ filters = [
+ VppNpolFilter(FILTER_TYPE_L4_PROTO, value=filter_value, should_match=0)
+ ]
+ # send l4proto packet and expect it is filtered
+ invalid = (
+ self.base_ip_packet(is_v6)
+ / l4proto(sport=src_l4, dport=dst_l4)
+ / random_payload()
+ )
+ # send icmp packet (different l4proto) and expect it is not filtered
+ valid = self.base_ip_packet(is_v6) / ICMP(type=8, code=3) / random_payload()
+ self.do_test_one_rule(filters, [], [valid], [invalid])
+
+ def test_l4proto_tcp4(self):
+ self.npol_test_l4proto(False, TCP)
+
+ def test_l4proto_tcp6(self):
+ self.npol_test_l4proto(True, TCP)
+
+ def test_l4proto_udp4(self):
+ self.npol_test_l4proto(False, UDP)
+
+ def test_l4proto_udp6(self):
+ self.npol_test_l4proto(True, UDP)
+
+ def test_prefixes_ip6(self):
+ self.test_prefixes(True)
+
+ def test_prefixes(self, is_ip6=False):
+ def pload():
+ return TCP(sport=src_l4, dport=dst_l4) / random_payload()
+
+ dst_ip_match = (
+ self.pg1.remote_ip6 + "/128" if is_ip6 else self.pg1.remote_ip4 + "/32"
+ )
+ match = {
+ "is_src": False,
+ "is_not": False,
+ "type": ENTRY_CIDR,
+ "data": {"cidr": dst_ip_match},
+ }
+ valid = self.base_ip_packet(is_ip6) / pload()
+ invalid = self.base_ip_packet(is_ip6, dst_ip2=True) / pload()
+ self.do_test_one_rule([], [match], [valid], [invalid])
+
+ match["is_not"] = True
+ self.do_test_one_rule([], [match], [invalid], [valid])
+
+ src_ip_match = (
+ self.pg0.remote_ip6 + "/128" if is_ip6 else self.pg0.remote_ip4 + "/32"
+ )
+ match = {
+ "is_src": True,
+ "is_not": False,
+ "type": ENTRY_CIDR,
+ "data": {"cidr": src_ip_match},
+ }
+ valid = self.base_ip_packet(is_ip6) / pload()
+ invalid = self.base_ip_packet(is_ip6, src_ip2=True) / pload()
+ self.do_test_one_rule([], [match], [valid], [invalid])
+
+ match["is_not"] = True
+ self.do_test_one_rule([], [match], [invalid], [valid])
+
+ def test_port_ranges_ip6(self):
+ self.test_prefixes(True)
+
+ def test_port_ranges(self, is_ip6=False):
+ base = self.base_ip_packet(is_ip6)
+ test_port = 5123
+ # Test all match kinds
+ match = {
+ "is_src": False,
+ "is_not": False,
+ "type": ENTRY_PORT_RANGE,
+ "data": {"port_range": {"start": test_port, "end": test_port}},
+ }
+ valid = base / TCP(sport=test_port, dport=test_port) / random_payload()
+ invalid = [
+ base / TCP(sport=test_port, dport=test_port + 1) / random_payload(),
+ base / TCP(sport=test_port, dport=test_port - 1) / random_payload(),
+ ]
+ self.do_test_one_rule([], [match], [valid], invalid)
+
+ match["is_not"] = True
+ self.do_test_one_rule([], [match], invalid, [valid])
+
+ match = {
+ "is_src": True,
+ "is_not": False,
+ "type": ENTRY_PORT_RANGE,
+ "data": {"port_range": {"start": test_port, "end": test_port}},
+ }
+ valid = base / TCP(sport=test_port, dport=test_port) / random_payload()
+ invalid = [
+ base / TCP(sport=test_port + 1, dport=test_port) / random_payload(),
+ base / TCP(sport=test_port - 1, dport=test_port) / random_payload(),
+ ]
+ self.do_test_one_rule([], [match], [valid], invalid)
+
+ match["is_not"] = True
+ self.do_test_one_rule([], [match], invalid, [valid])
+
+ # Test port ranges with several ports & UDP
+ match = {
+ "is_src": False,
+ "is_not": False,
+ "type": ENTRY_PORT_RANGE,
+ "data": {
+ "port_range": {
+ "start": test_port,
+ "end": test_port + 10,
+ }
+ },
+ }
+ valid = [
+ base / TCP(sport=test_port, dport=test_port) / random_payload(),
+ base / TCP(sport=test_port, dport=test_port + 5) / random_payload(),
+ base / TCP(sport=test_port, dport=test_port + 10) / random_payload(),
+ base / UDP(sport=test_port, dport=test_port) / random_payload(),
+ base / UDP(sport=test_port, dport=test_port + 5) / random_payload(),
+ base / UDP(sport=test_port, dport=test_port + 10) / random_payload(),
+ ]
+ invalid = [
+ base / TCP(sport=test_port, dport=test_port - 1) / random_payload(),
+ base / TCP(sport=test_port, dport=test_port + 11) / random_payload(),
+ ]
+ self.do_test_one_rule([], [match], valid, invalid)
+
+ def test_ip_ipset_ip6(self):
+ self.test_ip_ipset(True)
+
+ def test_ip_ipset(self, is_ip6=False):
+ def pload():
+ return TCP(sport=src_l4, dport=dst_l4) / random_payload()
+
+ dst_ip_match = (
+ self.pg1.remote_ip6 + "/128" if is_ip6 else self.pg1.remote_ip4 + "/32"
+ )
+ match = {
+ "is_src": False,
+ "is_not": False,
+ "type": ENTRY_IP_SET,
+ "data": {"set_id": {"set_id": self.dst_ip_ipset.vpp_id}},
+ }
+ valid = self.base_ip_packet(is_ip6) / pload()
+ invalid = self.base_ip_packet(is_ip6, dst_ip2=True) / pload()
+ self.do_test_one_rule([], [match], [valid], [invalid])
+
+ match["is_not"] = True
+ self.do_test_one_rule([], [match], [invalid], [valid])
+
+ src_ip_match = (
+ self.pg0.remote_ip6 + "/128" if is_ip6 else self.pg0.remote_ip4 + "/32"
+ )
+ match = {
+ "is_src": True,
+ "is_not": False,
+ "type": ENTRY_IP_SET,
+ "data": {"set_id": {"set_id": self.src_ip_ipset.vpp_id}},
+ }
+ valid = self.base_ip_packet(is_ip6) / pload()
+ invalid = self.base_ip_packet(is_ip6, src_ip2=True) / pload()
+ self.do_test_one_rule([], [match], [valid], [invalid])
+
+ match["is_not"] = True
+ self.do_test_one_rule([], [match], [invalid], [valid])
+
+ def test_net_ipset_ip6(self):
+ self.test_net_ipset(True)
+
+ def test_net_ipset(self, is_ip6=False):
+ def pload():
+ return TCP(sport=src_l4, dport=dst_l4) / random_payload()
+
+ dst_ip_match = (
+ self.pg1.remote_ip6 + "/128" if is_ip6 else self.pg1.remote_ip4 + "/32"
+ )
+ match = {
+ "is_src": False,
+ "is_not": False,
+ "type": ENTRY_IP_SET,
+ "data": {"set_id": {"set_id": self.dst_net_ipset.vpp_id}},
+ }
+ valid = self.base_ip_packet(is_ip6) / pload()
+ invalid = self.base_ip_packet(is_ip6, dst_ip2=True) / pload()
+ self.do_test_one_rule([], [match], [valid], [invalid])
+
+ match["is_not"] = True
+ self.do_test_one_rule([], [match], [invalid], [valid])
+
+ src_ip_match = (
+ self.pg0.remote_ip6 + "/128" if is_ip6 else self.pg0.remote_ip4 + "/32"
+ )
+ match = {
+ "is_src": True,
+ "is_not": False,
+ "type": ENTRY_IP_SET,
+ "data": {"set_id": {"set_id": self.src_net_ipset.vpp_id}},
+ }
+ valid = self.base_ip_packet(is_ip6) / pload()
+ invalid = self.base_ip_packet(is_ip6, src_ip2=True) / pload()
+ self.do_test_one_rule([], [match], [valid], [invalid])
+
+ match["is_not"] = True
+ self.do_test_one_rule([], [match], [invalid], [valid])
+
+ def test_ipport_ipset_ip6(self):
+ self.test_ipport_ipset(True)
+
+ def test_ipport_ipset(self, is_ip6=False):
+ match = {
+ "is_src": False,
+ "is_not": False,
+ "type": ENTRY_PORT_IP_SET,
+ "data": {"set_id": {"set_id": self.dst_ipport_ipset.vpp_id}},
+ }
+ valid = (
+ self.base_ip_packet(is_ip6)
+ / TCP(sport=src_l4, dport=dst_l4)
+ / random_payload()
+ )
+ invalid = [ # Change all criteria: address, proto, port
+ self.base_ip_packet(is_ip6, dst_ip2=True)
+ / TCP(sport=src_l4, dport=dst_l4)
+ / random_payload(),
+ self.base_ip_packet(is_ip6)
+ / UDP(sport=src_l4, dport=dst_l4)
+ / random_payload(),
+ self.base_ip_packet(is_ip6)
+ / TCP(sport=src_l4, dport=dst_l4 + 1)
+ / random_payload(),
+ ]
+ self.do_test_one_rule([], [match], [valid], invalid)
+
+ match["is_not"] = True
+ self.do_test_one_rule([], [match], invalid, [valid])
+
+ match = {
+ "is_src": True,
+ "is_not": False,
+ "type": ENTRY_PORT_IP_SET,
+ "data": {"set_id": {"set_id": self.src_ipport_ipset.vpp_id}},
+ }
+ valid = (
+ self.base_ip_packet(is_ip6)
+ / TCP(sport=src_l4, dport=dst_l4)
+ / random_payload()
+ )
+ invalid = [ # Change all criteria: address, proto, port
+ self.base_ip_packet(is_ip6, src_ip2=True)
+ / TCP(sport=src_l4, dport=dst_l4)
+ / random_payload(),
+ self.base_ip_packet(is_ip6)
+ / UDP(sport=src_l4, dport=dst_l4)
+ / random_payload(),
+ self.base_ip_packet(is_ip6)
+ / TCP(sport=src_l4 + 1, dport=dst_l4)
+ / random_payload(),
+ ]
+ self.do_test_one_rule([], [match], [valid], invalid)
+
+ match["is_not"] = True
+ self.do_test_one_rule([], [match], invalid, [valid])
+
+ # Calico specificity: if a rule has port ranges and ipport ipsets,
+ # a packet matches the rule if it matches either category
+ def test_port_range_and_ipport_ipset_ip6(self):
+ self.test_port_range_and_ipport_ipset(True)
+
+ def test_port_range_and_ipport_ipset(self, is_ip6=False):
+ # Test all match types to exercies all code (but not all combinations)
+ test_port = 4569
+ matches = [
+ {
+ "is_src": False,
+ "is_not": False,
+ "type": ENTRY_PORT_IP_SET,
+ "data": {"set_id": {"set_id": self.dst_ipport_ipset.vpp_id}},
+ },
+ {
+ "is_src": False,
+ "is_not": False,
+ "type": ENTRY_PORT_RANGE,
+ "data": {"port_range": {"start": test_port, "end": test_port}},
+ },
+ ]
+ valid = [
+ self.base_ip_packet(is_ip6)
+ / TCP(sport=src_l4, dport=dst_l4)
+ / random_payload(),
+ self.base_ip_packet(is_ip6)
+ / TCP(sport=src_l4, dport=test_port)
+ / random_payload(),
+ self.base_ip_packet(is_ip6)
+ / UDP(sport=src_l4, dport=test_port)
+ / random_payload(),
+ self.base_ip_packet(is_ip6, src_ip2=True)
+ / TCP(sport=src_l4, dport=test_port)
+ / random_payload(),
+ self.base_ip_packet(is_ip6, dst_ip2=True)
+ / TCP(sport=src_l4, dport=test_port)
+ / random_payload(),
+ ]
+ invalid = [
+ self.base_ip_packet(is_ip6, dst_ip2=True)
+ / TCP(sport=src_l4, dport=dst_l4)
+ / random_payload(),
+ self.base_ip_packet(is_ip6)
+ / UDP(sport=src_l4, dport=dst_l4)
+ / random_payload(),
+ self.base_ip_packet(is_ip6)
+ / TCP(sport=src_l4, dport=(dst_l4 + test_port) // 2)
+ / random_payload(),
+ ]
+ self.do_test_one_rule([], matches, valid, invalid)
+
+ for match in matches:
+ match["is_not"] = True
+ self.do_test_one_rule([], matches, invalid, valid)
+
+ matches = [
+ {
+ "is_src": True,
+ "is_not": False,
+ "type": ENTRY_PORT_IP_SET,
+ "data": {"set_id": {"set_id": self.src_ipport_ipset.vpp_id}},
+ },
+ {
+ "is_src": True,
+ "is_not": False,
+ "type": ENTRY_PORT_RANGE,
+ "data": {"port_range": {"start": test_port, "end": test_port}},
+ },
+ ]
+ valid = [
+ self.base_ip_packet(is_ip6)
+ / TCP(sport=src_l4, dport=dst_l4)
+ / random_payload(),
+ self.base_ip_packet(is_ip6)
+ / TCP(sport=test_port, dport=dst_l4)
+ / random_payload(),
+ self.base_ip_packet(is_ip6)
+ / UDP(sport=test_port, dport=dst_l4)
+ / random_payload(),
+ self.base_ip_packet(is_ip6, src_ip2=True)
+ / TCP(sport=test_port, dport=dst_l4)
+ / random_payload(),
+ self.base_ip_packet(is_ip6, dst_ip2=True)
+ / TCP(sport=test_port, dport=dst_l4)
+ / random_payload(),
+ ]
+ invalid = [
+ self.base_ip_packet(is_ip6, src_ip2=True)
+ / TCP(sport=src_l4, dport=dst_l4)
+ / random_payload(),
+ self.base_ip_packet(is_ip6)
+ / UDP(sport=src_l4, dport=dst_l4)
+ / random_payload(),
+ self.base_ip_packet(is_ip6)
+ / TCP(sport=(src_l4 + test_port) // 2, dport=dst_l4)
+ / random_payload(),
+ ]
+ self.do_test_one_rule([], matches, valid, invalid)
+
+ for match in matches:
+ match["is_not"] = True
+ self.do_test_one_rule([], matches, invalid, valid)
+
+
+class TestNpolPolicies(BaseNpolTest):
+ """Network Policies tests"""
+
+ @classmethod
+ def setUpClass(self):
+ super(TestNpolPolicies, self).setUpClass()
+
+ @classmethod
+ def tearDownClass(self):
+ super(TestNpolPolicies, self).tearDownClass()
+
+ def setUp(self):
+ super(TestNpolPolicies, self).setUp()
+
+ def tearDown(self):
+ super(TestNpolPolicies, self).tearDown()
+
+ def tcp_dport_rule(self, port, action):
+ return VppNpolRule(
+ self,
+ is_v6=False,
+ action=action,
+ filters=[VppNpolFilter(FILTER_TYPE_L4_PROTO, tcp_protocol, True)],
+ matches=[
+ {
+ "is_src": False,
+ "is_not": False,
+ "type": ENTRY_PORT_RANGE,
+ "data": {"port_range": {"start": port, "end": port}},
+ }
+ ],
+ )
+
+ def test_inbound_outbound(self):
+ r = self.tcp_dport_rule(1000, ACTION_ALLOW)
+ r.add_vpp_config()
+ pin = VppNpolPolicy(self, [VppNpolPolicyItem(is_inbound=1, rule_id=r.vpp_id())])
+ pout = VppNpolPolicy(
+ self, [VppNpolPolicyItem(is_inbound=0, rule_id=r.vpp_id())]
+ )
+ pin.add_vpp_config()
+ pout.add_vpp_config()
+
+ matching = self.base_ip_packet() / TCP(sport=1, dport=1000) / random_payload()
+ not_matching = (
+ self.base_ip_packet() / TCP(sport=1, dport=2000) / random_payload()
+ )
+
+ # out policy at src
+ self.configure_policies(self.pg0, [], [pout], [])
+ self.send_test_packets(self.pg0, self.pg1, [matching], [not_matching])
+
+ # policies configured at src + dst
+ self.configure_policies(self.pg1, [pin], [], [])
+ self.send_test_packets(self.pg0, self.pg1, [matching], [not_matching])
+
+ # policies configured at dst
+ self.configure_policies(self.pg0, [], [], [], 0, 0)
+ self.send_test_packets(self.pg0, self.pg1, [matching], [not_matching])
+
+ # no policies
+ self.configure_policies(self.pg1, [], [], [], 0, 0)
+ self.send_test_packets(self.pg0, self.pg1, [matching, not_matching], [])
+
+ def test_default_verdict(self):
+ # If profiles only are configured (pass_id = 0), default is deny
+ # If there are policies + profiles (pass_id > 0), then default
+ # is to deny before evaluating profiles, unless a rule with a PASS
+ # target matches
+ rule1 = self.tcp_dport_rule(1000, ACTION_ALLOW)
+ rule2 = self.tcp_dport_rule(2000, ACTION_ALLOW)
+ rule3 = self.tcp_dport_rule(1000, ACTION_DENY)
+ rule4 = self.tcp_dport_rule(1000, ACTION_PASS)
+ rule1.add_vpp_config()
+ rule2.add_vpp_config()
+ rule3.add_vpp_config()
+ rule4.add_vpp_config()
+ policy1 = VppNpolPolicy(
+ self, [VppNpolPolicyItem(is_inbound=1, rule_id=rule1.vpp_id())]
+ )
+ policy2 = VppNpolPolicy(
+ self, [VppNpolPolicyItem(is_inbound=1, rule_id=rule2.vpp_id())]
+ )
+ policy3 = VppNpolPolicy(
+ self, [VppNpolPolicyItem(is_inbound=1, rule_id=rule3.vpp_id())]
+ )
+ policy4 = VppNpolPolicy(
+ self, [VppNpolPolicyItem(is_inbound=1, rule_id=rule4.vpp_id())]
+ )
+ policy5 = VppNpolPolicy(
+ self,
+ [
+ VppNpolPolicyItem(is_inbound=1, rule_id=rule4.vpp_id()),
+ VppNpolPolicyItem(is_inbound=1, rule_id=rule3.vpp_id()),
+ ],
+ )
+ policy1.add_vpp_config()
+ policy2.add_vpp_config()
+ policy3.add_vpp_config()
+ policy4.add_vpp_config()
+ policy5.add_vpp_config()
+
+ # Test profile default deny: 1 allow rule, pass_id=0
+ self.configure_policies(self.pg1, [], [], [policy1], 2, 2, 1, 1)
+ passing = [self.base_ip_packet() / TCP(sport=1, dport=1000) / random_payload()]
+ dropped = [self.base_ip_packet() / TCP(sport=1, dport=2000) / random_payload()]
+ self.send_test_packets(self.pg0, self.pg1, passing, dropped)
+
+ # Test policy default deny: 1 allow rule, pass_id=1
+ self.configure_policies(self.pg1, [policy1], [], [])
+ self.send_test_packets(self.pg0, self.pg1, passing, dropped)
+
+ # Test that profiles are not executed when policies are configured
+ # 1 allow policy, 1 allow profile, pass_id=1
+ self.configure_policies(self.pg1, [policy1], [], [policy2])
+ self.send_test_packets(self.pg0, self.pg1, passing, dropped)
+
+ # Test that pass target does not evaluate further policies and
+ # jumps to profiles 1 pass policy, 1 deny policy, 1 allow profile,
+ # pass_id=2
+ self.configure_policies(self.pg1, [policy4, policy3], [], [policy1])
+ self.send_test_packets(self.pg0, self.pg1, passing, dropped)
+
+ # Test that pass target does not evaluate further rules in the
+ # policy and jumps to profiles 1 policy w/ 1 pass rule & 1 deny rule,
+ # 1 deny profile, pass_id=1
+ self.configure_policies(self.pg1, [policy5], [], [policy1])
+ self.send_test_packets(self.pg0, self.pg1, passing, dropped)
+
+ policy1.remove_vpp_config()
+ policy2.remove_vpp_config()
+ policy3.remove_vpp_config()
+ policy4.remove_vpp_config()
+ policy5.remove_vpp_config()
+ rule1.remove_vpp_config()
+ rule2.remove_vpp_config()
+ rule3.remove_vpp_config()
+ rule4.remove_vpp_config()
+
+ def test_realistic_policy(self):
+ # Rule 1 allows ping from everywhere
+ rule1 = VppNpolRule(
+ self,
+ is_v6=False,
+ action=ACTION_ALLOW,
+ filters=[
+ VppNpolFilter(FILTER_TYPE_L4_PROTO, icmp_protocol, True),
+ VppNpolFilter(FILTER_TYPE_ICMP_TYPE, 8, True),
+ VppNpolFilter(FILTER_TYPE_ICMP_CODE, 0, True),
+ ],
+ matches=[],
+ )
+ rule1.add_vpp_config()
+ # Rule 2 allows tcp dport 8080 from a single container
+ src_ipset = VppNpolIpset(
+ self,
+ IPSET_TYPE_NET,
+ [self.pg0.remote_ip4 + "/32", self.pg0.remote_ip6 + "/128"],
+ )
+ src_ipset.add_vpp_config()
+ rule2 = VppNpolRule(
+ self,
+ is_v6=False,
+ action=ACTION_ALLOW,
+ filters=[
+ VppNpolFilter(FILTER_TYPE_L4_PROTO, tcp_protocol, True),
+ ],
+ matches=[
+ {
+ "is_src": True,
+ "is_not": False,
+ "type": ENTRY_IP_SET,
+ "data": {"set_id": {"set_id": src_ipset.vpp_id}},
+ },
+ {
+ "is_src": False,
+ "is_not": False,
+ "type": ENTRY_PORT_RANGE,
+ "data": {"port_range": {"start": 8080, "end": 8080}},
+ },
+ ],
+ )
+ rule2.add_vpp_config()
+ policy = VppNpolPolicy(
+ self,
+ [
+ VppNpolPolicyItem(is_inbound=1, rule_id=rule1.vpp_id()),
+ VppNpolPolicyItem(is_inbound=1, rule_id=rule2.vpp_id()),
+ ],
+ )
+ policy.add_vpp_config()
+ self.configure_policies(self.pg1, [policy], [], [])
+
+ passing = [
+ self.base_ip_packet() / ICMP(type=8),
+ self.base_ip_packet(src_ip2=True) / ICMP(type=8),
+ self.base_ip_packet() / TCP(sport=1, dport=8080) / random_payload(),
+ ]
+ dropped = [
+ self.base_ip_packet() / ICMP(type=3),
+ self.base_ip_packet(src_ip2=True)
+ / TCP(sport=1, dport=8080)
+ / random_payload(),
+ self.base_ip_packet() / UDP(sport=1, dport=8080) / random_payload(),
+ self.base_ip_packet() / TCP(sport=1, dport=8081) / random_payload(),
+ ]
+ self.send_test_packets(self.pg0, self.pg1, passing, dropped)
+ # Cleanup
+ self.configure_policies(self.pg1, [], [], [], 0, 0)
+ policy.remove_vpp_config()
+ rule1.remove_vpp_config()
+ rule2.remove_vpp_config()
+ src_ipset.remove_vpp_config()