Range classifier is introduced to support filters based
on ranges. Only port-range filters are supported currently.
This can be combined with flower classifier to support a
combination of port-ranges and other parameters based
on existing fields supported by cls_flower.

Example:
1. Match on a port range:
-----------------------
$ tc filter add dev enp4s0 protocol ip parent ffff: prio 2 range\
ip_proto tcp dst_port 1-15 skip_hw action drop

$ tc -s filter show dev enp4s0 parent ffff:
filter protocol ip pref 2 range chain 0
filter protocol ip pref 2 range chain 0 handle 0x1
  eth_type ipv4
  ip_proto tcp
  dst_port_min 1
  dst_port_max 15
  skip_hw
  not_in_hw
        action order 1: gact action drop
         random type none pass val 0
         index 1 ref 1 bind 1 installed 34 sec used 2 sec
        Action statistics:
        Sent 1380 bytes 30 pkt (dropped 30, overlimits 0 requeues 0)
        backlog 0b 0p requeues 0

2. Match on IP address and port range:
--------------------------------------
$ tc filter add dev enp4s0 protocol ip parent ffff: prio 2 flower\
  dst_ip 192.168.1.1 skip_hw action goto chain 11

$ tc filter add dev enp4s0 protocol ip parent ffff: prio 2 chain 11\
  range ip_proto tcp dst_port 1-15 action drop

$ tc -s filter show dev enp4s0 parent ffff:
filter protocol ip pref 2 flower chain 0
filter protocol ip pref 2 flower chain 0 handle 0x1
  eth_type ipv4
  dst_ip 192.168.1.1
  skip_hw
  not_in_hw
        action order 1: gact action goto chain 11
         random type none pass val 0
         index 1 ref 1 bind 1 installed 1426 sec used 2 sec
        Action statistics:
        Sent 460 bytes 10 pkt (dropped 0, overlimits 0 requeues 0)
        backlog 0b 0p requeues 0

filter protocol ip pref 2 range chain 11
filter protocol ip pref 2 range chain 11 handle 0x1
  eth_type ipv4
  ip_proto tcp
  dst_port_min 1
  dst_port_max 15
  not_in_hw
        action order 1: gact action drop
         random type none pass val 0
         index 2 ref 1 bind 1 installed 1310 sec used 2 sec
        Action statistics:
        Sent 460 bytes 10 pkt (dropped 10, overlimits 0 requeues 0)
        backlog 0b 0p requeues 0

Signed-off-by: Amritha Nambiar <amritha.namb...@intel.com>
---
 include/uapi/linux/pkt_cls.h |   19 ++
 tc/Makefile                  |    1 
 tc/f_range.c                 |  369 ++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 389 insertions(+)
 create mode 100644 tc/f_range.c

