162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * net/sched/cls_flow.c		Generic flow classifier
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (c) 2007, 2008 Patrick McHardy <kaber@trash.net>
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci#include <linux/kernel.h>
962306a36Sopenharmony_ci#include <linux/init.h>
1062306a36Sopenharmony_ci#include <linux/list.h>
1162306a36Sopenharmony_ci#include <linux/jhash.h>
1262306a36Sopenharmony_ci#include <linux/random.h>
1362306a36Sopenharmony_ci#include <linux/pkt_cls.h>
1462306a36Sopenharmony_ci#include <linux/skbuff.h>
1562306a36Sopenharmony_ci#include <linux/in.h>
1662306a36Sopenharmony_ci#include <linux/ip.h>
1762306a36Sopenharmony_ci#include <linux/ipv6.h>
1862306a36Sopenharmony_ci#include <linux/if_vlan.h>
1962306a36Sopenharmony_ci#include <linux/slab.h>
2062306a36Sopenharmony_ci#include <linux/module.h>
2162306a36Sopenharmony_ci#include <net/inet_sock.h>
2262306a36Sopenharmony_ci
2362306a36Sopenharmony_ci#include <net/pkt_cls.h>
2462306a36Sopenharmony_ci#include <net/ip.h>
2562306a36Sopenharmony_ci#include <net/route.h>
2662306a36Sopenharmony_ci#include <net/flow_dissector.h>
2762306a36Sopenharmony_ci#include <net/tc_wrapper.h>
2862306a36Sopenharmony_ci
2962306a36Sopenharmony_ci#if IS_ENABLED(CONFIG_NF_CONNTRACK)
3062306a36Sopenharmony_ci#include <net/netfilter/nf_conntrack.h>
3162306a36Sopenharmony_ci#endif
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_cistruct flow_head {
3462306a36Sopenharmony_ci	struct list_head	filters;
3562306a36Sopenharmony_ci	struct rcu_head		rcu;
3662306a36Sopenharmony_ci};
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_cistruct flow_filter {
3962306a36Sopenharmony_ci	struct list_head	list;
4062306a36Sopenharmony_ci	struct tcf_exts		exts;
4162306a36Sopenharmony_ci	struct tcf_ematch_tree	ematches;
4262306a36Sopenharmony_ci	struct tcf_proto	*tp;
4362306a36Sopenharmony_ci	struct timer_list	perturb_timer;
4462306a36Sopenharmony_ci	u32			perturb_period;
4562306a36Sopenharmony_ci	u32			handle;
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_ci	u32			nkeys;
4862306a36Sopenharmony_ci	u32			keymask;
4962306a36Sopenharmony_ci	u32			mode;
5062306a36Sopenharmony_ci	u32			mask;
5162306a36Sopenharmony_ci	u32			xor;
5262306a36Sopenharmony_ci	u32			rshift;
5362306a36Sopenharmony_ci	u32			addend;
5462306a36Sopenharmony_ci	u32			divisor;
5562306a36Sopenharmony_ci	u32			baseclass;
5662306a36Sopenharmony_ci	u32			hashrnd;
5762306a36Sopenharmony_ci	struct rcu_work		rwork;
5862306a36Sopenharmony_ci};
5962306a36Sopenharmony_ci
6062306a36Sopenharmony_cistatic inline u32 addr_fold(void *addr)
6162306a36Sopenharmony_ci{
6262306a36Sopenharmony_ci	unsigned long a = (unsigned long)addr;
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_ci	return (a & 0xFFFFFFFF) ^ (BITS_PER_LONG > 32 ? a >> 32 : 0);
6562306a36Sopenharmony_ci}
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_cistatic u32 flow_get_src(const struct sk_buff *skb, const struct flow_keys *flow)
6862306a36Sopenharmony_ci{
6962306a36Sopenharmony_ci	__be32 src = flow_get_u32_src(flow);
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_ci	if (src)
7262306a36Sopenharmony_ci		return ntohl(src);
7362306a36Sopenharmony_ci
7462306a36Sopenharmony_ci	return addr_fold(skb->sk);
7562306a36Sopenharmony_ci}
7662306a36Sopenharmony_ci
7762306a36Sopenharmony_cistatic u32 flow_get_dst(const struct sk_buff *skb, const struct flow_keys *flow)
7862306a36Sopenharmony_ci{
7962306a36Sopenharmony_ci	__be32 dst = flow_get_u32_dst(flow);
8062306a36Sopenharmony_ci
8162306a36Sopenharmony_ci	if (dst)
8262306a36Sopenharmony_ci		return ntohl(dst);
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_ci	return addr_fold(skb_dst(skb)) ^ (__force u16)skb_protocol(skb, true);
8562306a36Sopenharmony_ci}
8662306a36Sopenharmony_ci
8762306a36Sopenharmony_cistatic u32 flow_get_proto(const struct sk_buff *skb,
8862306a36Sopenharmony_ci			  const struct flow_keys *flow)
8962306a36Sopenharmony_ci{
9062306a36Sopenharmony_ci	return flow->basic.ip_proto;
9162306a36Sopenharmony_ci}
9262306a36Sopenharmony_ci
9362306a36Sopenharmony_cistatic u32 flow_get_proto_src(const struct sk_buff *skb,
9462306a36Sopenharmony_ci			      const struct flow_keys *flow)
9562306a36Sopenharmony_ci{
9662306a36Sopenharmony_ci	if (flow->ports.ports)
9762306a36Sopenharmony_ci		return ntohs(flow->ports.src);
9862306a36Sopenharmony_ci
9962306a36Sopenharmony_ci	return addr_fold(skb->sk);
10062306a36Sopenharmony_ci}
10162306a36Sopenharmony_ci
10262306a36Sopenharmony_cistatic u32 flow_get_proto_dst(const struct sk_buff *skb,
10362306a36Sopenharmony_ci			      const struct flow_keys *flow)
10462306a36Sopenharmony_ci{
10562306a36Sopenharmony_ci	if (flow->ports.ports)
10662306a36Sopenharmony_ci		return ntohs(flow->ports.dst);
10762306a36Sopenharmony_ci
10862306a36Sopenharmony_ci	return addr_fold(skb_dst(skb)) ^ (__force u16)skb_protocol(skb, true);
10962306a36Sopenharmony_ci}
11062306a36Sopenharmony_ci
11162306a36Sopenharmony_cistatic u32 flow_get_iif(const struct sk_buff *skb)
11262306a36Sopenharmony_ci{
11362306a36Sopenharmony_ci	return skb->skb_iif;
11462306a36Sopenharmony_ci}
11562306a36Sopenharmony_ci
11662306a36Sopenharmony_cistatic u32 flow_get_priority(const struct sk_buff *skb)
11762306a36Sopenharmony_ci{
11862306a36Sopenharmony_ci	return skb->priority;
11962306a36Sopenharmony_ci}
12062306a36Sopenharmony_ci
12162306a36Sopenharmony_cistatic u32 flow_get_mark(const struct sk_buff *skb)
12262306a36Sopenharmony_ci{
12362306a36Sopenharmony_ci	return skb->mark;
12462306a36Sopenharmony_ci}
12562306a36Sopenharmony_ci
12662306a36Sopenharmony_cistatic u32 flow_get_nfct(const struct sk_buff *skb)
12762306a36Sopenharmony_ci{
12862306a36Sopenharmony_ci#if IS_ENABLED(CONFIG_NF_CONNTRACK)
12962306a36Sopenharmony_ci	return addr_fold(skb_nfct(skb));
13062306a36Sopenharmony_ci#else
13162306a36Sopenharmony_ci	return 0;
13262306a36Sopenharmony_ci#endif
13362306a36Sopenharmony_ci}
13462306a36Sopenharmony_ci
13562306a36Sopenharmony_ci#if IS_ENABLED(CONFIG_NF_CONNTRACK)
13662306a36Sopenharmony_ci#define CTTUPLE(skb, member)						\
13762306a36Sopenharmony_ci({									\
13862306a36Sopenharmony_ci	enum ip_conntrack_info ctinfo;					\
13962306a36Sopenharmony_ci	const struct nf_conn *ct = nf_ct_get(skb, &ctinfo);		\
14062306a36Sopenharmony_ci	if (ct == NULL)							\
14162306a36Sopenharmony_ci		goto fallback;						\
14262306a36Sopenharmony_ci	ct->tuplehash[CTINFO2DIR(ctinfo)].tuple.member;			\
14362306a36Sopenharmony_ci})
14462306a36Sopenharmony_ci#else
14562306a36Sopenharmony_ci#define CTTUPLE(skb, member)						\
14662306a36Sopenharmony_ci({									\
14762306a36Sopenharmony_ci	goto fallback;							\
14862306a36Sopenharmony_ci	0;								\
14962306a36Sopenharmony_ci})
15062306a36Sopenharmony_ci#endif
15162306a36Sopenharmony_ci
15262306a36Sopenharmony_cistatic u32 flow_get_nfct_src(const struct sk_buff *skb,
15362306a36Sopenharmony_ci			     const struct flow_keys *flow)
15462306a36Sopenharmony_ci{
15562306a36Sopenharmony_ci	switch (skb_protocol(skb, true)) {
15662306a36Sopenharmony_ci	case htons(ETH_P_IP):
15762306a36Sopenharmony_ci		return ntohl(CTTUPLE(skb, src.u3.ip));
15862306a36Sopenharmony_ci	case htons(ETH_P_IPV6):
15962306a36Sopenharmony_ci		return ntohl(CTTUPLE(skb, src.u3.ip6[3]));
16062306a36Sopenharmony_ci	}
16162306a36Sopenharmony_cifallback:
16262306a36Sopenharmony_ci	return flow_get_src(skb, flow);
16362306a36Sopenharmony_ci}
16462306a36Sopenharmony_ci
16562306a36Sopenharmony_cistatic u32 flow_get_nfct_dst(const struct sk_buff *skb,
16662306a36Sopenharmony_ci			     const struct flow_keys *flow)
16762306a36Sopenharmony_ci{
16862306a36Sopenharmony_ci	switch (skb_protocol(skb, true)) {
16962306a36Sopenharmony_ci	case htons(ETH_P_IP):
17062306a36Sopenharmony_ci		return ntohl(CTTUPLE(skb, dst.u3.ip));
17162306a36Sopenharmony_ci	case htons(ETH_P_IPV6):
17262306a36Sopenharmony_ci		return ntohl(CTTUPLE(skb, dst.u3.ip6[3]));
17362306a36Sopenharmony_ci	}
17462306a36Sopenharmony_cifallback:
17562306a36Sopenharmony_ci	return flow_get_dst(skb, flow);
17662306a36Sopenharmony_ci}
17762306a36Sopenharmony_ci
17862306a36Sopenharmony_cistatic u32 flow_get_nfct_proto_src(const struct sk_buff *skb,
17962306a36Sopenharmony_ci				   const struct flow_keys *flow)
18062306a36Sopenharmony_ci{
18162306a36Sopenharmony_ci	return ntohs(CTTUPLE(skb, src.u.all));
18262306a36Sopenharmony_cifallback:
18362306a36Sopenharmony_ci	return flow_get_proto_src(skb, flow);
18462306a36Sopenharmony_ci}
18562306a36Sopenharmony_ci
18662306a36Sopenharmony_cistatic u32 flow_get_nfct_proto_dst(const struct sk_buff *skb,
18762306a36Sopenharmony_ci				   const struct flow_keys *flow)
18862306a36Sopenharmony_ci{
18962306a36Sopenharmony_ci	return ntohs(CTTUPLE(skb, dst.u.all));
19062306a36Sopenharmony_cifallback:
19162306a36Sopenharmony_ci	return flow_get_proto_dst(skb, flow);
19262306a36Sopenharmony_ci}
19362306a36Sopenharmony_ci
19462306a36Sopenharmony_cistatic u32 flow_get_rtclassid(const struct sk_buff *skb)
19562306a36Sopenharmony_ci{
19662306a36Sopenharmony_ci#ifdef CONFIG_IP_ROUTE_CLASSID
19762306a36Sopenharmony_ci	if (skb_dst(skb))
19862306a36Sopenharmony_ci		return skb_dst(skb)->tclassid;
19962306a36Sopenharmony_ci#endif
20062306a36Sopenharmony_ci	return 0;
20162306a36Sopenharmony_ci}
20262306a36Sopenharmony_ci
20362306a36Sopenharmony_cistatic u32 flow_get_skuid(const struct sk_buff *skb)
20462306a36Sopenharmony_ci{
20562306a36Sopenharmony_ci	struct sock *sk = skb_to_full_sk(skb);
20662306a36Sopenharmony_ci
20762306a36Sopenharmony_ci	if (sk && sk->sk_socket && sk->sk_socket->file) {
20862306a36Sopenharmony_ci		kuid_t skuid = sk->sk_socket->file->f_cred->fsuid;
20962306a36Sopenharmony_ci
21062306a36Sopenharmony_ci		return from_kuid(&init_user_ns, skuid);
21162306a36Sopenharmony_ci	}
21262306a36Sopenharmony_ci	return 0;
21362306a36Sopenharmony_ci}
21462306a36Sopenharmony_ci
21562306a36Sopenharmony_cistatic u32 flow_get_skgid(const struct sk_buff *skb)
21662306a36Sopenharmony_ci{
21762306a36Sopenharmony_ci	struct sock *sk = skb_to_full_sk(skb);
21862306a36Sopenharmony_ci
21962306a36Sopenharmony_ci	if (sk && sk->sk_socket && sk->sk_socket->file) {
22062306a36Sopenharmony_ci		kgid_t skgid = sk->sk_socket->file->f_cred->fsgid;
22162306a36Sopenharmony_ci
22262306a36Sopenharmony_ci		return from_kgid(&init_user_ns, skgid);
22362306a36Sopenharmony_ci	}
22462306a36Sopenharmony_ci	return 0;
22562306a36Sopenharmony_ci}
22662306a36Sopenharmony_ci
22762306a36Sopenharmony_cistatic u32 flow_get_vlan_tag(const struct sk_buff *skb)
22862306a36Sopenharmony_ci{
22962306a36Sopenharmony_ci	u16 tag;
23062306a36Sopenharmony_ci
23162306a36Sopenharmony_ci	if (vlan_get_tag(skb, &tag) < 0)
23262306a36Sopenharmony_ci		return 0;
23362306a36Sopenharmony_ci	return tag & VLAN_VID_MASK;
23462306a36Sopenharmony_ci}
23562306a36Sopenharmony_ci
23662306a36Sopenharmony_cistatic u32 flow_get_rxhash(struct sk_buff *skb)
23762306a36Sopenharmony_ci{
23862306a36Sopenharmony_ci	return skb_get_hash(skb);
23962306a36Sopenharmony_ci}
24062306a36Sopenharmony_ci
24162306a36Sopenharmony_cistatic u32 flow_key_get(struct sk_buff *skb, int key, struct flow_keys *flow)
24262306a36Sopenharmony_ci{
24362306a36Sopenharmony_ci	switch (key) {
24462306a36Sopenharmony_ci	case FLOW_KEY_SRC:
24562306a36Sopenharmony_ci		return flow_get_src(skb, flow);
24662306a36Sopenharmony_ci	case FLOW_KEY_DST:
24762306a36Sopenharmony_ci		return flow_get_dst(skb, flow);
24862306a36Sopenharmony_ci	case FLOW_KEY_PROTO:
24962306a36Sopenharmony_ci		return flow_get_proto(skb, flow);
25062306a36Sopenharmony_ci	case FLOW_KEY_PROTO_SRC:
25162306a36Sopenharmony_ci		return flow_get_proto_src(skb, flow);
25262306a36Sopenharmony_ci	case FLOW_KEY_PROTO_DST:
25362306a36Sopenharmony_ci		return flow_get_proto_dst(skb, flow);
25462306a36Sopenharmony_ci	case FLOW_KEY_IIF:
25562306a36Sopenharmony_ci		return flow_get_iif(skb);
25662306a36Sopenharmony_ci	case FLOW_KEY_PRIORITY:
25762306a36Sopenharmony_ci		return flow_get_priority(skb);
25862306a36Sopenharmony_ci	case FLOW_KEY_MARK:
25962306a36Sopenharmony_ci		return flow_get_mark(skb);
26062306a36Sopenharmony_ci	case FLOW_KEY_NFCT:
26162306a36Sopenharmony_ci		return flow_get_nfct(skb);
26262306a36Sopenharmony_ci	case FLOW_KEY_NFCT_SRC:
26362306a36Sopenharmony_ci		return flow_get_nfct_src(skb, flow);
26462306a36Sopenharmony_ci	case FLOW_KEY_NFCT_DST:
26562306a36Sopenharmony_ci		return flow_get_nfct_dst(skb, flow);
26662306a36Sopenharmony_ci	case FLOW_KEY_NFCT_PROTO_SRC:
26762306a36Sopenharmony_ci		return flow_get_nfct_proto_src(skb, flow);
26862306a36Sopenharmony_ci	case FLOW_KEY_NFCT_PROTO_DST:
26962306a36Sopenharmony_ci		return flow_get_nfct_proto_dst(skb, flow);
27062306a36Sopenharmony_ci	case FLOW_KEY_RTCLASSID:
27162306a36Sopenharmony_ci		return flow_get_rtclassid(skb);
27262306a36Sopenharmony_ci	case FLOW_KEY_SKUID:
27362306a36Sopenharmony_ci		return flow_get_skuid(skb);
27462306a36Sopenharmony_ci	case FLOW_KEY_SKGID:
27562306a36Sopenharmony_ci		return flow_get_skgid(skb);
27662306a36Sopenharmony_ci	case FLOW_KEY_VLAN_TAG:
27762306a36Sopenharmony_ci		return flow_get_vlan_tag(skb);
27862306a36Sopenharmony_ci	case FLOW_KEY_RXHASH:
27962306a36Sopenharmony_ci		return flow_get_rxhash(skb);
28062306a36Sopenharmony_ci	default:
28162306a36Sopenharmony_ci		WARN_ON(1);
28262306a36Sopenharmony_ci		return 0;
28362306a36Sopenharmony_ci	}
28462306a36Sopenharmony_ci}
28562306a36Sopenharmony_ci
28662306a36Sopenharmony_ci#define FLOW_KEYS_NEEDED ((1 << FLOW_KEY_SRC) | 		\
28762306a36Sopenharmony_ci			  (1 << FLOW_KEY_DST) |			\
28862306a36Sopenharmony_ci			  (1 << FLOW_KEY_PROTO) |		\
28962306a36Sopenharmony_ci			  (1 << FLOW_KEY_PROTO_SRC) |		\
29062306a36Sopenharmony_ci			  (1 << FLOW_KEY_PROTO_DST) | 		\
29162306a36Sopenharmony_ci			  (1 << FLOW_KEY_NFCT_SRC) |		\
29262306a36Sopenharmony_ci			  (1 << FLOW_KEY_NFCT_DST) |		\
29362306a36Sopenharmony_ci			  (1 << FLOW_KEY_NFCT_PROTO_SRC) |	\
29462306a36Sopenharmony_ci			  (1 << FLOW_KEY_NFCT_PROTO_DST))
29562306a36Sopenharmony_ci
29662306a36Sopenharmony_ciTC_INDIRECT_SCOPE int flow_classify(struct sk_buff *skb,
29762306a36Sopenharmony_ci				    const struct tcf_proto *tp,
29862306a36Sopenharmony_ci				    struct tcf_result *res)
29962306a36Sopenharmony_ci{
30062306a36Sopenharmony_ci	struct flow_head *head = rcu_dereference_bh(tp->root);
30162306a36Sopenharmony_ci	struct flow_filter *f;
30262306a36Sopenharmony_ci	u32 keymask;
30362306a36Sopenharmony_ci	u32 classid;
30462306a36Sopenharmony_ci	unsigned int n, key;
30562306a36Sopenharmony_ci	int r;
30662306a36Sopenharmony_ci
30762306a36Sopenharmony_ci	list_for_each_entry_rcu(f, &head->filters, list) {
30862306a36Sopenharmony_ci		u32 keys[FLOW_KEY_MAX + 1];
30962306a36Sopenharmony_ci		struct flow_keys flow_keys;
31062306a36Sopenharmony_ci
31162306a36Sopenharmony_ci		if (!tcf_em_tree_match(skb, &f->ematches, NULL))
31262306a36Sopenharmony_ci			continue;
31362306a36Sopenharmony_ci
31462306a36Sopenharmony_ci		keymask = f->keymask;
31562306a36Sopenharmony_ci		if (keymask & FLOW_KEYS_NEEDED)
31662306a36Sopenharmony_ci			skb_flow_dissect_flow_keys(skb, &flow_keys, 0);
31762306a36Sopenharmony_ci
31862306a36Sopenharmony_ci		for (n = 0; n < f->nkeys; n++) {
31962306a36Sopenharmony_ci			key = ffs(keymask) - 1;
32062306a36Sopenharmony_ci			keymask &= ~(1 << key);
32162306a36Sopenharmony_ci			keys[n] = flow_key_get(skb, key, &flow_keys);
32262306a36Sopenharmony_ci		}
32362306a36Sopenharmony_ci
32462306a36Sopenharmony_ci		if (f->mode == FLOW_MODE_HASH)
32562306a36Sopenharmony_ci			classid = jhash2(keys, f->nkeys, f->hashrnd);
32662306a36Sopenharmony_ci		else {
32762306a36Sopenharmony_ci			classid = keys[0];
32862306a36Sopenharmony_ci			classid = (classid & f->mask) ^ f->xor;
32962306a36Sopenharmony_ci			classid = (classid >> f->rshift) + f->addend;
33062306a36Sopenharmony_ci		}
33162306a36Sopenharmony_ci
33262306a36Sopenharmony_ci		if (f->divisor)
33362306a36Sopenharmony_ci			classid %= f->divisor;
33462306a36Sopenharmony_ci
33562306a36Sopenharmony_ci		res->class   = 0;
33662306a36Sopenharmony_ci		res->classid = TC_H_MAKE(f->baseclass, f->baseclass + classid);
33762306a36Sopenharmony_ci
33862306a36Sopenharmony_ci		r = tcf_exts_exec(skb, &f->exts, res);
33962306a36Sopenharmony_ci		if (r < 0)
34062306a36Sopenharmony_ci			continue;
34162306a36Sopenharmony_ci		return r;
34262306a36Sopenharmony_ci	}
34362306a36Sopenharmony_ci	return -1;
34462306a36Sopenharmony_ci}
34562306a36Sopenharmony_ci
34662306a36Sopenharmony_cistatic void flow_perturbation(struct timer_list *t)
34762306a36Sopenharmony_ci{
34862306a36Sopenharmony_ci	struct flow_filter *f = from_timer(f, t, perturb_timer);
34962306a36Sopenharmony_ci
35062306a36Sopenharmony_ci	get_random_bytes(&f->hashrnd, 4);
35162306a36Sopenharmony_ci	if (f->perturb_period)
35262306a36Sopenharmony_ci		mod_timer(&f->perturb_timer, jiffies + f->perturb_period);
35362306a36Sopenharmony_ci}
35462306a36Sopenharmony_ci
35562306a36Sopenharmony_cistatic const struct nla_policy flow_policy[TCA_FLOW_MAX + 1] = {
35662306a36Sopenharmony_ci	[TCA_FLOW_KEYS]		= { .type = NLA_U32 },
35762306a36Sopenharmony_ci	[TCA_FLOW_MODE]		= { .type = NLA_U32 },
35862306a36Sopenharmony_ci	[TCA_FLOW_BASECLASS]	= { .type = NLA_U32 },
35962306a36Sopenharmony_ci	[TCA_FLOW_RSHIFT]	= { .type = NLA_U32 },
36062306a36Sopenharmony_ci	[TCA_FLOW_ADDEND]	= { .type = NLA_U32 },
36162306a36Sopenharmony_ci	[TCA_FLOW_MASK]		= { .type = NLA_U32 },
36262306a36Sopenharmony_ci	[TCA_FLOW_XOR]		= { .type = NLA_U32 },
36362306a36Sopenharmony_ci	[TCA_FLOW_DIVISOR]	= { .type = NLA_U32 },
36462306a36Sopenharmony_ci	[TCA_FLOW_ACT]		= { .type = NLA_NESTED },
36562306a36Sopenharmony_ci	[TCA_FLOW_POLICE]	= { .type = NLA_NESTED },
36662306a36Sopenharmony_ci	[TCA_FLOW_EMATCHES]	= { .type = NLA_NESTED },
36762306a36Sopenharmony_ci	[TCA_FLOW_PERTURB]	= { .type = NLA_U32 },
36862306a36Sopenharmony_ci};
36962306a36Sopenharmony_ci
37062306a36Sopenharmony_cistatic void __flow_destroy_filter(struct flow_filter *f)
37162306a36Sopenharmony_ci{
37262306a36Sopenharmony_ci	timer_shutdown_sync(&f->perturb_timer);
37362306a36Sopenharmony_ci	tcf_exts_destroy(&f->exts);
37462306a36Sopenharmony_ci	tcf_em_tree_destroy(&f->ematches);
37562306a36Sopenharmony_ci	tcf_exts_put_net(&f->exts);
37662306a36Sopenharmony_ci	kfree(f);
37762306a36Sopenharmony_ci}
37862306a36Sopenharmony_ci
37962306a36Sopenharmony_cistatic void flow_destroy_filter_work(struct work_struct *work)
38062306a36Sopenharmony_ci{
38162306a36Sopenharmony_ci	struct flow_filter *f = container_of(to_rcu_work(work),
38262306a36Sopenharmony_ci					     struct flow_filter,
38362306a36Sopenharmony_ci					     rwork);
38462306a36Sopenharmony_ci	rtnl_lock();
38562306a36Sopenharmony_ci	__flow_destroy_filter(f);
38662306a36Sopenharmony_ci	rtnl_unlock();
38762306a36Sopenharmony_ci}
38862306a36Sopenharmony_ci
38962306a36Sopenharmony_cistatic int flow_change(struct net *net, struct sk_buff *in_skb,
39062306a36Sopenharmony_ci		       struct tcf_proto *tp, unsigned long base,
39162306a36Sopenharmony_ci		       u32 handle, struct nlattr **tca,
39262306a36Sopenharmony_ci		       void **arg, u32 flags,
39362306a36Sopenharmony_ci		       struct netlink_ext_ack *extack)
39462306a36Sopenharmony_ci{
39562306a36Sopenharmony_ci	struct flow_head *head = rtnl_dereference(tp->root);
39662306a36Sopenharmony_ci	struct flow_filter *fold, *fnew;
39762306a36Sopenharmony_ci	struct nlattr *opt = tca[TCA_OPTIONS];
39862306a36Sopenharmony_ci	struct nlattr *tb[TCA_FLOW_MAX + 1];
39962306a36Sopenharmony_ci	unsigned int nkeys = 0;
40062306a36Sopenharmony_ci	unsigned int perturb_period = 0;
40162306a36Sopenharmony_ci	u32 baseclass = 0;
40262306a36Sopenharmony_ci	u32 keymask = 0;
40362306a36Sopenharmony_ci	u32 mode;
40462306a36Sopenharmony_ci	int err;
40562306a36Sopenharmony_ci
40662306a36Sopenharmony_ci	if (opt == NULL)
40762306a36Sopenharmony_ci		return -EINVAL;
40862306a36Sopenharmony_ci
40962306a36Sopenharmony_ci	err = nla_parse_nested_deprecated(tb, TCA_FLOW_MAX, opt, flow_policy,
41062306a36Sopenharmony_ci					  NULL);
41162306a36Sopenharmony_ci	if (err < 0)
41262306a36Sopenharmony_ci		return err;
41362306a36Sopenharmony_ci
41462306a36Sopenharmony_ci	if (tb[TCA_FLOW_BASECLASS]) {
41562306a36Sopenharmony_ci		baseclass = nla_get_u32(tb[TCA_FLOW_BASECLASS]);
41662306a36Sopenharmony_ci		if (TC_H_MIN(baseclass) == 0)
41762306a36Sopenharmony_ci			return -EINVAL;
41862306a36Sopenharmony_ci	}
41962306a36Sopenharmony_ci
42062306a36Sopenharmony_ci	if (tb[TCA_FLOW_KEYS]) {
42162306a36Sopenharmony_ci		keymask = nla_get_u32(tb[TCA_FLOW_KEYS]);
42262306a36Sopenharmony_ci
42362306a36Sopenharmony_ci		nkeys = hweight32(keymask);
42462306a36Sopenharmony_ci		if (nkeys == 0)
42562306a36Sopenharmony_ci			return -EINVAL;
42662306a36Sopenharmony_ci
42762306a36Sopenharmony_ci		if (fls(keymask) - 1 > FLOW_KEY_MAX)
42862306a36Sopenharmony_ci			return -EOPNOTSUPP;
42962306a36Sopenharmony_ci
43062306a36Sopenharmony_ci		if ((keymask & (FLOW_KEY_SKUID|FLOW_KEY_SKGID)) &&
43162306a36Sopenharmony_ci		    sk_user_ns(NETLINK_CB(in_skb).sk) != &init_user_ns)
43262306a36Sopenharmony_ci			return -EOPNOTSUPP;
43362306a36Sopenharmony_ci	}
43462306a36Sopenharmony_ci
43562306a36Sopenharmony_ci	fnew = kzalloc(sizeof(*fnew), GFP_KERNEL);
43662306a36Sopenharmony_ci	if (!fnew)
43762306a36Sopenharmony_ci		return -ENOBUFS;
43862306a36Sopenharmony_ci
43962306a36Sopenharmony_ci	err = tcf_em_tree_validate(tp, tb[TCA_FLOW_EMATCHES], &fnew->ematches);
44062306a36Sopenharmony_ci	if (err < 0)
44162306a36Sopenharmony_ci		goto err1;
44262306a36Sopenharmony_ci
44362306a36Sopenharmony_ci	err = tcf_exts_init(&fnew->exts, net, TCA_FLOW_ACT, TCA_FLOW_POLICE);
44462306a36Sopenharmony_ci	if (err < 0)
44562306a36Sopenharmony_ci		goto err2;
44662306a36Sopenharmony_ci
44762306a36Sopenharmony_ci	err = tcf_exts_validate(net, tp, tb, tca[TCA_RATE], &fnew->exts, flags,
44862306a36Sopenharmony_ci				extack);
44962306a36Sopenharmony_ci	if (err < 0)
45062306a36Sopenharmony_ci		goto err2;
45162306a36Sopenharmony_ci
45262306a36Sopenharmony_ci	fold = *arg;
45362306a36Sopenharmony_ci	if (fold) {
45462306a36Sopenharmony_ci		err = -EINVAL;
45562306a36Sopenharmony_ci		if (fold->handle != handle && handle)
45662306a36Sopenharmony_ci			goto err2;
45762306a36Sopenharmony_ci
45862306a36Sopenharmony_ci		/* Copy fold into fnew */
45962306a36Sopenharmony_ci		fnew->tp = fold->tp;
46062306a36Sopenharmony_ci		fnew->handle = fold->handle;
46162306a36Sopenharmony_ci		fnew->nkeys = fold->nkeys;
46262306a36Sopenharmony_ci		fnew->keymask = fold->keymask;
46362306a36Sopenharmony_ci		fnew->mode = fold->mode;
46462306a36Sopenharmony_ci		fnew->mask = fold->mask;
46562306a36Sopenharmony_ci		fnew->xor = fold->xor;
46662306a36Sopenharmony_ci		fnew->rshift = fold->rshift;
46762306a36Sopenharmony_ci		fnew->addend = fold->addend;
46862306a36Sopenharmony_ci		fnew->divisor = fold->divisor;
46962306a36Sopenharmony_ci		fnew->baseclass = fold->baseclass;
47062306a36Sopenharmony_ci		fnew->hashrnd = fold->hashrnd;
47162306a36Sopenharmony_ci
47262306a36Sopenharmony_ci		mode = fold->mode;
47362306a36Sopenharmony_ci		if (tb[TCA_FLOW_MODE])
47462306a36Sopenharmony_ci			mode = nla_get_u32(tb[TCA_FLOW_MODE]);
47562306a36Sopenharmony_ci		if (mode != FLOW_MODE_HASH && nkeys > 1)
47662306a36Sopenharmony_ci			goto err2;
47762306a36Sopenharmony_ci
47862306a36Sopenharmony_ci		if (mode == FLOW_MODE_HASH)
47962306a36Sopenharmony_ci			perturb_period = fold->perturb_period;
48062306a36Sopenharmony_ci		if (tb[TCA_FLOW_PERTURB]) {
48162306a36Sopenharmony_ci			if (mode != FLOW_MODE_HASH)
48262306a36Sopenharmony_ci				goto err2;
48362306a36Sopenharmony_ci			perturb_period = nla_get_u32(tb[TCA_FLOW_PERTURB]) * HZ;
48462306a36Sopenharmony_ci		}
48562306a36Sopenharmony_ci	} else {
48662306a36Sopenharmony_ci		err = -EINVAL;
48762306a36Sopenharmony_ci		if (!handle)
48862306a36Sopenharmony_ci			goto err2;
48962306a36Sopenharmony_ci		if (!tb[TCA_FLOW_KEYS])
49062306a36Sopenharmony_ci			goto err2;
49162306a36Sopenharmony_ci
49262306a36Sopenharmony_ci		mode = FLOW_MODE_MAP;
49362306a36Sopenharmony_ci		if (tb[TCA_FLOW_MODE])
49462306a36Sopenharmony_ci			mode = nla_get_u32(tb[TCA_FLOW_MODE]);
49562306a36Sopenharmony_ci		if (mode != FLOW_MODE_HASH && nkeys > 1)
49662306a36Sopenharmony_ci			goto err2;
49762306a36Sopenharmony_ci
49862306a36Sopenharmony_ci		if (tb[TCA_FLOW_PERTURB]) {
49962306a36Sopenharmony_ci			if (mode != FLOW_MODE_HASH)
50062306a36Sopenharmony_ci				goto err2;
50162306a36Sopenharmony_ci			perturb_period = nla_get_u32(tb[TCA_FLOW_PERTURB]) * HZ;
50262306a36Sopenharmony_ci		}
50362306a36Sopenharmony_ci
50462306a36Sopenharmony_ci		if (TC_H_MAJ(baseclass) == 0) {
50562306a36Sopenharmony_ci			struct Qdisc *q = tcf_block_q(tp->chain->block);
50662306a36Sopenharmony_ci
50762306a36Sopenharmony_ci			baseclass = TC_H_MAKE(q->handle, baseclass);
50862306a36Sopenharmony_ci		}
50962306a36Sopenharmony_ci		if (TC_H_MIN(baseclass) == 0)
51062306a36Sopenharmony_ci			baseclass = TC_H_MAKE(baseclass, 1);
51162306a36Sopenharmony_ci
51262306a36Sopenharmony_ci		fnew->handle = handle;
51362306a36Sopenharmony_ci		fnew->mask  = ~0U;
51462306a36Sopenharmony_ci		fnew->tp = tp;
51562306a36Sopenharmony_ci		get_random_bytes(&fnew->hashrnd, 4);
51662306a36Sopenharmony_ci	}
51762306a36Sopenharmony_ci
51862306a36Sopenharmony_ci	timer_setup(&fnew->perturb_timer, flow_perturbation, TIMER_DEFERRABLE);
51962306a36Sopenharmony_ci
52062306a36Sopenharmony_ci	tcf_block_netif_keep_dst(tp->chain->block);
52162306a36Sopenharmony_ci
52262306a36Sopenharmony_ci	if (tb[TCA_FLOW_KEYS]) {
52362306a36Sopenharmony_ci		fnew->keymask = keymask;
52462306a36Sopenharmony_ci		fnew->nkeys   = nkeys;
52562306a36Sopenharmony_ci	}
52662306a36Sopenharmony_ci
52762306a36Sopenharmony_ci	fnew->mode = mode;
52862306a36Sopenharmony_ci
52962306a36Sopenharmony_ci	if (tb[TCA_FLOW_MASK])
53062306a36Sopenharmony_ci		fnew->mask = nla_get_u32(tb[TCA_FLOW_MASK]);
53162306a36Sopenharmony_ci	if (tb[TCA_FLOW_XOR])
53262306a36Sopenharmony_ci		fnew->xor = nla_get_u32(tb[TCA_FLOW_XOR]);
53362306a36Sopenharmony_ci	if (tb[TCA_FLOW_RSHIFT])
53462306a36Sopenharmony_ci		fnew->rshift = nla_get_u32(tb[TCA_FLOW_RSHIFT]);
53562306a36Sopenharmony_ci	if (tb[TCA_FLOW_ADDEND])
53662306a36Sopenharmony_ci		fnew->addend = nla_get_u32(tb[TCA_FLOW_ADDEND]);
53762306a36Sopenharmony_ci
53862306a36Sopenharmony_ci	if (tb[TCA_FLOW_DIVISOR])
53962306a36Sopenharmony_ci		fnew->divisor = nla_get_u32(tb[TCA_FLOW_DIVISOR]);
54062306a36Sopenharmony_ci	if (baseclass)
54162306a36Sopenharmony_ci		fnew->baseclass = baseclass;
54262306a36Sopenharmony_ci
54362306a36Sopenharmony_ci	fnew->perturb_period = perturb_period;
54462306a36Sopenharmony_ci	if (perturb_period)
54562306a36Sopenharmony_ci		mod_timer(&fnew->perturb_timer, jiffies + perturb_period);
54662306a36Sopenharmony_ci
54762306a36Sopenharmony_ci	if (!*arg)
54862306a36Sopenharmony_ci		list_add_tail_rcu(&fnew->list, &head->filters);
54962306a36Sopenharmony_ci	else
55062306a36Sopenharmony_ci		list_replace_rcu(&fold->list, &fnew->list);
55162306a36Sopenharmony_ci
55262306a36Sopenharmony_ci	*arg = fnew;
55362306a36Sopenharmony_ci
55462306a36Sopenharmony_ci	if (fold) {
55562306a36Sopenharmony_ci		tcf_exts_get_net(&fold->exts);
55662306a36Sopenharmony_ci		tcf_queue_work(&fold->rwork, flow_destroy_filter_work);
55762306a36Sopenharmony_ci	}
55862306a36Sopenharmony_ci	return 0;
55962306a36Sopenharmony_ci
56062306a36Sopenharmony_cierr2:
56162306a36Sopenharmony_ci	tcf_exts_destroy(&fnew->exts);
56262306a36Sopenharmony_ci	tcf_em_tree_destroy(&fnew->ematches);
56362306a36Sopenharmony_cierr1:
56462306a36Sopenharmony_ci	kfree(fnew);
56562306a36Sopenharmony_ci	return err;
56662306a36Sopenharmony_ci}
56762306a36Sopenharmony_ci
56862306a36Sopenharmony_cistatic int flow_delete(struct tcf_proto *tp, void *arg, bool *last,
56962306a36Sopenharmony_ci		       bool rtnl_held, struct netlink_ext_ack *extack)
57062306a36Sopenharmony_ci{
57162306a36Sopenharmony_ci	struct flow_head *head = rtnl_dereference(tp->root);
57262306a36Sopenharmony_ci	struct flow_filter *f = arg;
57362306a36Sopenharmony_ci
57462306a36Sopenharmony_ci	list_del_rcu(&f->list);
57562306a36Sopenharmony_ci	tcf_exts_get_net(&f->exts);
57662306a36Sopenharmony_ci	tcf_queue_work(&f->rwork, flow_destroy_filter_work);
57762306a36Sopenharmony_ci	*last = list_empty(&head->filters);
57862306a36Sopenharmony_ci	return 0;
57962306a36Sopenharmony_ci}
58062306a36Sopenharmony_ci
58162306a36Sopenharmony_cistatic int flow_init(struct tcf_proto *tp)
58262306a36Sopenharmony_ci{
58362306a36Sopenharmony_ci	struct flow_head *head;
58462306a36Sopenharmony_ci
58562306a36Sopenharmony_ci	head = kzalloc(sizeof(*head), GFP_KERNEL);
58662306a36Sopenharmony_ci	if (head == NULL)
58762306a36Sopenharmony_ci		return -ENOBUFS;
58862306a36Sopenharmony_ci	INIT_LIST_HEAD(&head->filters);
58962306a36Sopenharmony_ci	rcu_assign_pointer(tp->root, head);
59062306a36Sopenharmony_ci	return 0;
59162306a36Sopenharmony_ci}
59262306a36Sopenharmony_ci
59362306a36Sopenharmony_cistatic void flow_destroy(struct tcf_proto *tp, bool rtnl_held,
59462306a36Sopenharmony_ci			 struct netlink_ext_ack *extack)
59562306a36Sopenharmony_ci{
59662306a36Sopenharmony_ci	struct flow_head *head = rtnl_dereference(tp->root);
59762306a36Sopenharmony_ci	struct flow_filter *f, *next;
59862306a36Sopenharmony_ci
59962306a36Sopenharmony_ci	list_for_each_entry_safe(f, next, &head->filters, list) {
60062306a36Sopenharmony_ci		list_del_rcu(&f->list);
60162306a36Sopenharmony_ci		if (tcf_exts_get_net(&f->exts))
60262306a36Sopenharmony_ci			tcf_queue_work(&f->rwork, flow_destroy_filter_work);
60362306a36Sopenharmony_ci		else
60462306a36Sopenharmony_ci			__flow_destroy_filter(f);
60562306a36Sopenharmony_ci	}
60662306a36Sopenharmony_ci	kfree_rcu(head, rcu);
60762306a36Sopenharmony_ci}
60862306a36Sopenharmony_ci
60962306a36Sopenharmony_cistatic void *flow_get(struct tcf_proto *tp, u32 handle)
61062306a36Sopenharmony_ci{
61162306a36Sopenharmony_ci	struct flow_head *head = rtnl_dereference(tp->root);
61262306a36Sopenharmony_ci	struct flow_filter *f;
61362306a36Sopenharmony_ci
61462306a36Sopenharmony_ci	list_for_each_entry(f, &head->filters, list)
61562306a36Sopenharmony_ci		if (f->handle == handle)
61662306a36Sopenharmony_ci			return f;
61762306a36Sopenharmony_ci	return NULL;
61862306a36Sopenharmony_ci}
61962306a36Sopenharmony_ci
62062306a36Sopenharmony_cistatic int flow_dump(struct net *net, struct tcf_proto *tp, void *fh,
62162306a36Sopenharmony_ci		     struct sk_buff *skb, struct tcmsg *t, bool rtnl_held)
62262306a36Sopenharmony_ci{
62362306a36Sopenharmony_ci	struct flow_filter *f = fh;
62462306a36Sopenharmony_ci	struct nlattr *nest;
62562306a36Sopenharmony_ci
62662306a36Sopenharmony_ci	if (f == NULL)
62762306a36Sopenharmony_ci		return skb->len;
62862306a36Sopenharmony_ci
62962306a36Sopenharmony_ci	t->tcm_handle = f->handle;
63062306a36Sopenharmony_ci
63162306a36Sopenharmony_ci	nest = nla_nest_start_noflag(skb, TCA_OPTIONS);
63262306a36Sopenharmony_ci	if (nest == NULL)
63362306a36Sopenharmony_ci		goto nla_put_failure;
63462306a36Sopenharmony_ci
63562306a36Sopenharmony_ci	if (nla_put_u32(skb, TCA_FLOW_KEYS, f->keymask) ||
63662306a36Sopenharmony_ci	    nla_put_u32(skb, TCA_FLOW_MODE, f->mode))
63762306a36Sopenharmony_ci		goto nla_put_failure;
63862306a36Sopenharmony_ci
63962306a36Sopenharmony_ci	if (f->mask != ~0 || f->xor != 0) {
64062306a36Sopenharmony_ci		if (nla_put_u32(skb, TCA_FLOW_MASK, f->mask) ||
64162306a36Sopenharmony_ci		    nla_put_u32(skb, TCA_FLOW_XOR, f->xor))
64262306a36Sopenharmony_ci			goto nla_put_failure;
64362306a36Sopenharmony_ci	}
64462306a36Sopenharmony_ci	if (f->rshift &&
64562306a36Sopenharmony_ci	    nla_put_u32(skb, TCA_FLOW_RSHIFT, f->rshift))
64662306a36Sopenharmony_ci		goto nla_put_failure;
64762306a36Sopenharmony_ci	if (f->addend &&
64862306a36Sopenharmony_ci	    nla_put_u32(skb, TCA_FLOW_ADDEND, f->addend))
64962306a36Sopenharmony_ci		goto nla_put_failure;
65062306a36Sopenharmony_ci
65162306a36Sopenharmony_ci	if (f->divisor &&
65262306a36Sopenharmony_ci	    nla_put_u32(skb, TCA_FLOW_DIVISOR, f->divisor))
65362306a36Sopenharmony_ci		goto nla_put_failure;
65462306a36Sopenharmony_ci	if (f->baseclass &&
65562306a36Sopenharmony_ci	    nla_put_u32(skb, TCA_FLOW_BASECLASS, f->baseclass))
65662306a36Sopenharmony_ci		goto nla_put_failure;
65762306a36Sopenharmony_ci
65862306a36Sopenharmony_ci	if (f->perturb_period &&
65962306a36Sopenharmony_ci	    nla_put_u32(skb, TCA_FLOW_PERTURB, f->perturb_period / HZ))
66062306a36Sopenharmony_ci		goto nla_put_failure;
66162306a36Sopenharmony_ci
66262306a36Sopenharmony_ci	if (tcf_exts_dump(skb, &f->exts) < 0)
66362306a36Sopenharmony_ci		goto nla_put_failure;
66462306a36Sopenharmony_ci#ifdef CONFIG_NET_EMATCH
66562306a36Sopenharmony_ci	if (f->ematches.hdr.nmatches &&
66662306a36Sopenharmony_ci	    tcf_em_tree_dump(skb, &f->ematches, TCA_FLOW_EMATCHES) < 0)
66762306a36Sopenharmony_ci		goto nla_put_failure;
66862306a36Sopenharmony_ci#endif
66962306a36Sopenharmony_ci	nla_nest_end(skb, nest);
67062306a36Sopenharmony_ci
67162306a36Sopenharmony_ci	if (tcf_exts_dump_stats(skb, &f->exts) < 0)
67262306a36Sopenharmony_ci		goto nla_put_failure;
67362306a36Sopenharmony_ci
67462306a36Sopenharmony_ci	return skb->len;
67562306a36Sopenharmony_ci
67662306a36Sopenharmony_cinla_put_failure:
67762306a36Sopenharmony_ci	nla_nest_cancel(skb, nest);
67862306a36Sopenharmony_ci	return -1;
67962306a36Sopenharmony_ci}
68062306a36Sopenharmony_ci
68162306a36Sopenharmony_cistatic void flow_walk(struct tcf_proto *tp, struct tcf_walker *arg,
68262306a36Sopenharmony_ci		      bool rtnl_held)
68362306a36Sopenharmony_ci{
68462306a36Sopenharmony_ci	struct flow_head *head = rtnl_dereference(tp->root);
68562306a36Sopenharmony_ci	struct flow_filter *f;
68662306a36Sopenharmony_ci
68762306a36Sopenharmony_ci	list_for_each_entry(f, &head->filters, list) {
68862306a36Sopenharmony_ci		if (!tc_cls_stats_dump(tp, arg, f))
68962306a36Sopenharmony_ci			break;
69062306a36Sopenharmony_ci	}
69162306a36Sopenharmony_ci}
69262306a36Sopenharmony_ci
69362306a36Sopenharmony_cistatic struct tcf_proto_ops cls_flow_ops __read_mostly = {
69462306a36Sopenharmony_ci	.kind		= "flow",
69562306a36Sopenharmony_ci	.classify	= flow_classify,
69662306a36Sopenharmony_ci	.init		= flow_init,
69762306a36Sopenharmony_ci	.destroy	= flow_destroy,
69862306a36Sopenharmony_ci	.change		= flow_change,
69962306a36Sopenharmony_ci	.delete		= flow_delete,
70062306a36Sopenharmony_ci	.get		= flow_get,
70162306a36Sopenharmony_ci	.dump		= flow_dump,
70262306a36Sopenharmony_ci	.walk		= flow_walk,
70362306a36Sopenharmony_ci	.owner		= THIS_MODULE,
70462306a36Sopenharmony_ci};
70562306a36Sopenharmony_ci
70662306a36Sopenharmony_cistatic int __init cls_flow_init(void)
70762306a36Sopenharmony_ci{
70862306a36Sopenharmony_ci	return register_tcf_proto_ops(&cls_flow_ops);
70962306a36Sopenharmony_ci}
71062306a36Sopenharmony_ci
71162306a36Sopenharmony_cistatic void __exit cls_flow_exit(void)
71262306a36Sopenharmony_ci{
71362306a36Sopenharmony_ci	unregister_tcf_proto_ops(&cls_flow_ops);
71462306a36Sopenharmony_ci}
71562306a36Sopenharmony_ci
71662306a36Sopenharmony_cimodule_init(cls_flow_init);
71762306a36Sopenharmony_cimodule_exit(cls_flow_exit);
71862306a36Sopenharmony_ci
71962306a36Sopenharmony_ciMODULE_LICENSE("GPL");
72062306a36Sopenharmony_ciMODULE_AUTHOR("Patrick McHardy <kaber@trash.net>");
72162306a36Sopenharmony_ciMODULE_DESCRIPTION("TC flow classifier");
722