Blame view

net/sched/cls_flow.c 16.6 KB
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
  /*
   * net/sched/cls_flow.c		Generic flow classifier
   *
   * Copyright (c) 2007, 2008 Patrick McHardy <kaber@trash.net>
   *
   * This program is free software; you can redistribute 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.
   */
  
  #include <linux/kernel.h>
  #include <linux/init.h>
  #include <linux/list.h>
  #include <linux/jhash.h>
  #include <linux/random.h>
  #include <linux/pkt_cls.h>
  #include <linux/skbuff.h>
  #include <linux/in.h>
  #include <linux/ip.h>
  #include <linux/ipv6.h>
9ec138101   Patrick McHardy   [NET_SCHED]: cls_...
22
  #include <linux/if_vlan.h>
5a0e3ad6a   Tejun Heo   include cleanup: ...
23
  #include <linux/slab.h>
3a9a231d9   Paul Gortmaker   net: Fix files ex...
24
  #include <linux/module.h>
743b2a667   Eric Dumazet   sched: cls_flow: ...
25
  #include <net/inet_sock.h>
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
26
27
28
29
  
  #include <net/pkt_cls.h>
  #include <net/ip.h>
  #include <net/route.h>
1bd758eb1   Jiri Pirko   net: change name ...
30
  #include <net/flow_dissector.h>
6bd2a9af1   Eric Dumazet   cls_flow: use skb...
31

0013de38a   Javier Martinez Canillas   net: sched: use I...
32
  #if IS_ENABLED(CONFIG_NF_CONNTRACK)
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
33
34
35
36
37
  #include <net/netfilter/nf_conntrack.h>
  #endif
  
  struct flow_head {
  	struct list_head	filters;
70da9f0bf   John Fastabend   net: sched: cls_f...
38
  	struct rcu_head		rcu;
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
39
40
41
42
43
44
  };
  
  struct flow_filter {
  	struct list_head	list;
  	struct tcf_exts		exts;
  	struct tcf_ematch_tree	ematches;
70da9f0bf   John Fastabend   net: sched: cls_f...
45
  	struct tcf_proto	*tp;
72d9794f4   Patrick McHardy   net-sched: cls_fl...
46
47
  	struct timer_list	perturb_timer;
  	u32			perturb_period;
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
48
49
50
51
52
53
54
55
56
57
58
  	u32			handle;
  
  	u32			nkeys;
  	u32			keymask;
  	u32			mode;
  	u32			mask;
  	u32			xor;
  	u32			rshift;
  	u32			addend;
  	u32			divisor;
  	u32			baseclass;
72d9794f4   Patrick McHardy   net-sched: cls_fl...
59
  	u32			hashrnd;
94cdb4756   Cong Wang   net_sched: use tc...
60
61
62
63
  	union {
  		struct work_struct	work;
  		struct rcu_head		rcu;
  	};
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
64
  };
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
65
66
67
68
69
70
  static inline u32 addr_fold(void *addr)
  {
  	unsigned long a = (unsigned long)addr;
  
  	return (a & 0xFFFFFFFF) ^ (BITS_PER_LONG > 32 ? a >> 32 : 0);
  }
6bd2a9af1   Eric Dumazet   cls_flow: use skb...
71
  static u32 flow_get_src(const struct sk_buff *skb, const struct flow_keys *flow)
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
72
  {
c3f832418   Tom Herbert   net: Add full IPv...
73
74
75
76
  	__be32 src = flow_get_u32_src(flow);
  
  	if (src)
  		return ntohl(src);
4b95c3d40   Changli Gao   cls_flow: add san...
77
  	return addr_fold(skb->sk);
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
78
  }
6bd2a9af1   Eric Dumazet   cls_flow: use skb...
79
  static u32 flow_get_dst(const struct sk_buff *skb, const struct flow_keys *flow)
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
80
  {
c3f832418   Tom Herbert   net: Add full IPv...
81
82
83
84
  	__be32 dst = flow_get_u32_dst(flow);
  
  	if (dst)
  		return ntohl(dst);
d8b9605d2   Jiri Pirko   net: sched: fix s...
85
  	return addr_fold(skb_dst(skb)) ^ (__force u16) tc_skb_protocol(skb);
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
86
  }
5a7a5555a   Jamal Hadi Salim   net sched: stylis...
87
88
  static u32 flow_get_proto(const struct sk_buff *skb,
  			  const struct flow_keys *flow)
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
89
  {
06635a35d   Jiri Pirko   flow_dissect: use...
90
  	return flow->basic.ip_proto;
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
91
  }
5a7a5555a   Jamal Hadi Salim   net sched: stylis...
92
93
  static u32 flow_get_proto_src(const struct sk_buff *skb,
  			      const struct flow_keys *flow)
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
94
  {
06635a35d   Jiri Pirko   flow_dissect: use...
95
  	if (flow->ports.ports)
59346afe7   Jiri Pirko   flow_dissector: c...
96
  		return ntohs(flow->ports.src);
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
97

859c20123   Eric Dumazet   net_sched: cls_fl...
98
99
  	return addr_fold(skb->sk);
  }
5a7a5555a   Jamal Hadi Salim   net sched: stylis...
100
101
  static u32 flow_get_proto_dst(const struct sk_buff *skb,
  			      const struct flow_keys *flow)
