Commit | Line | Data |
---|---|---|
2874c5fd | 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
c7e2b968 JP |
2 | /* |
3 | * Copyright (c) 2014 Jiri Pirko <jiri@resnulli.us> | |
c7e2b968 JP |
4 | */ |
5 | ||
6 | #include <linux/module.h> | |
7 | #include <linux/init.h> | |
8 | #include <linux/kernel.h> | |
9 | #include <linux/skbuff.h> | |
10 | #include <linux/rtnetlink.h> | |
11 | #include <linux/if_vlan.h> | |
12 | #include <net/netlink.h> | |
13 | #include <net/pkt_sched.h> | |
7e0c8892 | 14 | #include <net/pkt_cls.h> |
871cf386 | 15 | #include <net/tc_wrapper.h> |
c7e2b968 JP |
16 | |
17 | #include <linux/tc_act/tc_vlan.h> | |
18 | #include <net/tc_act/tc_vlan.h> | |
19 | ||
a85a970a | 20 | static struct tc_action_ops act_vlan_ops; |
ddf97ccd | 21 | |
871cf386 PT |
22 | TC_INDIRECT_SCOPE int tcf_vlan_act(struct sk_buff *skb, |
23 | const struct tc_action *a, | |
24 | struct tcf_result *res) | |
c7e2b968 | 25 | { |
a85a970a | 26 | struct tcf_vlan *v = to_vlan(a); |
4c5b9d96 | 27 | struct tcf_vlan_params *p; |
c7e2b968 JP |
28 | int action; |
29 | int err; | |
45a497f2 | 30 | u16 tci; |
c7e2b968 | 31 | |
9c4a4e48 | 32 | tcf_lastuse_update(&v->tcf_tm); |
5e1ad95b | 33 | tcf_action_update_bstats(&v->common, skb); |
e0496cbb | 34 | |
f39acc84 SL |
35 | /* Ensure 'data' points at mac_header prior calling vlan manipulating |
36 | * functions. | |
37 | */ | |
38 | if (skb_at_tc_ingress(skb)) | |
39 | skb_push_rcsum(skb, skb->mac_len); | |
40 | ||
4c5b9d96 MK |
41 | action = READ_ONCE(v->tcf_action); |
42 | ||
7fd4b288 | 43 | p = rcu_dereference_bh(v->vlan_p); |
4c5b9d96 MK |
44 | |
45 | switch (p->tcfv_action) { | |
c7e2b968 JP |
46 | case TCA_VLAN_ACT_POP: |
47 | err = skb_vlan_pop(skb); | |
48 | if (err) | |
49 | goto drop; | |
50 | break; | |
51 | case TCA_VLAN_ACT_PUSH: | |
4c5b9d96 MK |
52 | err = skb_vlan_push(skb, p->tcfv_push_proto, p->tcfv_push_vid | |
53 | (p->tcfv_push_prio << VLAN_PRIO_SHIFT)); | |
c7e2b968 JP |
54 | if (err) |
55 | goto drop; | |
56 | break; | |
45a497f2 SL |
57 | case TCA_VLAN_ACT_MODIFY: |
58 | /* No-op if no vlan tag (either hw-accel or in-payload) */ | |
59 | if (!skb_vlan_tagged(skb)) | |
7fd4b288 | 60 | goto out; |
45a497f2 SL |
61 | /* extract existing tag (and guarantee no hw-accel tag) */ |
62 | if (skb_vlan_tag_present(skb)) { | |
63 | tci = skb_vlan_tag_get(skb); | |
b1817524 | 64 | __vlan_hwaccel_clear_tag(skb); |
45a497f2 SL |
65 | } else { |
66 | /* in-payload vlan tag, pop it */ | |
67 | err = __skb_vlan_pop(skb, &tci); | |
68 | if (err) | |
69 | goto drop; | |
70 | } | |
71 | /* replace the vid */ | |
4c5b9d96 | 72 | tci = (tci & ~VLAN_VID_MASK) | p->tcfv_push_vid; |
45a497f2 | 73 | /* replace prio bits, if tcfv_push_prio specified */ |
9c5eee0a | 74 | if (p->tcfv_push_prio_exists) { |
45a497f2 | 75 | tci &= ~VLAN_PRIO_MASK; |
4c5b9d96 | 76 | tci |= p->tcfv_push_prio << VLAN_PRIO_SHIFT; |
45a497f2 SL |
77 | } |
78 | /* put updated tci as hwaccel tag */ | |
4c5b9d96 | 79 | __vlan_hwaccel_put_tag(skb, p->tcfv_push_proto, tci); |
45a497f2 | 80 | break; |
19fbcb36 GN |
81 | case TCA_VLAN_ACT_POP_ETH: |
82 | err = skb_eth_pop(skb); | |
83 | if (err) | |
84 | goto drop; | |
85 | break; | |
86 | case TCA_VLAN_ACT_PUSH_ETH: | |
87 | err = skb_eth_push(skb, p->tcfv_push_dst, p->tcfv_push_src); | |
88 | if (err) | |
89 | goto drop; | |
90 | break; | |
c7e2b968 JP |
91 | default: |
92 | BUG(); | |
93 | } | |
94 | ||
7fd4b288 | 95 | out: |
f39acc84 SL |
96 | if (skb_at_tc_ingress(skb)) |
97 | skb_pull_rcsum(skb, skb->mac_len); | |
98 | ||
c7e2b968 | 99 | return action; |
7fd4b288 PA |
100 | |
101 | drop: | |
26b537a8 | 102 | tcf_action_inc_drop_qstats(&v->common); |
7fd4b288 | 103 | return TC_ACT_SHOT; |
c7e2b968 JP |
104 | } |
105 | ||
106 | static const struct nla_policy vlan_policy[TCA_VLAN_MAX + 1] = { | |
19fbcb36 | 107 | [TCA_VLAN_UNSPEC] = { .strict_start_type = TCA_VLAN_PUSH_ETH_DST }, |
c7e2b968 JP |
108 | [TCA_VLAN_PARMS] = { .len = sizeof(struct tc_vlan) }, |
109 | [TCA_VLAN_PUSH_VLAN_ID] = { .type = NLA_U16 }, | |
110 | [TCA_VLAN_PUSH_VLAN_PROTOCOL] = { .type = NLA_U16 }, | |
956af371 | 111 | [TCA_VLAN_PUSH_VLAN_PRIORITY] = { .type = NLA_U8 }, |
19fbcb36 GN |
112 | [TCA_VLAN_PUSH_ETH_DST] = NLA_POLICY_ETH_ADDR, |
113 | [TCA_VLAN_PUSH_ETH_SRC] = NLA_POLICY_ETH_ADDR, | |
c7e2b968 JP |
114 | }; |
115 | ||
116 | static int tcf_vlan_init(struct net *net, struct nlattr *nla, | |
a85a970a | 117 | struct nlattr *est, struct tc_action **a, |
abbb0d33 VB |
118 | struct tcf_proto *tp, u32 flags, |
119 | struct netlink_ext_ack *extack) | |
c7e2b968 | 120 | { |
acd0a7ab | 121 | struct tc_action_net *tn = net_generic(net, act_vlan_ops.net_id); |
695176bf | 122 | bool bind = flags & TCA_ACT_FLAGS_BIND; |
c7e2b968 | 123 | struct nlattr *tb[TCA_VLAN_MAX + 1]; |
7e0c8892 | 124 | struct tcf_chain *goto_ch = NULL; |
9c5eee0a | 125 | bool push_prio_exists = false; |
764e9a24 | 126 | struct tcf_vlan_params *p; |
c7e2b968 JP |
127 | struct tc_vlan *parm; |
128 | struct tcf_vlan *v; | |
129 | int action; | |
94cb5492 | 130 | u16 push_vid = 0; |
c7e2b968 | 131 | __be16 push_proto = 0; |
956af371 | 132 | u8 push_prio = 0; |
b2313077 WC |
133 | bool exists = false; |
134 | int ret = 0, err; | |
7be8ef2c | 135 | u32 index; |
c7e2b968 JP |
136 | |
137 | if (!nla) | |
138 | return -EINVAL; | |
139 | ||
8cb08174 JB |
140 | err = nla_parse_nested_deprecated(tb, TCA_VLAN_MAX, nla, vlan_policy, |
141 | NULL); | |
c7e2b968 JP |
142 | if (err < 0) |
143 | return err; | |
144 | ||
145 | if (!tb[TCA_VLAN_PARMS]) | |
146 | return -EINVAL; | |
147 | parm = nla_data(tb[TCA_VLAN_PARMS]); | |
7be8ef2c DL |
148 | index = parm->index; |
149 | err = tcf_idr_check_alloc(tn, &index, a, bind); | |
0190c1d4 VB |
150 | if (err < 0) |
151 | return err; | |
152 | exists = err; | |
5026c9b1 JHS |
153 | if (exists && bind) |
154 | return 0; | |
155 | ||
c7e2b968 JP |
156 | switch (parm->v_action) { |
157 | case TCA_VLAN_ACT_POP: | |
158 | break; | |
159 | case TCA_VLAN_ACT_PUSH: | |
45a497f2 | 160 | case TCA_VLAN_ACT_MODIFY: |
5026c9b1 JHS |
161 | if (!tb[TCA_VLAN_PUSH_VLAN_ID]) { |
162 | if (exists) | |
65a206c0 | 163 | tcf_idr_release(*a, bind); |
0190c1d4 | 164 | else |
7be8ef2c | 165 | tcf_idr_cleanup(tn, index); |
c7e2b968 | 166 | return -EINVAL; |
5026c9b1 | 167 | } |
c7e2b968 | 168 | push_vid = nla_get_u16(tb[TCA_VLAN_PUSH_VLAN_ID]); |
5026c9b1 JHS |
169 | if (push_vid >= VLAN_VID_MASK) { |
170 | if (exists) | |
65a206c0 | 171 | tcf_idr_release(*a, bind); |
0190c1d4 | 172 | else |
7be8ef2c | 173 | tcf_idr_cleanup(tn, index); |
c7e2b968 | 174 | return -ERANGE; |
5026c9b1 | 175 | } |
c7e2b968 JP |
176 | |
177 | if (tb[TCA_VLAN_PUSH_VLAN_PROTOCOL]) { | |
178 | push_proto = nla_get_be16(tb[TCA_VLAN_PUSH_VLAN_PROTOCOL]); | |
179 | switch (push_proto) { | |
180 | case htons(ETH_P_8021Q): | |
181 | case htons(ETH_P_8021AD): | |
182 | break; | |
183 | default: | |
5a4931ae DC |
184 | if (exists) |
185 | tcf_idr_release(*a, bind); | |
0190c1d4 | 186 | else |
7be8ef2c | 187 | tcf_idr_cleanup(tn, index); |
c7e2b968 JP |
188 | return -EPROTONOSUPPORT; |
189 | } | |
190 | } else { | |
191 | push_proto = htons(ETH_P_8021Q); | |
192 | } | |
956af371 | 193 | |
9c5eee0a BS |
194 | push_prio_exists = !!tb[TCA_VLAN_PUSH_VLAN_PRIORITY]; |
195 | if (push_prio_exists) | |
956af371 | 196 | push_prio = nla_get_u8(tb[TCA_VLAN_PUSH_VLAN_PRIORITY]); |
c7e2b968 | 197 | break; |
19fbcb36 GN |
198 | case TCA_VLAN_ACT_POP_ETH: |
199 | break; | |
200 | case TCA_VLAN_ACT_PUSH_ETH: | |
201 | if (!tb[TCA_VLAN_PUSH_ETH_DST] || !tb[TCA_VLAN_PUSH_ETH_SRC]) { | |
202 | if (exists) | |
203 | tcf_idr_release(*a, bind); | |
204 | else | |
205 | tcf_idr_cleanup(tn, index); | |
206 | return -EINVAL; | |
207 | } | |
208 | break; | |
c7e2b968 | 209 | default: |
5026c9b1 | 210 | if (exists) |
65a206c0 | 211 | tcf_idr_release(*a, bind); |
0190c1d4 | 212 | else |
7be8ef2c | 213 | tcf_idr_cleanup(tn, index); |
c7e2b968 JP |
214 | return -EINVAL; |
215 | } | |
216 | action = parm->v_action; | |
217 | ||
5026c9b1 | 218 | if (!exists) { |
e3822678 VB |
219 | ret = tcf_idr_create_from_flags(tn, index, est, a, |
220 | &act_vlan_ops, bind, flags); | |
0190c1d4 | 221 | if (ret) { |
7be8ef2c | 222 | tcf_idr_cleanup(tn, index); |
c7e2b968 | 223 | return ret; |
0190c1d4 | 224 | } |
c7e2b968 JP |
225 | |
226 | ret = ACT_P_CREATED; | |
695176bf | 227 | } else if (!(flags & TCA_ACT_FLAGS_REPLACE)) { |
65a206c0 | 228 | tcf_idr_release(*a, bind); |
4e8ddd7f | 229 | return -EEXIST; |
c7e2b968 JP |
230 | } |
231 | ||
7e0c8892 DC |
232 | err = tcf_action_check_ctrlact(parm->action, tp, &goto_ch, extack); |
233 | if (err < 0) | |
234 | goto release_idr; | |
235 | ||
a85a970a | 236 | v = to_vlan(*a); |
c7e2b968 | 237 | |
4c5b9d96 MK |
238 | p = kzalloc(sizeof(*p), GFP_KERNEL); |
239 | if (!p) { | |
7e0c8892 DC |
240 | err = -ENOMEM; |
241 | goto put_chain; | |
4c5b9d96 | 242 | } |
c7e2b968 | 243 | |
4c5b9d96 MK |
244 | p->tcfv_action = action; |
245 | p->tcfv_push_vid = push_vid; | |
246 | p->tcfv_push_prio = push_prio; | |
9c5eee0a | 247 | p->tcfv_push_prio_exists = push_prio_exists || action == TCA_VLAN_ACT_PUSH; |
4c5b9d96 MK |
248 | p->tcfv_push_proto = push_proto; |
249 | ||
19fbcb36 GN |
250 | if (action == TCA_VLAN_ACT_PUSH_ETH) { |
251 | nla_memcpy(&p->tcfv_push_dst, tb[TCA_VLAN_PUSH_ETH_DST], | |
252 | ETH_ALEN); | |
253 | nla_memcpy(&p->tcfv_push_src, tb[TCA_VLAN_PUSH_ETH_SRC], | |
254 | ETH_ALEN); | |
255 | } | |
256 | ||
653cd284 | 257 | spin_lock_bh(&v->tcf_lock); |
7e0c8892 | 258 | goto_ch = tcf_action_set_ctrlact(*a, parm->action, goto_ch); |
445d3749 | 259 | p = rcu_replace_pointer(v->vlan_p, p, lockdep_is_held(&v->tcf_lock)); |
653cd284 | 260 | spin_unlock_bh(&v->tcf_lock); |
4c5b9d96 | 261 | |
7e0c8892 DC |
262 | if (goto_ch) |
263 | tcf_chain_put_by_act(goto_ch); | |
764e9a24 VB |
264 | if (p) |
265 | kfree_rcu(p, rcu); | |
c7e2b968 | 266 | |
c7e2b968 | 267 | return ret; |
7e0c8892 DC |
268 | put_chain: |
269 | if (goto_ch) | |
270 | tcf_chain_put_by_act(goto_ch); | |
271 | release_idr: | |
272 | tcf_idr_release(*a, bind); | |
273 | return err; | |
c7e2b968 JP |
274 | } |
275 | ||
9a63b255 | 276 | static void tcf_vlan_cleanup(struct tc_action *a) |
4c5b9d96 MK |
277 | { |
278 | struct tcf_vlan *v = to_vlan(a); | |
279 | struct tcf_vlan_params *p; | |
280 | ||
281 | p = rcu_dereference_protected(v->vlan_p, 1); | |
1edf8abe DC |
282 | if (p) |
283 | kfree_rcu(p, rcu); | |
4c5b9d96 MK |
284 | } |
285 | ||
c7e2b968 JP |
286 | static int tcf_vlan_dump(struct sk_buff *skb, struct tc_action *a, |
287 | int bind, int ref) | |
288 | { | |
289 | unsigned char *b = skb_tail_pointer(skb); | |
a85a970a | 290 | struct tcf_vlan *v = to_vlan(a); |
764e9a24 | 291 | struct tcf_vlan_params *p; |
c7e2b968 JP |
292 | struct tc_vlan opt = { |
293 | .index = v->tcf_index, | |
036bb443 VB |
294 | .refcnt = refcount_read(&v->tcf_refcnt) - ref, |
295 | .bindcnt = atomic_read(&v->tcf_bindcnt) - bind, | |
c7e2b968 JP |
296 | }; |
297 | struct tcf_t t; | |
298 | ||
653cd284 | 299 | spin_lock_bh(&v->tcf_lock); |
764e9a24 VB |
300 | opt.action = v->tcf_action; |
301 | p = rcu_dereference_protected(v->vlan_p, lockdep_is_held(&v->tcf_lock)); | |
302 | opt.v_action = p->tcfv_action; | |
c7e2b968 JP |
303 | if (nla_put(skb, TCA_VLAN_PARMS, sizeof(opt), &opt)) |
304 | goto nla_put_failure; | |
305 | ||
4c5b9d96 MK |
306 | if ((p->tcfv_action == TCA_VLAN_ACT_PUSH || |
307 | p->tcfv_action == TCA_VLAN_ACT_MODIFY) && | |
308 | (nla_put_u16(skb, TCA_VLAN_PUSH_VLAN_ID, p->tcfv_push_vid) || | |
0b0f43fe | 309 | nla_put_be16(skb, TCA_VLAN_PUSH_VLAN_PROTOCOL, |
4c5b9d96 | 310 | p->tcfv_push_proto) || |
8323b20f BS |
311 | (p->tcfv_push_prio_exists && |
312 | nla_put_u8(skb, TCA_VLAN_PUSH_VLAN_PRIORITY, p->tcfv_push_prio)))) | |
c7e2b968 JP |
313 | goto nla_put_failure; |
314 | ||
19fbcb36 GN |
315 | if (p->tcfv_action == TCA_VLAN_ACT_PUSH_ETH) { |
316 | if (nla_put(skb, TCA_VLAN_PUSH_ETH_DST, ETH_ALEN, | |
317 | p->tcfv_push_dst)) | |
318 | goto nla_put_failure; | |
319 | if (nla_put(skb, TCA_VLAN_PUSH_ETH_SRC, ETH_ALEN, | |
320 | p->tcfv_push_src)) | |
321 | goto nla_put_failure; | |
322 | } | |
323 | ||
48d8ee16 | 324 | tcf_tm_dump(&t, &v->tcf_tm); |
9854518e | 325 | if (nla_put_64bit(skb, TCA_VLAN_TM, sizeof(t), &t, TCA_VLAN_PAD)) |
c7e2b968 | 326 | goto nla_put_failure; |
653cd284 | 327 | spin_unlock_bh(&v->tcf_lock); |
764e9a24 | 328 | |
c7e2b968 JP |
329 | return skb->len; |
330 | ||
331 | nla_put_failure: | |
653cd284 | 332 | spin_unlock_bh(&v->tcf_lock); |
c7e2b968 JP |
333 | nlmsg_trim(skb, b); |
334 | return -1; | |
335 | } | |
336 | ||
4b61d3e8 PL |
337 | static void tcf_vlan_stats_update(struct tc_action *a, u64 bytes, u64 packets, |
338 | u64 drops, u64 lastuse, bool hw) | |
fa730a3b JP |
339 | { |
340 | struct tcf_vlan *v = to_vlan(a); | |
341 | struct tcf_t *tm = &v->tcf_tm; | |
342 | ||
4b61d3e8 | 343 | tcf_action_update_stats(a, bytes, packets, drops, hw); |
fa730a3b JP |
344 | tm->lastuse = max_t(u64, tm->lastuse, lastuse); |
345 | } | |
346 | ||
b35475c5 RM |
347 | static size_t tcf_vlan_get_fill_size(const struct tc_action *act) |
348 | { | |
349 | return nla_total_size(sizeof(struct tc_vlan)) | |
350 | + nla_total_size(sizeof(u16)) /* TCA_VLAN_PUSH_VLAN_ID */ | |
351 | + nla_total_size(sizeof(u16)) /* TCA_VLAN_PUSH_VLAN_PROTOCOL */ | |
352 | + nla_total_size(sizeof(u8)); /* TCA_VLAN_PUSH_VLAN_PRIORITY */ | |
353 | } | |
354 | ||
c54e1d92 | 355 | static int tcf_vlan_offload_act_setup(struct tc_action *act, void *entry_data, |
c2ccf84e IS |
356 | u32 *index_inc, bool bind, |
357 | struct netlink_ext_ack *extack) | |
c54e1d92 BZ |
358 | { |
359 | if (bind) { | |
360 | struct flow_action_entry *entry = entry_data; | |
361 | ||
362 | switch (tcf_vlan_action(act)) { | |
363 | case TCA_VLAN_ACT_PUSH: | |
364 | entry->id = FLOW_ACTION_VLAN_PUSH; | |
365 | entry->vlan.vid = tcf_vlan_push_vid(act); | |
366 | entry->vlan.proto = tcf_vlan_push_proto(act); | |
367 | entry->vlan.prio = tcf_vlan_push_prio(act); | |
368 | break; | |
369 | case TCA_VLAN_ACT_POP: | |
370 | entry->id = FLOW_ACTION_VLAN_POP; | |
371 | break; | |
372 | case TCA_VLAN_ACT_MODIFY: | |
373 | entry->id = FLOW_ACTION_VLAN_MANGLE; | |
374 | entry->vlan.vid = tcf_vlan_push_vid(act); | |
375 | entry->vlan.proto = tcf_vlan_push_proto(act); | |
376 | entry->vlan.prio = tcf_vlan_push_prio(act); | |
377 | break; | |
ab95465c MD |
378 | case TCA_VLAN_ACT_POP_ETH: |
379 | entry->id = FLOW_ACTION_VLAN_POP_ETH; | |
380 | break; | |
381 | case TCA_VLAN_ACT_PUSH_ETH: | |
382 | entry->id = FLOW_ACTION_VLAN_PUSH_ETH; | |
383 | tcf_vlan_push_eth(entry->vlan_push_eth.src, entry->vlan_push_eth.dst, act); | |
384 | break; | |
c54e1d92 | 385 | default: |
f8fab316 | 386 | NL_SET_ERR_MSG_MOD(extack, "Unsupported vlan action mode offload"); |
c54e1d92 BZ |
387 | return -EOPNOTSUPP; |
388 | } | |
389 | *index_inc = 1; | |
390 | } else { | |
8cbfe939 BZ |
391 | struct flow_offload_action *fl_action = entry_data; |
392 | ||
393 | switch (tcf_vlan_action(act)) { | |
394 | case TCA_VLAN_ACT_PUSH: | |
395 | fl_action->id = FLOW_ACTION_VLAN_PUSH; | |
396 | break; | |
397 | case TCA_VLAN_ACT_POP: | |
398 | fl_action->id = FLOW_ACTION_VLAN_POP; | |
399 | break; | |
400 | case TCA_VLAN_ACT_MODIFY: | |
401 | fl_action->id = FLOW_ACTION_VLAN_MANGLE; | |
402 | break; | |
ab95465c MD |
403 | case TCA_VLAN_ACT_POP_ETH: |
404 | fl_action->id = FLOW_ACTION_VLAN_POP_ETH; | |
405 | break; | |
406 | case TCA_VLAN_ACT_PUSH_ETH: | |
407 | fl_action->id = FLOW_ACTION_VLAN_PUSH_ETH; | |
408 | break; | |
8cbfe939 BZ |
409 | default: |
410 | return -EOPNOTSUPP; | |
411 | } | |
c54e1d92 BZ |
412 | } |
413 | ||
414 | return 0; | |
415 | } | |
416 | ||
c7e2b968 JP |
417 | static struct tc_action_ops act_vlan_ops = { |
418 | .kind = "vlan", | |
eddd2cf1 | 419 | .id = TCA_ID_VLAN, |
c7e2b968 | 420 | .owner = THIS_MODULE, |
8aa7f22e | 421 | .act = tcf_vlan_act, |
c7e2b968 JP |
422 | .dump = tcf_vlan_dump, |
423 | .init = tcf_vlan_init, | |
4c5b9d96 | 424 | .cleanup = tcf_vlan_cleanup, |
fa730a3b | 425 | .stats_update = tcf_vlan_stats_update, |
b35475c5 | 426 | .get_fill_size = tcf_vlan_get_fill_size, |
c54e1d92 | 427 | .offload_act_setup = tcf_vlan_offload_act_setup, |
a85a970a | 428 | .size = sizeof(struct tcf_vlan), |
ddf97ccd WC |
429 | }; |
430 | ||
431 | static __net_init int vlan_init_net(struct net *net) | |
432 | { | |
acd0a7ab | 433 | struct tc_action_net *tn = net_generic(net, act_vlan_ops.net_id); |
ddf97ccd | 434 | |
981471bd | 435 | return tc_action_net_init(net, tn, &act_vlan_ops); |
ddf97ccd WC |
436 | } |
437 | ||
039af9c6 | 438 | static void __net_exit vlan_exit_net(struct list_head *net_list) |
ddf97ccd | 439 | { |
acd0a7ab | 440 | tc_action_net_exit(net_list, act_vlan_ops.net_id); |
ddf97ccd WC |
441 | } |
442 | ||
443 | static struct pernet_operations vlan_net_ops = { | |
444 | .init = vlan_init_net, | |
039af9c6 | 445 | .exit_batch = vlan_exit_net, |
acd0a7ab | 446 | .id = &act_vlan_ops.net_id, |
ddf97ccd | 447 | .size = sizeof(struct tc_action_net), |
c7e2b968 JP |
448 | }; |
449 | ||
450 | static int __init vlan_init_module(void) | |
451 | { | |
ddf97ccd | 452 | return tcf_register_action(&act_vlan_ops, &vlan_net_ops); |
c7e2b968 JP |
453 | } |
454 | ||
455 | static void __exit vlan_cleanup_module(void) | |
456 | { | |
ddf97ccd | 457 | tcf_unregister_action(&act_vlan_ops, &vlan_net_ops); |
c7e2b968 JP |
458 | } |
459 | ||
460 | module_init(vlan_init_module); | |
461 | module_exit(vlan_cleanup_module); | |
462 | ||
463 | MODULE_AUTHOR("Jiri Pirko <jiri@resnulli.us>"); | |
464 | MODULE_DESCRIPTION("vlan manipulation actions"); | |
465 | MODULE_LICENSE("GPL v2"); |