Commit | Line | Data |
---|---|---|
2874c5fd | 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
1da177e4 | 2 | /* |
0c6965dd | 3 | * net/sched/act_pedit.c Generic packet editor |
1da177e4 | 4 | * |
1da177e4 LT |
5 | * Authors: Jamal Hadi Salim (2002-4) |
6 | */ | |
7 | ||
1da177e4 LT |
8 | #include <linux/types.h> |
9 | #include <linux/kernel.h> | |
1da177e4 | 10 | #include <linux/string.h> |
1da177e4 | 11 | #include <linux/errno.h> |
1da177e4 LT |
12 | #include <linux/skbuff.h> |
13 | #include <linux/rtnetlink.h> | |
14 | #include <linux/module.h> | |
15 | #include <linux/init.h> | |
5a0e3ad6 | 16 | #include <linux/slab.h> |
dc5fc579 | 17 | #include <net/netlink.h> |
1da177e4 LT |
18 | #include <net/pkt_sched.h> |
19 | #include <linux/tc_act/tc_pedit.h> | |
20 | #include <net/tc_act/tc_pedit.h> | |
71d0ed70 | 21 | #include <uapi/linux/tc_act/tc_pedit.h> |
6ac86ca3 | 22 | #include <net/pkt_cls.h> |
871cf386 | 23 | #include <net/tc_wrapper.h> |
1da177e4 | 24 | |
a85a970a | 25 | static struct tc_action_ops act_pedit_ops; |
ddf97ccd | 26 | |
53b2bf3f | 27 | static const struct nla_policy pedit_policy[TCA_PEDIT_MAX + 1] = { |
53f7e35f | 28 | [TCA_PEDIT_PARMS] = { .len = sizeof(struct tc_pedit) }, |
71d0ed70 | 29 | [TCA_PEDIT_KEYS_EX] = { .type = NLA_NESTED }, |
53b2bf3f PM |
30 | }; |
31 | ||
71d0ed70 | 32 | static const struct nla_policy pedit_key_ex_policy[TCA_PEDIT_KEY_EX_MAX + 1] = { |
50360345 PT |
33 | [TCA_PEDIT_KEY_EX_HTYPE] = |
34 | NLA_POLICY_MAX(NLA_U16, TCA_PEDIT_HDR_TYPE_MAX), | |
35 | [TCA_PEDIT_KEY_EX_CMD] = NLA_POLICY_MAX(NLA_U16, TCA_PEDIT_CMD_MAX), | |
71d0ed70 AV |
36 | }; |
37 | ||
38 | static struct tcf_pedit_key_ex *tcf_pedit_keys_ex_parse(struct nlattr *nla, | |
0c83c521 | 39 | u8 n, struct netlink_ext_ack *extack) |
71d0ed70 AV |
40 | { |
41 | struct tcf_pedit_key_ex *keys_ex; | |
42 | struct tcf_pedit_key_ex *k; | |
43 | const struct nlattr *ka; | |
44 | int err = -EINVAL; | |
45 | int rem; | |
46 | ||
f67169fe | 47 | if (!nla) |
71d0ed70 AV |
48 | return NULL; |
49 | ||
50 | keys_ex = kcalloc(n, sizeof(*k), GFP_KERNEL); | |
51 | if (!keys_ex) | |
52 | return ERR_PTR(-ENOMEM); | |
53 | ||
54 | k = keys_ex; | |
55 | ||
56 | nla_for_each_nested(ka, nla, rem) { | |
57 | struct nlattr *tb[TCA_PEDIT_KEY_EX_MAX + 1]; | |
58 | ||
59 | if (!n) { | |
0c83c521 | 60 | NL_SET_ERR_MSG_MOD(extack, "Can't parse more extended keys than requested"); |
71d0ed70 AV |
61 | err = -EINVAL; |
62 | goto err_out; | |
63 | } | |
64 | n--; | |
65 | ||
66 | if (nla_type(ka) != TCA_PEDIT_KEY_EX) { | |
0c83c521 | 67 | NL_SET_ERR_MSG_ATTR(extack, ka, "Unknown attribute, expected extended key"); |
71d0ed70 AV |
68 | err = -EINVAL; |
69 | goto err_out; | |
70 | } | |
71 | ||
8cb08174 JB |
72 | err = nla_parse_nested_deprecated(tb, TCA_PEDIT_KEY_EX_MAX, |
73 | ka, pedit_key_ex_policy, | |
74 | NULL); | |
71d0ed70 AV |
75 | if (err) |
76 | goto err_out; | |
77 | ||
0c83c521 PT |
78 | if (NL_REQ_ATTR_CHECK(extack, nla, tb, TCA_PEDIT_KEY_EX_HTYPE)) { |
79 | NL_SET_ERR_MSG(extack, "Missing required attribute"); | |
80 | err = -EINVAL; | |
81 | goto err_out; | |
82 | } | |
83 | ||
84 | if (NL_REQ_ATTR_CHECK(extack, nla, tb, TCA_PEDIT_KEY_EX_CMD)) { | |
85 | NL_SET_ERR_MSG(extack, "Missing required attribute"); | |
71d0ed70 AV |
86 | err = -EINVAL; |
87 | goto err_out; | |
88 | } | |
89 | ||
90 | k->htype = nla_get_u16(tb[TCA_PEDIT_KEY_EX_HTYPE]); | |
853a14ba | 91 | k->cmd = nla_get_u16(tb[TCA_PEDIT_KEY_EX_CMD]); |
71d0ed70 | 92 | |
71d0ed70 AV |
93 | k++; |
94 | } | |
95 | ||
c4f65b09 | 96 | if (n) { |
0c83c521 | 97 | NL_SET_ERR_MSG_MOD(extack, "Not enough extended keys to parse"); |
c4f65b09 | 98 | err = -EINVAL; |
71d0ed70 | 99 | goto err_out; |
c4f65b09 | 100 | } |
71d0ed70 AV |
101 | |
102 | return keys_ex; | |
103 | ||
104 | err_out: | |
105 | kfree(keys_ex); | |
106 | return ERR_PTR(err); | |
107 | } | |
108 | ||
109 | static int tcf_pedit_key_ex_dump(struct sk_buff *skb, | |
110 | struct tcf_pedit_key_ex *keys_ex, int n) | |
111 | { | |
ae0be8de MK |
112 | struct nlattr *keys_start = nla_nest_start_noflag(skb, |
113 | TCA_PEDIT_KEYS_EX); | |
71d0ed70 | 114 | |
85eb9af1 DC |
115 | if (!keys_start) |
116 | goto nla_failure; | |
71d0ed70 AV |
117 | for (; n > 0; n--) { |
118 | struct nlattr *key_start; | |
119 | ||
ae0be8de | 120 | key_start = nla_nest_start_noflag(skb, TCA_PEDIT_KEY_EX); |
85eb9af1 DC |
121 | if (!key_start) |
122 | goto nla_failure; | |
71d0ed70 | 123 | |
853a14ba | 124 | if (nla_put_u16(skb, TCA_PEDIT_KEY_EX_HTYPE, keys_ex->htype) || |
85eb9af1 DC |
125 | nla_put_u16(skb, TCA_PEDIT_KEY_EX_CMD, keys_ex->cmd)) |
126 | goto nla_failure; | |
71d0ed70 AV |
127 | |
128 | nla_nest_end(skb, key_start); | |
129 | ||
130 | keys_ex++; | |
131 | } | |
132 | ||
133 | nla_nest_end(skb, keys_start); | |
134 | ||
135 | return 0; | |
85eb9af1 DC |
136 | nla_failure: |
137 | nla_nest_cancel(skb, keys_start); | |
138 | return -EINVAL; | |
71d0ed70 AV |
139 | } |
140 | ||
52cf89f7 PT |
141 | static void tcf_pedit_cleanup_rcu(struct rcu_head *head) |
142 | { | |
143 | struct tcf_pedit_parms *parms = | |
144 | container_of(head, struct tcf_pedit_parms, rcu); | |
145 | ||
146 | kfree(parms->tcfp_keys_ex); | |
147 | kfree(parms->tcfp_keys); | |
148 | ||
149 | kfree(parms); | |
150 | } | |
151 | ||
c1b52739 | 152 | static int tcf_pedit_init(struct net *net, struct nlattr *nla, |
a85a970a | 153 | struct nlattr *est, struct tc_action **a, |
abbb0d33 VB |
154 | struct tcf_proto *tp, u32 flags, |
155 | struct netlink_ext_ack *extack) | |
1da177e4 | 156 | { |
acd0a7ab | 157 | struct tc_action_net *tn = net_generic(net, act_pedit_ops.net_id); |
695176bf | 158 | bool bind = flags & TCA_ACT_FLAGS_BIND; |
6ac86ca3 | 159 | struct tcf_chain *goto_ch = NULL; |
52cf89f7 PT |
160 | struct tcf_pedit_parms *oparms, *nparms; |
161 | struct nlattr *tb[TCA_PEDIT_MAX + 1]; | |
80f0f574 RM |
162 | struct tc_pedit *parm; |
163 | struct nlattr *pattr; | |
164 | struct tcf_pedit *p; | |
165 | int ret = 0, err; | |
8b796475 | 166 | int i, ksize; |
7be8ef2c | 167 | u32 index; |
1da177e4 | 168 | |
9868c0b2 RM |
169 | if (!nla) { |
170 | NL_SET_ERR_MSG_MOD(extack, "Pedit requires attributes to be passed"); | |
1da177e4 | 171 | return -EINVAL; |
9868c0b2 | 172 | } |
1da177e4 | 173 | |
8cb08174 JB |
174 | err = nla_parse_nested_deprecated(tb, TCA_PEDIT_MAX, nla, |
175 | pedit_policy, NULL); | |
cee63723 PM |
176 | if (err < 0) |
177 | return err; | |
178 | ||
71d0ed70 AV |
179 | pattr = tb[TCA_PEDIT_PARMS]; |
180 | if (!pattr) | |
181 | pattr = tb[TCA_PEDIT_PARMS_EX]; | |
9868c0b2 RM |
182 | if (!pattr) { |
183 | NL_SET_ERR_MSG_MOD(extack, "Missing required TCA_PEDIT_PARMS or TCA_PEDIT_PARMS_EX pedit attribute"); | |
1da177e4 | 184 | return -EINVAL; |
9868c0b2 | 185 | } |
71d0ed70 AV |
186 | |
187 | parm = nla_data(pattr); | |
71d0ed70 | 188 | |
7be8ef2c DL |
189 | index = parm->index; |
190 | err = tcf_idr_check_alloc(tn, &index, a, bind); | |
0190c1d4 | 191 | if (!err) { |
52cf89f7 PT |
192 | ret = tcf_idr_create_from_flags(tn, index, est, a, |
193 | &act_pedit_ops, bind, flags); | |
0190c1d4 | 194 | if (ret) { |
7be8ef2c | 195 | tcf_idr_cleanup(tn, index); |
e9e42292 | 196 | return ret; |
0190c1d4 | 197 | } |
1da177e4 | 198 | ret = ACT_P_CREATED; |
0190c1d4 | 199 | } else if (err > 0) { |
1a29321e | 200 | if (bind) |
e9e42292 | 201 | return 0; |
695176bf | 202 | if (!(flags & TCA_ACT_FLAGS_REPLACE)) { |
30e99ed6 | 203 | ret = -EEXIST; |
67b0c1a3 | 204 | goto out_release; |
1da177e4 | 205 | } |
0190c1d4 | 206 | } else { |
e9e42292 PT |
207 | return err; |
208 | } | |
209 | ||
210 | if (!parm->nkeys) { | |
211 | NL_SET_ERR_MSG_MOD(extack, "Pedit requires keys to be passed"); | |
212 | ret = -EINVAL; | |
213 | goto out_release; | |
214 | } | |
215 | ksize = parm->nkeys * sizeof(struct tc_pedit_key); | |
216 | if (nla_len(pattr) < sizeof(*parm) + ksize) { | |
217 | NL_SET_ERR_MSG_ATTR(extack, pattr, "Length of TCA_PEDIT_PARMS or TCA_PEDIT_PARMS_EX pedit attribute is invalid"); | |
218 | ret = -EINVAL; | |
219 | goto out_release; | |
220 | } | |
221 | ||
222 | nparms = kzalloc(sizeof(*nparms), GFP_KERNEL); | |
223 | if (!nparms) { | |
224 | ret = -ENOMEM; | |
225 | goto out_release; | |
226 | } | |
227 | ||
228 | nparms->tcfp_keys_ex = | |
0c83c521 | 229 | tcf_pedit_keys_ex_parse(tb[TCA_PEDIT_KEYS_EX], parm->nkeys, extack); |
e9e42292 PT |
230 | if (IS_ERR(nparms->tcfp_keys_ex)) { |
231 | ret = PTR_ERR(nparms->tcfp_keys_ex); | |
232 | goto out_free; | |
1da177e4 LT |
233 | } |
234 | ||
6ac86ca3 DC |
235 | err = tcf_action_check_ctrlact(parm->action, tp, &goto_ch, extack); |
236 | if (err < 0) { | |
237 | ret = err; | |
e9e42292 | 238 | goto out_free_ex; |
6ac86ca3 | 239 | } |
67b0c1a3 | 240 | |
52cf89f7 PT |
241 | nparms->tcfp_off_max_hint = 0; |
242 | nparms->tcfp_flags = parm->flags; | |
243 | nparms->tcfp_nkeys = parm->nkeys; | |
244 | ||
245 | nparms->tcfp_keys = kmalloc(ksize, GFP_KERNEL); | |
246 | if (!nparms->tcfp_keys) { | |
247 | ret = -ENOMEM; | |
248 | goto put_chain; | |
1da177e4 | 249 | } |
52cf89f7 PT |
250 | |
251 | memcpy(nparms->tcfp_keys, parm->keys, ksize); | |
252 | ||
253 | for (i = 0; i < nparms->tcfp_nkeys; ++i) { | |
e1201bc7 | 254 | u32 offmask = nparms->tcfp_keys[i].offmask; |
52cf89f7 | 255 | u32 cur = nparms->tcfp_keys[i].off; |
8b796475 | 256 | |
e1201bc7 PT |
257 | /* The AT option can be added to static offsets in the datapath */ |
258 | if (!offmask && cur % 4) { | |
259 | NL_SET_ERR_MSG_MOD(extack, "Offsets must be on 32bit boundaries"); | |
260 | ret = -EINVAL; | |
1b483d9f | 261 | goto out_free_keys; |
e1201bc7 PT |
262 | } |
263 | ||
4d42d54a | 264 | /* sanitize the shift value for any later use */ |
52cf89f7 PT |
265 | nparms->tcfp_keys[i].shift = min_t(size_t, |
266 | BITS_PER_TYPE(int) - 1, | |
267 | nparms->tcfp_keys[i].shift); | |
4d42d54a | 268 | |
8b796475 PA |
269 | /* The AT option can read a single byte, we can bound the actual |
270 | * value with uchar max. | |
271 | */ | |
e1201bc7 | 272 | cur += (0xff & offmask) >> nparms->tcfp_keys[i].shift; |
8b796475 PA |
273 | |
274 | /* Each key touches 4 bytes starting from the computed offset */ | |
52cf89f7 PT |
275 | nparms->tcfp_off_max_hint = |
276 | max(nparms->tcfp_off_max_hint, cur + 4); | |
8b796475 | 277 | } |
71d0ed70 | 278 | |
52cf89f7 PT |
279 | p = to_pedit(*a); |
280 | ||
281 | spin_lock_bh(&p->tcf_lock); | |
6ac86ca3 | 282 | goto_ch = tcf_action_set_ctrlact(*a, parm->action, goto_ch); |
52cf89f7 PT |
283 | oparms = rcu_replace_pointer(p->parms, nparms, 1); |
284 | spin_unlock_bh(&p->tcf_lock); | |
67b0c1a3 | 285 | |
52cf89f7 PT |
286 | if (oparms) |
287 | call_rcu(&oparms->rcu, tcf_pedit_cleanup_rcu); | |
71d0ed70 | 288 | |
6ac86ca3 DC |
289 | if (goto_ch) |
290 | tcf_chain_put_by_act(goto_ch); | |
52cf89f7 | 291 | |
1da177e4 | 292 | return ret; |
67b0c1a3 | 293 | |
1b483d9f PT |
294 | out_free_keys: |
295 | kfree(nparms->tcfp_keys); | |
6ac86ca3 DC |
296 | put_chain: |
297 | if (goto_ch) | |
298 | tcf_chain_put_by_act(goto_ch); | |
52cf89f7 PT |
299 | out_free_ex: |
300 | kfree(nparms->tcfp_keys_ex); | |
30e99ed6 | 301 | out_free: |
52cf89f7 | 302 | kfree(nparms); |
e9e42292 PT |
303 | out_release: |
304 | tcf_idr_release(*a, bind); | |
30e99ed6 | 305 | return ret; |
1da177e4 LT |
306 | } |
307 | ||
9a63b255 | 308 | static void tcf_pedit_cleanup(struct tc_action *a) |
1da177e4 | 309 | { |
a85a970a | 310 | struct tcf_pedit *p = to_pedit(a); |
52cf89f7 | 311 | struct tcf_pedit_parms *parms; |
80f0f574 | 312 | |
52cf89f7 PT |
313 | parms = rcu_dereference_protected(p->parms, 1); |
314 | ||
315 | if (parms) | |
316 | call_rcu(&parms->rcu, tcf_pedit_cleanup_rcu); | |
1da177e4 LT |
317 | } |
318 | ||
95c2027b AV |
319 | static bool offset_valid(struct sk_buff *skb, int offset) |
320 | { | |
321 | if (offset > 0 && offset > skb->len) | |
322 | return false; | |
323 | ||
324 | if (offset < 0 && -offset > skb_headroom(skb)) | |
325 | return false; | |
326 | ||
327 | return true; | |
328 | } | |
329 | ||
57714018 PT |
330 | static void pedit_skb_hdr_offset(struct sk_buff *skb, |
331 | enum pedit_header_type htype, int *hoffset) | |
71d0ed70 | 332 | { |
57714018 | 333 | /* 'htype' is validated in the netlink parsing */ |
71d0ed70 AV |
334 | switch (htype) { |
335 | case TCA_PEDIT_KEY_EX_HDR_TYPE_ETH: | |
57714018 | 336 | if (skb_mac_header_was_set(skb)) |
71d0ed70 | 337 | *hoffset = skb_mac_offset(skb); |
71d0ed70 AV |
338 | break; |
339 | case TCA_PEDIT_KEY_EX_HDR_TYPE_NETWORK: | |
340 | case TCA_PEDIT_KEY_EX_HDR_TYPE_IP4: | |
341 | case TCA_PEDIT_KEY_EX_HDR_TYPE_IP6: | |
342 | *hoffset = skb_network_offset(skb); | |
71d0ed70 AV |
343 | break; |
344 | case TCA_PEDIT_KEY_EX_HDR_TYPE_TCP: | |
345 | case TCA_PEDIT_KEY_EX_HDR_TYPE_UDP: | |
57714018 | 346 | if (skb_transport_header_was_set(skb)) |
71d0ed70 | 347 | *hoffset = skb_transport_offset(skb); |
71d0ed70 AV |
348 | break; |
349 | default: | |
71d0ed70 | 350 | break; |
0a80848e | 351 | } |
71d0ed70 AV |
352 | } |
353 | ||
871cf386 PT |
354 | TC_INDIRECT_SCOPE int tcf_pedit_act(struct sk_buff *skb, |
355 | const struct tc_action *a, | |
356 | struct tcf_result *res) | |
1da177e4 | 357 | { |
95b06938 PT |
358 | enum pedit_header_type htype = TCA_PEDIT_KEY_EX_HDR_TYPE_NETWORK; |
359 | enum pedit_cmd cmd = TCA_PEDIT_KEY_EX_CMD_SET; | |
a85a970a | 360 | struct tcf_pedit *p = to_pedit(a); |
95b06938 | 361 | struct tcf_pedit_key_ex *tkey_ex; |
52cf89f7 | 362 | struct tcf_pedit_parms *parms; |
95b06938 | 363 | struct tc_pedit_key *tkey; |
8b796475 | 364 | u32 max_offset; |
4749c3ef | 365 | int i; |
1da177e4 | 366 | |
52cf89f7 | 367 | parms = rcu_dereference_bh(p->parms); |
1da177e4 | 368 | |
8b796475 PA |
369 | max_offset = (skb_transport_header_was_set(skb) ? |
370 | skb_transport_offset(skb) : | |
371 | skb_network_offset(skb)) + | |
52cf89f7 | 372 | parms->tcfp_off_max_hint; |
8b796475 | 373 | if (skb_ensure_writable(skb, min(skb->len, max_offset))) |
52cf89f7 | 374 | goto done; |
8b796475 | 375 | |
9c4a4e48 | 376 | tcf_lastuse_update(&p->tcf_tm); |
52cf89f7 | 377 | tcf_action_update_bstats(&p->common, skb); |
1da177e4 | 378 | |
95b06938 PT |
379 | tkey = parms->tcfp_keys; |
380 | tkey_ex = parms->tcfp_keys_ex; | |
71d0ed70 | 381 | |
95b06938 PT |
382 | for (i = parms->tcfp_nkeys; i > 0; i--, tkey++) { |
383 | int offset = tkey->off; | |
57714018 | 384 | int hoffset = 0; |
95b06938 | 385 | u32 *ptr, hdata; |
95b06938 | 386 | u32 val; |
1da177e4 | 387 | |
95b06938 PT |
388 | if (tkey_ex) { |
389 | htype = tkey_ex->htype; | |
390 | cmd = tkey_ex->cmd; | |
1da177e4 | 391 | |
95b06938 PT |
392 | tkey_ex++; |
393 | } | |
95c2027b | 394 | |
57714018 | 395 | pedit_skb_hdr_offset(skb, htype, &hoffset); |
1da177e4 | 396 | |
95b06938 PT |
397 | if (tkey->offmask) { |
398 | u8 *d, _d; | |
399 | ||
400 | if (!offset_valid(skb, hoffset + tkey->at)) { | |
e3c9673e PT |
401 | pr_info_ratelimited("tc action pedit 'at' offset %d out of bounds\n", |
402 | hoffset + tkey->at); | |
853a14ba AV |
403 | goto bad; |
404 | } | |
95b06938 PT |
405 | d = skb_header_pointer(skb, hoffset + tkey->at, |
406 | sizeof(_d), &_d); | |
407 | if (!d) | |
408 | goto bad; | |
853a14ba | 409 | |
e1201bc7 PT |
410 | offset += (*d & tkey->offmask) >> tkey->shift; |
411 | if (offset % 4) { | |
e3c9673e | 412 | pr_info_ratelimited("tc action pedit offset must be on 32 bit boundaries\n"); |
e1201bc7 PT |
413 | goto bad; |
414 | } | |
1da177e4 | 415 | } |
10297b99 | 416 | |
95b06938 | 417 | if (!offset_valid(skb, hoffset + offset)) { |
e3c9673e | 418 | pr_info_ratelimited("tc action pedit offset %d out of bounds\n", hoffset + offset); |
95b06938 PT |
419 | goto bad; |
420 | } | |
421 | ||
422 | ptr = skb_header_pointer(skb, hoffset + offset, | |
423 | sizeof(hdata), &hdata); | |
424 | if (!ptr) | |
425 | goto bad; | |
426 | /* just do it, baby */ | |
427 | switch (cmd) { | |
428 | case TCA_PEDIT_KEY_EX_CMD_SET: | |
429 | val = tkey->val; | |
430 | break; | |
431 | case TCA_PEDIT_KEY_EX_CMD_ADD: | |
432 | val = (*ptr + tkey->val) & ~tkey->mask; | |
433 | break; | |
434 | default: | |
e3c9673e | 435 | pr_info_ratelimited("tc action pedit bad command (%d)\n", cmd); |
95b06938 PT |
436 | goto bad; |
437 | } | |
438 | ||
439 | *ptr = ((*ptr & tkey->mask) ^ val); | |
440 | if (ptr == &hdata) | |
441 | skb_store_bits(skb, hoffset + offset, ptr, 4); | |
80f0f574 | 442 | } |
1da177e4 | 443 | |
95b06938 PT |
444 | goto done; |
445 | ||
1da177e4 | 446 | bad: |
2d2e75d2 | 447 | tcf_action_inc_overlimit_qstats(&p->common); |
52cf89f7 | 448 | done: |
e9ce1cd3 | 449 | return p->tcf_action; |
1da177e4 LT |
450 | } |
451 | ||
4b61d3e8 PL |
452 | static void tcf_pedit_stats_update(struct tc_action *a, u64 bytes, u64 packets, |
453 | u64 drops, u64 lastuse, bool hw) | |
d4d9d9c5 PM |
454 | { |
455 | struct tcf_pedit *d = to_pedit(a); | |
456 | struct tcf_t *tm = &d->tcf_tm; | |
457 | ||
4b61d3e8 | 458 | tcf_action_update_stats(a, bytes, packets, drops, hw); |
d4d9d9c5 PM |
459 | tm->lastuse = max_t(u64, tm->lastuse, lastuse); |
460 | } | |
461 | ||
e9ce1cd3 DM |
462 | static int tcf_pedit_dump(struct sk_buff *skb, struct tc_action *a, |
463 | int bind, int ref) | |
1da177e4 | 464 | { |
27a884dc | 465 | unsigned char *b = skb_tail_pointer(skb); |
a85a970a | 466 | struct tcf_pedit *p = to_pedit(a); |
52cf89f7 | 467 | struct tcf_pedit_parms *parms; |
1da177e4 | 468 | struct tc_pedit *opt; |
1da177e4 | 469 | struct tcf_t t; |
10297b99 YH |
470 | int s; |
471 | ||
52cf89f7 PT |
472 | spin_lock_bh(&p->tcf_lock); |
473 | parms = rcu_dereference_protected(p->parms, 1); | |
474 | s = struct_size(opt, keys, parms->tcfp_nkeys); | |
1da177e4 | 475 | |
0da974f4 | 476 | opt = kzalloc(s, GFP_ATOMIC); |
52cf89f7 PT |
477 | if (unlikely(!opt)) { |
478 | spin_unlock_bh(&p->tcf_lock); | |
1da177e4 | 479 | return -ENOBUFS; |
52cf89f7 | 480 | } |
1da177e4 | 481 | |
52cf89f7 PT |
482 | memcpy(opt->keys, parms->tcfp_keys, |
483 | flex_array_size(opt, keys, parms->tcfp_nkeys)); | |
e9ce1cd3 | 484 | opt->index = p->tcf_index; |
52cf89f7 PT |
485 | opt->nkeys = parms->tcfp_nkeys; |
486 | opt->flags = parms->tcfp_flags; | |
e9ce1cd3 | 487 | opt->action = p->tcf_action; |
036bb443 VB |
488 | opt->refcnt = refcount_read(&p->tcf_refcnt) - ref; |
489 | opt->bindcnt = atomic_read(&p->tcf_bindcnt) - bind; | |
1da177e4 | 490 | |
52cf89f7 PT |
491 | if (parms->tcfp_keys_ex) { |
492 | if (tcf_pedit_key_ex_dump(skb, parms->tcfp_keys_ex, | |
493 | parms->tcfp_nkeys)) | |
85eb9af1 | 494 | goto nla_put_failure; |
71d0ed70 AV |
495 | |
496 | if (nla_put(skb, TCA_PEDIT_PARMS_EX, s, opt)) | |
497 | goto nla_put_failure; | |
498 | } else { | |
499 | if (nla_put(skb, TCA_PEDIT_PARMS, s, opt)) | |
500 | goto nla_put_failure; | |
501 | } | |
48d8ee16 JHS |
502 | |
503 | tcf_tm_dump(&t, &p->tcf_tm); | |
9854518e | 504 | if (nla_put_64bit(skb, TCA_PEDIT_TM, sizeof(t), &t, TCA_PEDIT_PAD)) |
1b34ec43 | 505 | goto nla_put_failure; |
67b0c1a3 | 506 | spin_unlock_bh(&p->tcf_lock); |
48d8ee16 | 507 | |
541673c8 | 508 | kfree(opt); |
1da177e4 LT |
509 | return skb->len; |
510 | ||
7ba699c6 | 511 | nla_put_failure: |
67b0c1a3 | 512 | spin_unlock_bh(&p->tcf_lock); |
dc5fc579 | 513 | nlmsg_trim(skb, b); |
541673c8 | 514 | kfree(opt); |
1da177e4 LT |
515 | return -1; |
516 | } | |
517 | ||
c54e1d92 | 518 | static int tcf_pedit_offload_act_setup(struct tc_action *act, void *entry_data, |
c2ccf84e IS |
519 | u32 *index_inc, bool bind, |
520 | struct netlink_ext_ack *extack) | |
c54e1d92 BZ |
521 | { |
522 | if (bind) { | |
523 | struct flow_action_entry *entry = entry_data; | |
524 | int k; | |
525 | ||
526 | for (k = 0; k < tcf_pedit_nkeys(act); k++) { | |
527 | switch (tcf_pedit_cmd(act, k)) { | |
528 | case TCA_PEDIT_KEY_EX_CMD_SET: | |
529 | entry->id = FLOW_ACTION_MANGLE; | |
530 | break; | |
531 | case TCA_PEDIT_KEY_EX_CMD_ADD: | |
532 | entry->id = FLOW_ACTION_ADD; | |
533 | break; | |
534 | default: | |
bf3b99e4 | 535 | NL_SET_ERR_MSG_MOD(extack, "Unsupported pedit command offload"); |
c54e1d92 BZ |
536 | return -EOPNOTSUPP; |
537 | } | |
538 | entry->mangle.htype = tcf_pedit_htype(act, k); | |
539 | entry->mangle.mask = tcf_pedit_mask(act, k); | |
540 | entry->mangle.val = tcf_pedit_val(act, k); | |
541 | entry->mangle.offset = tcf_pedit_offset(act, k); | |
542 | entry->hw_stats = tc_act_hw_stats(act->hw_stats); | |
543 | entry++; | |
544 | } | |
545 | *index_inc = k; | |
546 | } else { | |
3320f36f OS |
547 | struct flow_offload_action *fl_action = entry_data; |
548 | u32 cmd = tcf_pedit_cmd(act, 0); | |
549 | int k; | |
550 | ||
551 | switch (cmd) { | |
552 | case TCA_PEDIT_KEY_EX_CMD_SET: | |
553 | fl_action->id = FLOW_ACTION_MANGLE; | |
554 | break; | |
555 | case TCA_PEDIT_KEY_EX_CMD_ADD: | |
556 | fl_action->id = FLOW_ACTION_ADD; | |
557 | break; | |
558 | default: | |
559 | NL_SET_ERR_MSG_MOD(extack, "Unsupported pedit command offload"); | |
560 | return -EOPNOTSUPP; | |
561 | } | |
562 | ||
563 | for (k = 1; k < tcf_pedit_nkeys(act); k++) { | |
564 | if (cmd != tcf_pedit_cmd(act, k)) { | |
565 | NL_SET_ERR_MSG_MOD(extack, "Unsupported pedit command offload"); | |
566 | return -EOPNOTSUPP; | |
567 | } | |
568 | } | |
c54e1d92 BZ |
569 | } |
570 | ||
571 | return 0; | |
572 | } | |
573 | ||
e9ce1cd3 | 574 | static struct tc_action_ops act_pedit_ops = { |
1da177e4 | 575 | .kind = "pedit", |
eddd2cf1 | 576 | .id = TCA_ID_PEDIT, |
1da177e4 | 577 | .owner = THIS_MODULE, |
6a2b401c | 578 | .act = tcf_pedit_act, |
d4d9d9c5 | 579 | .stats_update = tcf_pedit_stats_update, |
1da177e4 LT |
580 | .dump = tcf_pedit_dump, |
581 | .cleanup = tcf_pedit_cleanup, | |
1da177e4 | 582 | .init = tcf_pedit_init, |
c54e1d92 | 583 | .offload_act_setup = tcf_pedit_offload_act_setup, |
a85a970a | 584 | .size = sizeof(struct tcf_pedit), |
ddf97ccd WC |
585 | }; |
586 | ||
587 | static __net_init int pedit_init_net(struct net *net) | |
588 | { | |
acd0a7ab | 589 | struct tc_action_net *tn = net_generic(net, act_pedit_ops.net_id); |
ddf97ccd | 590 | |
981471bd | 591 | return tc_action_net_init(net, tn, &act_pedit_ops); |
ddf97ccd WC |
592 | } |
593 | ||
039af9c6 | 594 | static void __net_exit pedit_exit_net(struct list_head *net_list) |
ddf97ccd | 595 | { |
acd0a7ab | 596 | tc_action_net_exit(net_list, act_pedit_ops.net_id); |
ddf97ccd WC |
597 | } |
598 | ||
599 | static struct pernet_operations pedit_net_ops = { | |
600 | .init = pedit_init_net, | |
039af9c6 | 601 | .exit_batch = pedit_exit_net, |
acd0a7ab | 602 | .id = &act_pedit_ops.net_id, |
ddf97ccd | 603 | .size = sizeof(struct tc_action_net), |
1da177e4 LT |
604 | }; |
605 | ||
606 | MODULE_AUTHOR("Jamal Hadi Salim(2002-4)"); | |
607 | MODULE_DESCRIPTION("Generic Packet Editor actions"); | |
608 | MODULE_LICENSE("GPL"); | |
609 | ||
e9ce1cd3 | 610 | static int __init pedit_init_module(void) |
1da177e4 | 611 | { |
ddf97ccd | 612 | return tcf_register_action(&act_pedit_ops, &pedit_net_ops); |
1da177e4 LT |
613 | } |
614 | ||
e9ce1cd3 | 615 | static void __exit pedit_cleanup_module(void) |
1da177e4 | 616 | { |
ddf97ccd | 617 | tcf_unregister_action(&act_pedit_ops, &pedit_net_ops); |
1da177e4 LT |
618 | } |
619 | ||
620 | module_init(pedit_init_module); | |
621 | module_exit(pedit_cleanup_module); |