859c20123   Eric Dumazet   net_sched: cls_fl...
102
  {
06635a35d   Jiri Pirko   flow_dissect: use...
103
  	if (flow->ports.ports)
59346afe7   Jiri Pirko   flow_dissector: c...
104
  		return ntohs(flow->ports.dst);
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
105

d8b9605d2   Jiri Pirko   net: sched: fix s...
106
  	return addr_fold(skb_dst(skb)) ^ (__force u16) tc_skb_protocol(skb);
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
107
108
109
110
  }
  
  static u32 flow_get_iif(const struct sk_buff *skb)
  {
8964be4a9   Eric Dumazet   net: rename skb->...
111
  	return skb->skb_iif;
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
112
113
114
115
116
117
118
119
120
121
122
123
124
125
  }
  
  static u32 flow_get_priority(const struct sk_buff *skb)
  {
  	return skb->priority;
  }
  
  static u32 flow_get_mark(const struct sk_buff *skb)
  {
  	return skb->mark;
  }
  
  static u32 flow_get_nfct(const struct sk_buff *skb)
  {
0013de38a   Javier Martinez Canillas   net: sched: use I...
126
  #if IS_ENABLED(CONFIG_NF_CONNTRACK)
cb9c68363   Florian Westphal   skbuff: add and u...
127
  	return addr_fold(skb_nfct(skb));
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
128
129
130
131
  #else
  	return 0;
  #endif
  }
0013de38a   Javier Martinez Canillas   net: sched: use I...
132
  #if IS_ENABLED(CONFIG_NF_CONNTRACK)
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
133
134
135
  #define CTTUPLE(skb, member)						\
  ({									\
  	enum ip_conntrack_info ctinfo;					\
859c20123   Eric Dumazet   net_sched: cls_fl...
136
  	const struct nf_conn *ct = nf_ct_get(skb, &ctinfo);		\
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
137
138
139
140
141
142
143
144
145
146
147
  	if (ct == NULL)							\
  		goto fallback;						\
  	ct->tuplehash[CTINFO2DIR(ctinfo)].tuple.member;			\
  })
  #else
  #define CTTUPLE(skb, member)						\
  ({									\
  	goto fallback;							\
  	0;								\
  })
  #endif
5a7a5555a   Jamal Hadi Salim   net sched: stylis...
148
149
  static u32 flow_get_nfct_src(const struct sk_buff *skb,
  			     const struct flow_keys *flow)
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
150
  {
d8b9605d2   Jiri Pirko   net: sched: fix s...
151
  	switch (tc_skb_protocol(skb)) {
606780404   Arnaldo Carvalho de Melo   net: Use hton[sl]...
152
  	case htons(ETH_P_IP):
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
153
  		return ntohl(CTTUPLE(skb, src.u3.ip));
606780404   Arnaldo Carvalho de Melo   net: Use hton[sl]...
154
  	case htons(ETH_P_IPV6):
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
155
156
157
  		return ntohl(CTTUPLE(skb, src.u3.ip6[3]));
  	}
  fallback:
6bd2a9af1   Eric Dumazet   cls_flow: use skb...
158
  	return flow_get_src(skb, flow);
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
159
  }
5a7a5555a   Jamal Hadi Salim   net sched: stylis...
160
161
  static u32 flow_get_nfct_dst(const struct sk_buff *skb,
  			     const struct flow_keys *flow)
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
162
  {
d8b9605d2   Jiri Pirko   net: sched: fix s...
163
  	switch (tc_skb_protocol(skb)) {
606780404   Arnaldo Carvalho de Melo   net: Use hton[sl]...
164
  	case htons(ETH_P_IP):
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
165
  		return ntohl(CTTUPLE(skb, dst.u3.ip));
606780404   Arnaldo Carvalho de Melo   net: Use hton[sl]...
166
  	case htons(ETH_P_IPV6):
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
167
168
169
  		return ntohl(CTTUPLE(skb, dst.u3.ip6[3]));
  	}
  fallback:
6bd2a9af1   Eric Dumazet   cls_flow: use skb...
170
  	return flow_get_dst(skb, flow);
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
171
  }
5a7a5555a   Jamal Hadi Salim   net sched: stylis...
172
173
  static u32 flow_get_nfct_proto_src(const struct sk_buff *skb,
  				   const struct flow_keys *flow)
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
174
175
176
  {
  	return ntohs(CTTUPLE(skb, src.u.all));
  fallback:
6bd2a9af1   Eric Dumazet   cls_flow: use skb...
177
  	return flow_get_proto_src(skb, flow);
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
178
  }
