All of lore.kernel.org
 help / color / mirror / Atom feed
* [iproute2,RFC PATCH] tc: range: Introduce TC range classifier
@ 2018-09-13 20:54 Amritha Nambiar
  0 siblings, 0 replies; only message in thread
From: Amritha Nambiar @ 2018-09-13 20:54 UTC (permalink / raw)
  To: stephen, netdev
  Cc: alexander.h.duyck, jakub.kicinski, amritha.nambiar,
	sridhar.samudrala, jhs, jesse.brandeburg, jiri, xiyou.wangcong

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.nambiar@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.nambiar@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,
+};

^ permalink raw reply related	[flat|nested] only message in thread

only message in thread, other threads:[~2018-09-14  7:07 UTC | newest]

Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2018-09-13 20:54 [iproute2,RFC PATCH] tc: range: Introduce TC range classifier Amritha Nambiar

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.