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(ð_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, +};