5a7a5555a   Jamal Hadi Salim   net sched: stylis...
179
180
  static u32 flow_get_nfct_proto_dst(const struct sk_buff *skb,
  				   const struct flow_keys *flow)
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
181
182
183
  {
  	return ntohs(CTTUPLE(skb, dst.u.all));
  fallback:
6bd2a9af1   Eric Dumazet   cls_flow: use skb...
184
  	return flow_get_proto_dst(skb, flow);
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
185
186
187
188
  }
  
  static u32 flow_get_rtclassid(const struct sk_buff *skb)
  {
c7066f70d   Patrick McHardy   netfilter: fix Kc...
189
  #ifdef CONFIG_IP_ROUTE_CLASSID
adf30907d   Eric Dumazet   net: skb->dst acc...
190
191
  	if (skb_dst(skb))
  		return skb_dst(skb)->tclassid;
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
192
193
194
195
196
197
  #endif
  	return 0;
  }
  
  static u32 flow_get_skuid(const struct sk_buff *skb)
  {
743b2a667   Eric Dumazet   sched: cls_flow: ...
198
199
200
201
  	struct sock *sk = skb_to_full_sk(skb);
  
  	if (sk && sk->sk_socket && sk->sk_socket->file) {
  		kuid_t skuid = sk->sk_socket->file->f_cred->fsuid;
a6c6796c7   Eric W. Biederman   userns: Convert c...
202
203
  		return from_kuid(&init_user_ns, skuid);
  	}
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
204
205
206
207
208
  	return 0;
  }
  
  static u32 flow_get_skgid(const struct sk_buff *skb)
  {
743b2a667   Eric Dumazet   sched: cls_flow: ...
209
210
211
212
  	struct sock *sk = skb_to_full_sk(skb);
  
  	if (sk && sk->sk_socket && sk->sk_socket->file) {
  		kgid_t skgid = sk->sk_socket->file->f_cred->fsgid;
a6c6796c7   Eric W. Biederman   userns: Convert c...
213
214
  		return from_kgid(&init_user_ns, skgid);
  	}
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
215
216
  	return 0;
  }
9ec138101   Patrick McHardy   [NET_SCHED]: cls_...
217
218
219
220
221
222
223
224
  static u32 flow_get_vlan_tag(const struct sk_buff *skb)
  {
  	u16 uninitialized_var(tag);
  
  	if (vlan_get_tag(skb, &tag) < 0)
  		return 0;
  	return tag & VLAN_VID_MASK;
  }
739a91ef0   Changli Gao   net_sched: cls_fl...
225
226
  static u32 flow_get_rxhash(struct sk_buff *skb)
  {
3958afa1b   Tom Herbert   net: Change skb_g...
227
  	return skb_get_hash(skb);
739a91ef0   Changli Gao   net_sched: cls_fl...
228
  }
6bd2a9af1   Eric Dumazet   cls_flow: use skb...
229
  static u32 flow_key_get(struct sk_buff *skb, int key, struct flow_keys *flow)
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
230
231
232
  {
  	switch (key) {
  	case FLOW_KEY_SRC:
6bd2a9af1   Eric Dumazet   cls_flow: use skb...
233
  		return flow_get_src(skb, flow);
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
234
  	case FLOW_KEY_DST:
6bd2a9af1   Eric Dumazet   cls_flow: use skb...
235
  		return flow_get_dst(skb, flow);
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
236
  	case FLOW_KEY_PROTO:
6bd2a9af1   Eric Dumazet   cls_flow: use skb...
237
  		return flow_get_proto(skb, flow);
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
238
  	case FLOW_KEY_PROTO_SRC:
6bd2a9af1   Eric Dumazet   cls_flow: use skb...
239
  		return flow_get_proto_src(skb, flow);
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
240
  	case FLOW_KEY_PROTO_DST:
6bd2a9af1   Eric Dumazet   cls_flow: use skb...
241
  		return flow_get_proto_dst(skb, flow);
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
242
243
244
245
246
247
248
249
250
  	case FLOW_KEY_IIF:
  		return flow_get_iif(skb);
  	case FLOW_KEY_PRIORITY:
  		return flow_get_priority(skb);
  	case FLOW_KEY_MARK:
  		return flow_get_mark(skb);
  	case FLOW_KEY_NFCT:
  		return flow_get_nfct(skb);
  	case FLOW_KEY_NFCT_SRC:
6bd2a9af1   Eric Dumazet   cls_flow: use skb...
251
  		return flow_get_nfct_src(skb, flow);
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
252
  	case FLOW_KEY_NFCT_DST:
6bd2a9af1   Eric Dumazet   cls_flow: use skb...
253
  		return flow_get_nfct_dst(skb, flow);
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
254
  	case FLOW_KEY_NFCT_PROTO_SRC:
6bd2a9af1   Eric Dumazet   cls_flow: use skb...
255
  		return flow_get_nfct_proto_src(skb, flow);
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
256
  	case FLOW_KEY_NFCT_PROTO_DST:
6bd2a9af1   Eric Dumazet   cls_flow: use skb...
257
  		return flow_get_nfct_proto_dst(skb, flow);
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
258
259
260
261
262
263
  	case FLOW_KEY_RTCLASSID:
  		return flow_get_rtclassid(skb);
  	case FLOW_KEY_SKUID:
  		return flow_get_skuid(skb);
  	case FLOW_KEY_SKGID:
  		return flow_get_skgid(skb);
9ec138101   Patrick McHardy   [NET_SCHED]: cls_...
264
265
  	case FLOW_KEY_VLAN_TAG:
  		return flow_get_vlan_tag(skb);
739a91ef0   Changli Gao   net_sched: cls_fl...
266
267
  	case FLOW_KEY_RXHASH:
  		return flow_get_rxhash(skb);
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
268
269
270
271
272
  	default:
  		WARN_ON(1);
  		return 0;
  	}
  }