diff --git a/include/uapi/linux/pkt_cls.h b/include/uapi/linux/pkt_cls.h
index be382fb..8ef3a5a 100644
--- a/include/uapi/linux/pkt_cls.h
+++ b/include/uapi/linux/pkt_cls.h
@@ -379,6 +379,25 @@ enum {
 
 #define TCA_BPF_MAX (__TCA_BPF_MAX - 1)
 
+/* RANGE classifier */
+
+enum {
+       TCA_RANGE_UNSPEC,
+       TCA_RANGE_CLASSID,              /* u32 */
+       TCA_RANGE_INDEV,
+       TCA_RANGE_ACT,
+       TCA_RANGE_KEY_ETH_TYPE,         /* be16 */
+       TCA_RANGE_KEY_IP_PROTO,         /* u8 */
+       TCA_RANGE_KEY_PORT_SRC_MIN,     /* be16 */
+       TCA_RANGE_KEY_PORT_SRC_MAX,     /* be16 */
+       TCA_RANGE_KEY_PORT_DST_MIN,     /* be16 */
+       TCA_RANGE_KEY_PORT_DST_MAX,     /* be16 */
+       TCA_RANGE_FLAGS,                /* u32 */
+       __TCA_RANGE_MAX,
+};
+
+#define TCA_RANGE_MAX (__TCA_RANGE_MAX - 1)
+
 /* Flower classifier */
 
 enum {
diff --git a/tc/Makefile b/tc/Makefile
index 5a1a7ff..155cabe 100644
--- a/tc/Makefile
+++ b/tc/Makefile
@@ -29,6 +29,7 @@ TCMODULES += f_bpf.o
 TCMODULES += f_flow.o
 TCMODULES += f_cgroup.o
 TCMODULES += f_flower.o
+TCMODULES += f_range.o
 TCMODULES += q_dsmark.o
 TCMODULES += q_gred.o
 TCMODULES += f_tcindex.o
diff --git a/tc/f_range.c b/tc/f_range.c
new file mode 100644
index 0000000..388b275
--- /dev/null
+++ b/tc/f_range.c
@@ -0,0 +1,369 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * f_range.c           Range Classifier
+ *
+ *             This program is free software; you can distribute it and/or
+ *             modify it under the terms of the GNU General Public License
+ *             as published by the Free Software Foundation; either version
+ *             2 of the License, or (at your option) any later version.
+ *
+ * Authors:    Amritha Nambiar <amritha.namb...@intel.com>
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <linux/if_arp.h>
+
+#include "utils.h"
+#include "tc_util.h"
+
+enum range_type {
+       RANGE_PORT_SRC,
+       RANGE_PORT_DST
+};
+
+struct range_values {
+       __be16 min_port_type;
+       __be16 max_port_type;
+};
+
+static void explain(void)
+{
+       fprintf(stderr, "Usage: ... range [ MATCH-LIST ]\n");
+       fprintf(stderr, "                 [skip_sw | skip_hw]\n");
+       fprintf(stderr, "                 [ action ACTION_SPEC ] [ classid 
CLASSID ]\n");
+       fprintf(stderr, "\n");
+       fprintf(stderr, "Where: SELECTOR := SAMPLE SAMPLE ...\n");
+       fprintf(stderr, "       FILTERID := X:Y:Z\n");
+       fprintf(stderr, "       ACTION_SPEC := ... look at individual 
actions\n");
+       fprintf(stderr, "\nNOTE: CLASSID is parsed as hexadecimal input.\n");
+}
+
+static int range_parse_ip_proto(char *str, __be16 eth_type, int type,
+                               __u8 *p_ip_proto, struct nlmsghdr *n)
+{
+       int ret;
+       __u8 ip_proto;
+
+       if (eth_type != htons(ETH_P_IP) && eth_type != htons(ETH_P_IPV6))
+               goto err;
+
+       if (matches(str, "tcp") == 0) {
+               ip_proto = IPPROTO_TCP;
+       } else if (matches(str, "udp") == 0) {
+               ip_proto = IPPROTO_UDP;
+       } else if (matches(str, "sctp") == 0) {
+               ip_proto = IPPROTO_SCTP;
+       } else if (matches(str, "icmp") == 0) {
+               if (eth_type != htons(ETH_P_IP))
+                       goto err;
+               ip_proto = IPPROTO_ICMP;
+       } else if (matches(str, "icmpv6") == 0) {
+               if (eth_type != htons(ETH_P_IPV6))
+                       goto err;
+               ip_proto = IPPROTO_ICMPV6;
+       } else {
+               ret = get_u8(&ip_proto, str, 16);
+               if (ret)
+                       return -1;
+       }
+       addattr8(n, MAX_MSG, type, ip_proto);
+       *p_ip_proto = ip_proto;
+       return 0;
+
+err:
+       fprintf(stderr, "Illegal \"eth_type\" for ip proto\n");
+       return -1;
+}
+
+static int range_port_attr_type(__u8 ip_proto, enum range_type type,
+                               struct range_values *range)
+{
+       if (ip_proto == IPPROTO_TCP || ip_proto == IPPROTO_UDP ||
+           ip_proto == IPPROTO_SCTP) {
+               if (type == RANGE_PORT_SRC) {
+                       range->min_port_type = TCA_RANGE_KEY_PORT_SRC_MIN;
+                       range->max_port_type = TCA_RANGE_KEY_PORT_SRC_MAX;
+               } else {
+                       range->min_port_type = TCA_RANGE_KEY_PORT_DST_MIN;
+                       range->max_port_type = TCA_RANGE_KEY_PORT_DST_MAX;
+               }
+       } else {
+               return -1;
+       }
+
+       return 0;
+}
+
+static int range_parse_port_range(__be16 *min, __be16 *max, __u8 ip_proto,
+                                 enum range_type type, struct nlmsghdr *n)
+{
+       struct range_values range;
+
+       range_port_attr_type(ip_proto, type, &range);
+
+       addattr16(n, MAX_MSG, range.min_port_type, *min);
+       addattr16(n, MAX_MSG, range.max_port_type, *max);
+
+       return 0;
+}
+
+static int get_range(__be16 *min, __be16 *max, char *argv)
+{
+       char *r;
+
+       r = strchr(argv, '-');
+       if (r) {
+               *r = '\0';
+               if (get_be16(min, argv, 10)) {
+                       fprintf(stderr, "invalid min range\n");
+                       return -1;
+               }
+               if (get_be16(max, r + 1, 10)) {
+                       fprintf(stderr, "invalid max range\n");
+                       return -1;
+               }
+       } else {
+               fprintf(stderr, "Illegal range format\n");
+               return -1;
+       }
+       return 0;
+}
+
+static int range_parse_opt(struct filter_util *qu, char *handle,
+                          int argc, char **argv, struct nlmsghdr *n)
+{
+       struct tcmsg *t = NLMSG_DATA(n);
+       struct rtattr *tail;
+       __be16 eth_type = TC_H_MIN(t->tcm_info);
+       __u8 ip_proto = 0xff;
+       __u32 flags = 0;
+       int ret;
+
+       if (handle) {
+               ret = get_u32(&t->tcm_handle, handle, 0);
+               if (ret) {
+                       fprintf(stderr, "Illegal \"handle\"\n");
+                       return -1;
+               }
+       }
+
+       tail = (struct rtattr *)(((void *)n) + NLMSG_ALIGN(n->nlmsg_len));
+       addattr_l(n, MAX_MSG, TCA_OPTIONS, NULL, 0);
+
+       if (argc == 0) {
+               /*at minimal we will match all ethertype packets */
+               goto parse_done;
+       }
+
+       while (argc > 0) {
+               if (matches(*argv, "classid") == 0 ||
+                   strcmp(*argv, "flowid") == 0) {
+                       unsigned int handle;
+
+                       NEXT_ARG();
+                       ret = get_tc_classid(&handle, *argv);
+                       if (ret) {
+                               fprintf(stderr, "Illegal \"classid\"\n");
+                               return -1;
+                       }
+                       addattr_l(n, MAX_MSG, TCA_RANGE_CLASSID, &handle, 4);
+               } else if (matches(*argv, "ip_proto") == 0) {
+                       NEXT_ARG();
+                       ret = range_parse_ip_proto(*argv, eth_type,
+                                                  TCA_RANGE_KEY_IP_PROTO,
+                                                  &ip_proto, n);
+                       if (ret < 0) {
+                               fprintf(stderr, "Illegal \"ip_proto\"\n");
+                               return -1;
+                       }
+               } else if (matches(*argv, "dst_port") == 0) {
+                       __be16 min, max;
+
+                       NEXT_ARG();
+                       ret = get_range(&min, &max, *argv);
+                       if (ret < 0)
+                               return -1;
+                       ret = range_parse_port_range(&min, &max, ip_proto,
+                                                    RANGE_PORT_DST, n);
+                       if (ret < 0) {
+                               fprintf(stderr, "Illegal \"dst_port range\"\n");
+                               return -1;
+                       }
+               } else if (matches(*argv, "src_port") == 0) {
+                       __be16 min, max;
+
+                       NEXT_ARG();
+                       ret = get_range(&min, &max, *argv);
+                       if (ret < 0)
+                               return -1;
+                       ret = range_parse_port_range(&min, &max, ip_proto,
+                                                    RANGE_PORT_SRC, n);
+                       if (ret < 0) {
+                               fprintf(stderr, "Illegal \"src_port range\"\n");
+                               return -1;
+                       }
+               } else if (matches(*argv, "skip_hw") == 0) {
+                       flags |= TCA_CLS_FLAGS_SKIP_HW;
+               } else if (matches(*argv, "skip_sw") == 0) {
+                       flags |= TCA_CLS_FLAGS_SKIP_SW;
+               } else if (matches(*argv, "action") == 0) {
+                       NEXT_ARG();
+                       if (parse_action(&argc, &argv, TCA_RANGE_ACT, n)) {
+                               fprintf(stderr, "Illegal \"action\"\n");
+                               return -1;
+                       }
+                       continue;
+               } else if (strcmp(*argv, "help") == 0) {
+                       explain();
+                       return -1;
+               } else {
+                       fprintf(stderr, "What is \"%s\"?\n", *argv);
+                       explain();
+                       return -1;
+               }
+               argc--; argv++;
+       }
+parse_done:
+       ret = addattr32(n, MAX_MSG, TCA_RANGE_FLAGS, flags);
+       if (ret)
+               return ret;
+
+       if (eth_type != htons(ETH_P_ALL)) {
+               ret = addattr16(n, MAX_MSG, TCA_RANGE_KEY_ETH_TYPE, eth_type);
+               if (ret)
+                       return ret;
+       }
+
+       tail->rta_len = (((void *)n) + n->nlmsg_len) - (void *)tail;
+       return 0;
+}
+
+static void range_print_eth_type(__be16 *p_eth_type,
+                                struct rtattr *eth_type_attr)
+{
+       SPRINT_BUF(out);
+       __be16 eth_type;
+
+       if (!eth_type_attr)
+               return;
+
+       eth_type = rta_getattr_u16(eth_type_attr);
+       if (eth_type == htons(ETH_P_IP))
+               sprintf(out, "ipv4");
+       else if (eth_type == htons(ETH_P_IPV6))
+               sprintf(out, "ipv6");
+       else if (eth_type == htons(ETH_P_ARP))
+               sprintf(out, "arp");
+       else if (eth_type == htons(ETH_P_RARP))
+               sprintf(out, "rarp");
+       else
+               sprintf(out, "%04x", ntohs(eth_type));
+
+       print_string(PRINT_ANY, "eth_type", "\n  eth_type %s", out);
+       *p_eth_type = eth_type;
+}
+
+static void range_print_ip_proto(__u8 *p_ip_proto, struct rtattr 
*ip_proto_attr)
+{
+       SPRINT_BUF(out);
+       __u8 ip_proto;
+
+       if (!ip_proto_attr)
+               return;
+
+       ip_proto = rta_getattr_u8(ip_proto_attr);
+       if (ip_proto == IPPROTO_TCP)
+               sprintf(out, "tcp");
+       else if (ip_proto == IPPROTO_UDP)
+               sprintf(out, "udp");
+       else if (ip_proto == IPPROTO_SCTP)
+               sprintf(out, "sctp");
+       else if (ip_proto == IPPROTO_ICMP)
+               sprintf(out, "icmp");
+       else if (ip_proto == IPPROTO_ICMPV6)
+               sprintf(out, "icmpv6");
+       else
+               sprintf(out, "%02x", ip_proto);
+
+       print_string(PRINT_ANY, "ip_proto", "\n  ip_proto %s", out);
+       *p_ip_proto = ip_proto;
+}
+
+static void range_print_port_range(char *name, struct rtattr *attr)
+{
+       SPRINT_BUF(namefrm);
+
+       if (!attr)
+               return;
+
+       sprintf(namefrm, "\n  %s %%u", name);
+       print_uint(PRINT_ANY, name, namefrm, rta_getattr_be16(attr));
+}
+
+static int range_print_opt(struct filter_util *qu, FILE *f,
+                          struct rtattr *opt, __u32 handle)
+{
+       struct rtattr *tb[TCA_RANGE_MAX + 1];
+       struct range_values range;
+       __be16 eth_type = 0;
+       __u8 ip_proto = 0xff;
+
+       if (!opt)
+               return 0;
+
+       parse_rtattr_nested(tb, TCA_RANGE_MAX, opt);
+
+       if (handle)
+               print_uint(PRINT_ANY, "handle", "handle 0x%x ", handle);
+
+       if (tb[TCA_RANGE_CLASSID]) {
+               __u32 h = rta_getattr_u32(tb[TCA_RANGE_CLASSID]);
+
+               SPRINT_BUF(b1);
+               print_string(PRINT_ANY, "classid", "classid %s ",
+                            sprint_tc_classid(h, b1));
+       }
+
+       range_print_eth_type(&eth_type, tb[TCA_RANGE_KEY_ETH_TYPE]);
+       range_print_ip_proto(&ip_proto, tb[TCA_RANGE_KEY_IP_PROTO]);
+
+       if (range_port_attr_type(ip_proto, RANGE_PORT_DST, &range) == 0) {
+               range_print_port_range("dst_port_min", tb[range.min_port_type]);
+               range_print_port_range("dst_port_max", tb[range.max_port_type]);
+       }
+
+       if (range_port_attr_type(ip_proto, RANGE_PORT_SRC, &range) == 0) {
+               range_print_port_range("src_port_min", tb[range.min_port_type]);
+               range_print_port_range("src_port_max", tb[range.max_port_type]);
+       }
+
+       if (tb[TCA_RANGE_FLAGS]) {
+               __u32 flags = rta_getattr_u32(tb[TCA_RANGE_FLAGS]);
+
+               if (flags & TCA_CLS_FLAGS_SKIP_HW)
+                       print_bool(PRINT_ANY, "skip_hw", "\n  skip_hw", true);
+               if (flags & TCA_CLS_FLAGS_SKIP_SW)
+                       print_bool(PRINT_ANY, "skip_sw", "\n  skip_sw", true);
+
+               if (flags & TCA_CLS_FLAGS_IN_HW)
+                       print_bool(PRINT_ANY, "in_hw", "\n  in_hw", true);
+               else if (flags & TCA_CLS_FLAGS_NOT_IN_HW)
+                       print_bool(PRINT_ANY, "not_in_hw", "\n  not_in_hw",
+                                  true);
+       }
+
+       if (tb[TCA_RANGE_ACT])
+               tc_print_action(f, tb[TCA_RANGE_ACT], 0);
+
+       return 0;
+}
+
+struct filter_util range_filter_util = {
+       .id = "range",
+       .parse_fopt = range_parse_opt,
+       .print_fopt = range_print_opt,
+};

Reply via email to