6bd2a9af1   Eric Dumazet   cls_flow: use skb...
273
274
275
276
277
278
279
280
281
  #define FLOW_KEYS_NEEDED ((1 << FLOW_KEY_SRC) | 		\
  			  (1 << FLOW_KEY_DST) |			\
  			  (1 << FLOW_KEY_PROTO) |		\
  			  (1 << FLOW_KEY_PROTO_SRC) |		\
  			  (1 << FLOW_KEY_PROTO_DST) | 		\
  			  (1 << FLOW_KEY_NFCT_SRC) |		\
  			  (1 << FLOW_KEY_NFCT_DST) |		\
  			  (1 << FLOW_KEY_NFCT_PROTO_SRC) |	\
  			  (1 << FLOW_KEY_NFCT_PROTO_DST))
dc7f9f6e8   Eric Dumazet   net: sched: const...
282
  static int flow_classify(struct sk_buff *skb, const struct tcf_proto *tp,
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
283
284
  			 struct tcf_result *res)
  {
70da9f0bf   John Fastabend   net: sched: cls_f...
285
  	struct flow_head *head = rcu_dereference_bh(tp->root);
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
286
287
288
289
290
  	struct flow_filter *f;
  	u32 keymask;
  	u32 classid;
  	unsigned int n, key;
  	int r;
70da9f0bf   John Fastabend   net: sched: cls_f...
291
  	list_for_each_entry_rcu(f, &head->filters, list) {
3a53943b5   Eric Dumazet   cls_flow: remove ...
292
  		u32 keys[FLOW_KEY_MAX + 1];
6bd2a9af1   Eric Dumazet   cls_flow: use skb...
293
  		struct flow_keys flow_keys;
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
294
295
296
297
298
  
  		if (!tcf_em_tree_match(skb, &f->ematches, NULL))
  			continue;
  
  		keymask = f->keymask;
6bd2a9af1   Eric Dumazet   cls_flow: use skb...
299
  		if (keymask & FLOW_KEYS_NEEDED)
cd79a2382   Tom Herbert   flow_dissector: A...
300
  			skb_flow_dissect_flow_keys(skb, &flow_keys, 0);
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
301
302
303
304
  
  		for (n = 0; n < f->nkeys; n++) {
  			key = ffs(keymask) - 1;
  			keymask &= ~(1 << key);
6bd2a9af1   Eric Dumazet   cls_flow: use skb...
305
  			keys[n] = flow_key_get(skb, key, &flow_keys);
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
306
307
308
  		}
  
  		if (f->mode == FLOW_MODE_HASH)
72d9794f4   Patrick McHardy   net-sched: cls_fl...
309
  			classid = jhash2(keys, f->nkeys, f->hashrnd);
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
  		else {
  			classid = keys[0];
  			classid = (classid & f->mask) ^ f->xor;
  			classid = (classid >> f->rshift) + f->addend;
  		}
  
  		if (f->divisor)
  			classid %= f->divisor;
  
  		res->class   = 0;
  		res->classid = TC_H_MAKE(f->baseclass, f->baseclass + classid);
  
  		r = tcf_exts_exec(skb, &f->exts, res);
  		if (r < 0)
  			continue;
  		return r;
  	}
  	return -1;
  }
72d9794f4   Patrick McHardy   net-sched: cls_fl...
329
330
331
332
333
334
335
336
  static void flow_perturbation(unsigned long arg)
  {
  	struct flow_filter *f = (struct flow_filter *)arg;
  
  	get_random_bytes(&f->hashrnd, 4);
  	if (f->perturb_period)
  		mod_timer(&f->perturb_timer, jiffies + f->perturb_period);
  }
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
337
338
339
340
341
342
343
344
345
346
347
348
  static const struct nla_policy flow_policy[TCA_FLOW_MAX + 1] = {
  	[TCA_FLOW_KEYS]		= { .type = NLA_U32 },
  	[TCA_FLOW_MODE]		= { .type = NLA_U32 },
  	[TCA_FLOW_BASECLASS]	= { .type = NLA_U32 },
  	[TCA_FLOW_RSHIFT]	= { .type = NLA_U32 },
  	[TCA_FLOW_ADDEND]	= { .type = NLA_U32 },
  	[TCA_FLOW_MASK]		= { .type = NLA_U32 },
  	[TCA_FLOW_XOR]		= { .type = NLA_U32 },
  	[TCA_FLOW_DIVISOR]	= { .type = NLA_U32 },
  	[TCA_FLOW_ACT]		= { .type = NLA_NESTED },
  	[TCA_FLOW_POLICE]	= { .type = NLA_NESTED },
  	[TCA_FLOW_EMATCHES]	= { .type = NLA_NESTED },
72d9794f4   Patrick McHardy   net-sched: cls_fl...
349
  	[TCA_FLOW_PERTURB]	= { .type = NLA_U32 },
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
350
  };
22f7cec93   Cong Wang   cls_flow: use tcf...
351
  static void __flow_destroy_filter(struct flow_filter *f)
70da9f0bf   John Fastabend   net: sched: cls_f...
352
  {
70da9f0bf   John Fastabend   net: sched: cls_f...
353
  	del_timer_sync(&f->perturb_timer);
18d0264f6   WANG Cong   net_sched: remove...
354
  	tcf_exts_destroy(&f->exts);
82a470f11   John Fastabend   net: sched: remov...
355
  	tcf_em_tree_destroy(&f->ematches);
22f7cec93   Cong Wang   cls_flow: use tcf...
356
  	tcf_exts_put_net(&f->exts);
70da9f0bf   John Fastabend   net: sched: cls_f...
357
  	kfree(f);
22f7cec93   Cong Wang   cls_flow: use tcf...
358
359
360
361
362
363
364
365
  }
  
  static void flow_destroy_filter_work(struct work_struct *work)
  {
  	struct flow_filter *f = container_of(work, struct flow_filter, work);
  
  	rtnl_lock();
  	__flow_destroy_filter(f);
94cdb4756   Cong Wang   net_sched: use tc...
366
367
368
369
370
371
372
373
374
  	rtnl_unlock();
  }
  
  static void flow_destroy_filter(struct rcu_head *head)
  {
  	struct flow_filter *f = container_of(head, struct flow_filter, rcu);
  
  	INIT_WORK(&f->work, flow_destroy_filter_work);
  	tcf_queue_work(&f->work);
70da9f0bf   John Fastabend   net: sched: cls_f...
375
  }
c1b52739e   Benjamin LaHaise   pkt_sched: namesp...
376
  static int flow_change(struct net *net, struct sk_buff *in_skb,
af4c6641f   Eric W. Biederman   net sched: Pass t...
377
  		       struct tcf_proto *tp, unsigned long base,
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
378
  		       u32 handle, struct nlattr **tca,
8113c0956   WANG Cong   net_sched: use vo...
379
  		       void **arg, bool ovr)
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
380
  {
70da9f0bf   John Fastabend   net: sched: cls_f...
381
382
  	struct flow_head *head = rtnl_dereference(tp->root);
  	struct flow_filter *fold, *fnew;
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
383
384
  	struct nlattr *opt = tca[TCA_OPTIONS];
  	struct nlattr *tb[TCA_FLOW_MAX + 1];
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
385
  	unsigned int nkeys = 0;
72d9794f4   Patrick McHardy   net-sched: cls_fl...
386
  	unsigned int perturb_period = 0;
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
387
388
389
390
391
392
393
  	u32 baseclass = 0;
  	u32 keymask = 0;
  	u32 mode;
  	int err;
  
  	if (opt == NULL)
  		return -EINVAL;
fceb6435e   Johannes Berg   netlink: pass ext...
394
  	err = nla_parse_nested(tb, TCA_FLOW_MAX, opt, flow_policy, NULL);
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
395
396
397
398
399
400
401
402
403
404
405
  	if (err < 0)
  		return err;
  
  	if (tb[TCA_FLOW_BASECLASS]) {
  		baseclass = nla_get_u32(tb[TCA_FLOW_BASECLASS]);
  		if (TC_H_MIN(baseclass) == 0)
  			return -EINVAL;
  	}
  
  	if (tb[TCA_FLOW_KEYS]) {
  		keymask = nla_get_u32(tb[TCA_FLOW_KEYS]);
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
406
407
408
409
  
  		nkeys = hweight32(keymask);
  		if (nkeys == 0)
  			return -EINVAL;
4f2504910   Patrick McHardy   [NET_SCHED]: cls_...
410
411
412
  
  		if (fls(keymask) - 1 > FLOW_KEY_MAX)
  			return -EOPNOTSUPP;
a6c6796c7   Eric W. Biederman   userns: Convert c...
413
414
  
  		if ((keymask & (FLOW_KEY_SKUID|FLOW_KEY_SKGID)) &&
e32123e59   Patrick McHardy   netlink: rename s...
415
  		    sk_user_ns(NETLINK_CB(in_skb).sk) != &init_user_ns)
a6c6796c7   Eric W. Biederman   userns: Convert c...
416
  			return -EOPNOTSUPP;
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
417
  	}
70da9f0bf   John Fastabend   net: sched: cls_f...
418
419
  	fnew = kzalloc(sizeof(*fnew), GFP_KERNEL);
  	if (!fnew)
c09fc2e11   Jiri Pirko   net: sched: cls_f...
420
  		return -ENOBUFS;
4ebc1e3cf   Jiri Pirko   net: sched: remov...
421
422
423
  
  	err = tcf_em_tree_validate(tp, tb[TCA_FLOW_EMATCHES], &fnew->ematches);
  	if (err < 0)
c09fc2e11   Jiri Pirko   net: sched: cls_f...
424
  		goto err1;
70da9f0bf   John Fastabend   net: sched: cls_f...
425

b9a24bb76   WANG Cong   net_sched: proper...
426
427
  	err = tcf_exts_init(&fnew->exts, TCA_FLOW_ACT, TCA_FLOW_POLICE);
  	if (err < 0)
c09fc2e11   Jiri Pirko   net: sched: cls_f...
428
429
430
431
432
  		goto err2;
  
  	err = tcf_exts_validate(net, tp, tb, tca[TCA_RATE], &fnew->exts, ovr);
  	if (err < 0)
  		goto err2;
32b2f4b19   Daniel Borkmann   sched: cls_flow: ...
433

8113c0956   WANG Cong   net_sched: use vo...
434
  	fold = *arg;
70da9f0bf   John Fastabend   net: sched: cls_f...
435
  	if (fold) {
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
436
  		err = -EINVAL;
70da9f0bf   John Fastabend   net: sched: cls_f...
437
  		if (fold->handle != handle && handle)
c09fc2e11   Jiri Pirko   net: sched: cls_f...
438
  			goto err2;
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
439

70da9f0bf   John Fastabend   net: sched: cls_f...
440
  		/* Copy fold into fnew */
70da9f0bf   John Fastabend   net: sched: cls_f...
441
  		fnew->tp = fold->tp;
70da9f0bf   John Fastabend   net: sched: cls_f...
442
443
444
445
446
447
448
449
450
451
452
453
454
  		fnew->handle = fold->handle;
  		fnew->nkeys = fold->nkeys;
  		fnew->keymask = fold->keymask;
  		fnew->mode = fold->mode;
  		fnew->mask = fold->mask;
  		fnew->xor = fold->xor;
  		fnew->rshift = fold->rshift;
  		fnew->addend = fold->addend;
  		fnew->divisor = fold->divisor;
  		fnew->baseclass = fold->baseclass;
  		fnew->hashrnd = fold->hashrnd;
  
  		mode = fold->mode;
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
455
456
457
  		if (tb[TCA_FLOW_MODE])
  			mode = nla_get_u32(tb[TCA_FLOW_MODE]);
  		if (mode != FLOW_MODE_HASH && nkeys > 1)
c09fc2e11   Jiri Pirko   net: sched: cls_f...
458
  			goto err2;
72d9794f4   Patrick McHardy   net-sched: cls_fl...
459
460
  
  		if (mode == FLOW_MODE_HASH)
70da9f0bf   John Fastabend   net: sched: cls_f...
461
  			perturb_period = fold->perturb_period;
72d9794f4   Patrick McHardy   net-sched: cls_fl...
462
463
  		if (tb[TCA_FLOW_PERTURB]) {
  			if (mode != FLOW_MODE_HASH)
c09fc2e11   Jiri Pirko   net: sched: cls_f...
464
  				goto err2;
72d9794f4   Patrick McHardy   net-sched: cls_fl...
465
466
  			perturb_period = nla_get_u32(tb[TCA_FLOW_PERTURB]) * HZ;
  		}
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
467
468
469
  	} else {
  		err = -EINVAL;
  		if (!handle)
c09fc2e11   Jiri Pirko   net: sched: cls_f...
470
  			goto err2;
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
471
  		if (!tb[TCA_FLOW_KEYS])
c09fc2e11   Jiri Pirko   net: sched: cls_f...
472
  			goto err2;
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
473
474
475
476
477
  
  		mode = FLOW_MODE_MAP;
  		if (tb[TCA_FLOW_MODE])
  			mode = nla_get_u32(tb[TCA_FLOW_MODE]);
  		if (mode != FLOW_MODE_HASH && nkeys > 1)
c09fc2e11   Jiri Pirko   net: sched: cls_f...
478
  			goto err2;
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
479

72d9794f4   Patrick McHardy   net-sched: cls_fl...
480
481
  		if (tb[TCA_FLOW_PERTURB]) {
  			if (mode != FLOW_MODE_HASH)
c09fc2e11   Jiri Pirko   net: sched: cls_f...
482
  				goto err2;
72d9794f4   Patrick McHardy   net-sched: cls_fl...
483
484
  			perturb_period = nla_get_u32(tb[TCA_FLOW_PERTURB]) * HZ;
  		}
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
485
486
487
488
  		if (TC_H_MAJ(baseclass) == 0)
  			baseclass = TC_H_MAKE(tp->q->handle, baseclass);
  		if (TC_H_MIN(baseclass) == 0)
  			baseclass = TC_H_MAKE(baseclass, 1);
70da9f0bf   John Fastabend   net: sched: cls_f...
489
490
491
492
  		fnew->handle = handle;
  		fnew->mask  = ~0U;
  		fnew->tp = tp;
  		get_random_bytes(&fnew->hashrnd, 4);
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
493
  	}
3b1af93cf   Geliang Tang   net_sched: use se...
494
495
  	setup_deferrable_timer(&fnew->perturb_timer, flow_perturbation,
  			       (unsigned long)fnew);
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
496

028758788   Eric Dumazet   net: better IFF_X...
497
  	netif_keep_dst(qdisc_dev(tp->q));
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
498
  	if (tb[TCA_FLOW_KEYS]) {
70da9f0bf   John Fastabend   net: sched: cls_f...
499
500
  		fnew->keymask = keymask;
  		fnew->nkeys   = nkeys;
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
501
  	}
70da9f0bf   John Fastabend   net: sched: cls_f...
502
  	fnew->mode = mode;
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
503
504
  
  	if (tb[TCA_FLOW_MASK])
70da9f0bf   John Fastabend   net: sched: cls_f...
505
  		fnew->mask = nla_get_u32(tb[TCA_FLOW_MASK]);
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
506
  	if (tb[TCA_FLOW_XOR])
70da9f0bf   John Fastabend   net: sched: cls_f...
507
  		fnew->xor = nla_get_u32(tb[TCA_FLOW_XOR]);
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
508
  	if (tb[TCA_FLOW_RSHIFT])
70da9f0bf   John Fastabend   net: sched: cls_f...
509
  		fnew->rshift = nla_get_u32(tb[TCA_FLOW_RSHIFT]);
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
510
  	if (tb[TCA_FLOW_ADDEND])
70da9f0bf   John Fastabend   net: sched: cls_f...
511
  		fnew->addend = nla_get_u32(tb[TCA_FLOW_ADDEND]);
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
512
513
  
  	if (tb[TCA_FLOW_DIVISOR])
70da9f0bf   John Fastabend   net: sched: cls_f...
514
  		fnew->divisor = nla_get_u32(tb[TCA_FLOW_DIVISOR]);
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
515
  	if (baseclass)
70da9f0bf   John Fastabend   net: sched: cls_f...
516
  		fnew->baseclass = baseclass;
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
517

70da9f0bf   John Fastabend   net: sched: cls_f...
518
  	fnew->perturb_period = perturb_period;
72d9794f4   Patrick McHardy   net-sched: cls_fl...
519
  	if (perturb_period)
70da9f0bf   John Fastabend   net: sched: cls_f...
520
  		mod_timer(&fnew->perturb_timer, jiffies + perturb_period);
72d9794f4   Patrick McHardy   net-sched: cls_fl...
521

8113c0956   WANG Cong   net_sched: use vo...
522
  	if (!*arg)
70da9f0bf   John Fastabend   net: sched: cls_f...
523
524
  		list_add_tail_rcu(&fnew->list, &head->filters);
  	else
32b2f4b19   Daniel Borkmann   sched: cls_flow: ...
525
  		list_replace_rcu(&fold->list, &fnew->list);
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
526

8113c0956   WANG Cong   net_sched: use vo...
527
  	*arg = fnew;
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
528

22f7cec93   Cong Wang   cls_flow: use tcf...
529
530
  	if (fold) {
  		tcf_exts_get_net(&fold->exts);
70da9f0bf   John Fastabend   net: sched: cls_f...
531
  		call_rcu(&fold->rcu, flow_destroy_filter);
22f7cec93   Cong Wang   cls_flow: use tcf...
532
  	}
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
533
  	return 0;
c09fc2e11   Jiri Pirko   net: sched: cls_f...
534
  err2:
b9a24bb76   WANG Cong   net_sched: proper...
535
  	tcf_exts_destroy(&fnew->exts);
4ebc1e3cf   Jiri Pirko   net: sched: remov...
536
  	tcf_em_tree_destroy(&fnew->ematches);
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
537
  err1:
c09fc2e11   Jiri Pirko   net: sched: cls_f...
538
  	kfree(fnew);
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
539
540
  	return err;
  }
8113c0956   WANG Cong   net_sched: use vo...
541
  static int flow_delete(struct tcf_proto *tp, void *arg, bool *last)
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
542
  {
763dbf632   WANG Cong   net_sched: move t...
543
  	struct flow_head *head = rtnl_dereference(tp->root);
8113c0956   WANG Cong   net_sched: use vo...
544
  	struct flow_filter *f = arg;
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
545

70da9f0bf   John Fastabend   net: sched: cls_f...
546
  	list_del_rcu(&f->list);
22f7cec93   Cong Wang   cls_flow: use tcf...
547
  	tcf_exts_get_net(&f->exts);
70da9f0bf   John Fastabend   net: sched: cls_f...
548
  	call_rcu(&f->rcu, flow_destroy_filter);
763dbf632   WANG Cong   net_sched: move t...
549
  	*last = list_empty(&head->filters);
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
550
551
552
553
554
555
  	return 0;
  }
  
  static int flow_init(struct tcf_proto *tp)
  {
  	struct flow_head *head;
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
556
557
558
559
  	head = kzalloc(sizeof(*head), GFP_KERNEL);
  	if (head == NULL)
  		return -ENOBUFS;
  	INIT_LIST_HEAD(&head->filters);
70da9f0bf   John Fastabend   net: sched: cls_f...
560
  	rcu_assign_pointer(tp->root, head);
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
561
562
  	return 0;
  }
763dbf632   WANG Cong   net_sched: move t...
563
  static void flow_destroy(struct tcf_proto *tp)
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
564
  {
70da9f0bf   John Fastabend   net: sched: cls_f...
565
  	struct flow_head *head = rtnl_dereference(tp->root);
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
566
567
568
  	struct flow_filter *f, *next;
  
  	list_for_each_entry_safe(f, next, &head->filters, list) {
70da9f0bf   John Fastabend   net: sched: cls_f...
569
  		list_del_rcu(&f->list);
22f7cec93   Cong Wang   cls_flow: use tcf...
570
571
572
573
  		if (tcf_exts_get_net(&f->exts))
  			call_rcu(&f->rcu, flow_destroy_filter);
  		else
  			__flow_destroy_filter(f);
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
574
  	}
70da9f0bf   John Fastabend   net: sched: cls_f...
575
  	kfree_rcu(head, rcu);
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
576
  }
8113c0956   WANG Cong   net_sched: use vo...
577
  static void *flow_get(struct tcf_proto *tp, u32 handle)
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
578
  {
70da9f0bf   John Fastabend   net: sched: cls_f...
579
  	struct flow_head *head = rtnl_dereference(tp->root);
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
580
  	struct flow_filter *f;
6a659cd06   Jiri Pirko   net_sched: cls_fl...
581
  	list_for_each_entry(f, &head->filters, list)
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
582
  		if (f->handle == handle)
8113c0956   WANG Cong   net_sched: use vo...
583
584
  			return f;
  	return NULL;
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
585
  }
8113c0956   WANG Cong   net_sched: use vo...
586
  static int flow_dump(struct net *net, struct tcf_proto *tp, void *fh,
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
587
588
  		     struct sk_buff *skb, struct tcmsg *t)
  {
8113c0956   WANG Cong   net_sched: use vo...
589
  	struct flow_filter *f = fh;
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
590
591
592
593
594
595
596
597
598
599
  	struct nlattr *nest;
  
  	if (f == NULL)
  		return skb->len;
  
  	t->tcm_handle = f->handle;
  
  	nest = nla_nest_start(skb, TCA_OPTIONS);
  	if (nest == NULL)
  		goto nla_put_failure;
1b34ec43c   David S. Miller   pkt_sched: Stop u...
600
601
602
  	if (nla_put_u32(skb, TCA_FLOW_KEYS, f->keymask) ||
  	    nla_put_u32(skb, TCA_FLOW_MODE, f->mode))
  		goto nla_put_failure;
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
603
604
  
  	if (f->mask != ~0 || f->xor != 0) {
1b34ec43c   David S. Miller   pkt_sched: Stop u...
605
606
607
  		if (nla_put_u32(skb, TCA_FLOW_MASK, f->mask) ||
  		    nla_put_u32(skb, TCA_FLOW_XOR, f->xor))
  			goto nla_put_failure;
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
608
  	}
1b34ec43c   David S. Miller   pkt_sched: Stop u...
609
610
611
612
613
614
  	if (f->rshift &&
  	    nla_put_u32(skb, TCA_FLOW_RSHIFT, f->rshift))
  		goto nla_put_failure;
  	if (f->addend &&
  	    nla_put_u32(skb, TCA_FLOW_ADDEND, f->addend))
  		goto nla_put_failure;
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
615

1b34ec43c   David S. Miller   pkt_sched: Stop u...
616
617
618
619
620
621
  	if (f->divisor &&
  	    nla_put_u32(skb, TCA_FLOW_DIVISOR, f->divisor))
  		goto nla_put_failure;
  	if (f->baseclass &&
  	    nla_put_u32(skb, TCA_FLOW_BASECLASS, f->baseclass))
  		goto nla_put_failure;
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
622

1b34ec43c   David S. Miller   pkt_sched: Stop u...
623
624
625
  	if (f->perturb_period &&
  	    nla_put_u32(skb, TCA_FLOW_PERTURB, f->perturb_period / HZ))
  		goto nla_put_failure;
72d9794f4   Patrick McHardy   net-sched: cls_fl...
626

5da57f422   WANG Cong   net_sched: cls: r...
627
  	if (tcf_exts_dump(skb, &f->exts) < 0)
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
628
  		goto nla_put_failure;
0aead5434   Rami Rosen   [NET_SCHED]: Add ...
629
  #ifdef CONFIG_NET_EMATCH
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
630
631
632
  	if (f->ematches.hdr.nmatches &&
  	    tcf_em_tree_dump(skb, &f->ematches, TCA_FLOW_EMATCHES) < 0)
  		goto nla_put_failure;
0aead5434   Rami Rosen   [NET_SCHED]: Add ...
633
  #endif
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
634
  	nla_nest_end(skb, nest);
5da57f422   WANG Cong   net_sched: cls: r...
635
  	if (tcf_exts_dump_stats(skb, &f->exts) < 0)
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
636
637
638
639
640
  		goto nla_put_failure;
  
  	return skb->len;
  
  nla_put_failure:
6ea3b446b   Jiri Pirko   net: sched: cls: ...
641
  	nla_nest_cancel(skb, nest);
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
642
643
644
645
646
  	return -1;
  }
  
  static void flow_walk(struct tcf_proto *tp, struct tcf_walker *arg)
  {
70da9f0bf   John Fastabend   net: sched: cls_f...
647
  	struct flow_head *head = rtnl_dereference(tp->root);
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
648
  	struct flow_filter *f;
6a659cd06   Jiri Pirko   net_sched: cls_fl...
649
  	list_for_each_entry(f, &head->filters, list) {
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
650
651
  		if (arg->count < arg->skip)
  			goto skip;
8113c0956   WANG Cong   net_sched: use vo...
652
  		if (arg->fn(tp, f, arg) < 0) {
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
  			arg->stop = 1;
  			break;
  		}
  skip:
  		arg->count++;
  	}
  }
  
  static struct tcf_proto_ops cls_flow_ops __read_mostly = {
  	.kind		= "flow",
  	.classify	= flow_classify,
  	.init		= flow_init,
  	.destroy	= flow_destroy,
  	.change		= flow_change,
  	.delete		= flow_delete,
  	.get		= flow_get,
e5dfb8151   Patrick McHardy   [NET_SCHED]: Add ...
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
  	.dump		= flow_dump,
  	.walk		= flow_walk,
  	.owner		= THIS_MODULE,
  };
  
  static int __init cls_flow_init(void)
  {
  	return register_tcf_proto_ops(&cls_flow_ops);
  }
  
  static void __exit cls_flow_exit(void)
  {
  	unregister_tcf_proto_ops(&cls_flow_ops);
  }
  
  module_init(cls_flow_init);
  module_exit(cls_flow_exit);
  
  MODULE_LICENSE("GPL");
  MODULE_AUTHOR("Patrick McHardy <kaber@trash.net>");
  MODULE_DESCRIPTION("TC flow classifier");