]>
Commit | Line | Data |
---|---|---|
c9626a2c PNA |
1 | /* SPDX-License-Identifier: GPL-2.0 */ |
2 | #include <linux/init.h> | |
3 | #include <linux/module.h> | |
4 | #include <linux/netfilter.h> | |
5 | #include <net/flow_offload.h> | |
6 | #include <net/netfilter/nf_tables.h> | |
7 | #include <net/netfilter/nf_tables_offload.h> | |
8 | #include <net/pkt_cls.h> | |
9 | ||
10 | static struct nft_flow_rule *nft_flow_rule_alloc(int num_actions) | |
11 | { | |
12 | struct nft_flow_rule *flow; | |
13 | ||
14 | flow = kzalloc(sizeof(struct nft_flow_rule), GFP_KERNEL); | |
15 | if (!flow) | |
16 | return NULL; | |
17 | ||
18 | flow->rule = flow_rule_alloc(num_actions); | |
19 | if (!flow->rule) { | |
20 | kfree(flow); | |
21 | return NULL; | |
22 | } | |
23 | ||
24 | flow->rule->match.dissector = &flow->match.dissector; | |
25 | flow->rule->match.mask = &flow->match.mask; | |
26 | flow->rule->match.key = &flow->match.key; | |
27 | ||
28 | return flow; | |
29 | } | |
30 | ||
be2861dc PNA |
31 | struct nft_flow_rule *nft_flow_rule_create(struct net *net, |
32 | const struct nft_rule *rule) | |
c9626a2c | 33 | { |
b44492af | 34 | struct nft_offload_ctx *ctx; |
c9626a2c PNA |
35 | struct nft_flow_rule *flow; |
36 | int num_actions = 0, err; | |
37 | struct nft_expr *expr; | |
38 | ||
39 | expr = nft_expr_first(rule); | |
40 | while (expr->ops && expr != nft_expr_last(rule)) { | |
41 | if (expr->ops->offload_flags & NFT_OFFLOAD_F_ACTION) | |
42 | num_actions++; | |
43 | ||
44 | expr = nft_expr_next(expr); | |
45 | } | |
46 | ||
47 | flow = nft_flow_rule_alloc(num_actions); | |
48 | if (!flow) | |
49 | return ERR_PTR(-ENOMEM); | |
50 | ||
51 | expr = nft_expr_first(rule); | |
b44492af AB |
52 | |
53 | ctx = kzalloc(sizeof(struct nft_offload_ctx), GFP_KERNEL); | |
54 | if (!ctx) { | |
55 | err = -ENOMEM; | |
56 | goto err_out; | |
57 | } | |
be2861dc | 58 | ctx->net = net; |
b44492af AB |
59 | ctx->dep.type = NFT_OFFLOAD_DEP_UNSPEC; |
60 | ||
c9626a2c PNA |
61 | while (expr->ops && expr != nft_expr_last(rule)) { |
62 | if (!expr->ops->offload) { | |
63 | err = -EOPNOTSUPP; | |
64 | goto err_out; | |
65 | } | |
b44492af | 66 | err = expr->ops->offload(ctx, flow, expr); |
c9626a2c PNA |
67 | if (err < 0) |
68 | goto err_out; | |
69 | ||
70 | expr = nft_expr_next(expr); | |
71 | } | |
b44492af AB |
72 | flow->proto = ctx->dep.l3num; |
73 | kfree(ctx); | |
c9626a2c PNA |
74 | |
75 | return flow; | |
76 | err_out: | |
b44492af | 77 | kfree(ctx); |
c9626a2c PNA |
78 | nft_flow_rule_destroy(flow); |
79 | ||
80 | return ERR_PTR(err); | |
81 | } | |
82 | ||
83 | void nft_flow_rule_destroy(struct nft_flow_rule *flow) | |
84 | { | |
be2861dc PNA |
85 | struct flow_action_entry *entry; |
86 | int i; | |
87 | ||
88 | flow_action_for_each(i, entry, &flow->rule->action) { | |
89 | switch (entry->id) { | |
90 | case FLOW_ACTION_REDIRECT: | |
91 | case FLOW_ACTION_MIRRED: | |
92 | dev_put(entry->dev); | |
93 | break; | |
94 | default: | |
95 | break; | |
96 | } | |
97 | } | |
c9626a2c PNA |
98 | kfree(flow->rule); |
99 | kfree(flow); | |
100 | } | |
101 | ||
102 | void nft_offload_set_dependency(struct nft_offload_ctx *ctx, | |
103 | enum nft_offload_dep_type type) | |
104 | { | |
105 | ctx->dep.type = type; | |
106 | } | |
107 | ||
108 | void nft_offload_update_dependency(struct nft_offload_ctx *ctx, | |
109 | const void *data, u32 len) | |
110 | { | |
111 | switch (ctx->dep.type) { | |
112 | case NFT_OFFLOAD_DEP_NETWORK: | |
113 | WARN_ON(len != sizeof(__u16)); | |
114 | memcpy(&ctx->dep.l3num, data, sizeof(__u16)); | |
115 | break; | |
116 | case NFT_OFFLOAD_DEP_TRANSPORT: | |
117 | WARN_ON(len != sizeof(__u8)); | |
118 | memcpy(&ctx->dep.protonum, data, sizeof(__u8)); | |
119 | break; | |
120 | default: | |
121 | break; | |
122 | } | |
123 | ctx->dep.type = NFT_OFFLOAD_DEP_UNSPEC; | |
124 | } | |
125 | ||
126 | static void nft_flow_offload_common_init(struct flow_cls_common_offload *common, | |
3bc158f8 PNA |
127 | __be16 proto, int priority, |
128 | struct netlink_ext_ack *extack) | |
c9626a2c PNA |
129 | { |
130 | common->protocol = proto; | |
3bc158f8 | 131 | common->prio = priority; |
c9626a2c PNA |
132 | common->extack = extack; |
133 | } | |
134 | ||
b5828880 PNA |
135 | static int nft_setup_cb_call(enum tc_setup_type type, void *type_data, |
136 | struct list_head *cb_list) | |
c9626a2c PNA |
137 | { |
138 | struct flow_block_cb *block_cb; | |
139 | int err; | |
140 | ||
b5828880 | 141 | list_for_each_entry(block_cb, cb_list, list) { |
c9626a2c PNA |
142 | err = block_cb->cb(type, type_data, block_cb->cb_priv); |
143 | if (err < 0) | |
144 | return err; | |
145 | } | |
146 | return 0; | |
147 | } | |
148 | ||
3bc158f8 PNA |
149 | int nft_chain_offload_priority(struct nft_base_chain *basechain) |
150 | { | |
151 | if (basechain->ops.priority <= 0 || | |
152 | basechain->ops.priority > USHRT_MAX) | |
153 | return -1; | |
154 | ||
155 | return 0; | |
156 | } | |
157 | ||
c5d27527 PNA |
158 | static void nft_flow_cls_offload_setup(struct flow_cls_offload *cls_flow, |
159 | const struct nft_base_chain *basechain, | |
160 | const struct nft_rule *rule, | |
161 | const struct nft_flow_rule *flow, | |
be193f5e | 162 | struct netlink_ext_ack *extack, |
c5d27527 PNA |
163 | enum flow_cls_command command) |
164 | { | |
c5d27527 PNA |
165 | __be16 proto = ETH_P_ALL; |
166 | ||
167 | memset(cls_flow, 0, sizeof(*cls_flow)); | |
168 | ||
169 | if (flow) | |
170 | proto = flow->proto; | |
171 | ||
172 | nft_flow_offload_common_init(&cls_flow->common, proto, | |
be193f5e | 173 | basechain->ops.priority, extack); |
c5d27527 PNA |
174 | cls_flow->command = command; |
175 | cls_flow->cookie = (unsigned long) rule; | |
176 | if (flow) | |
177 | cls_flow->rule = flow->rule; | |
178 | } | |
179 | ||
e211aab7 | 180 | static int nft_flow_offload_rule(struct nft_chain *chain, |
181 | struct nft_rule *rule, | |
182 | struct nft_flow_rule *flow, | |
c9626a2c PNA |
183 | enum flow_cls_command command) |
184 | { | |
be193f5e | 185 | struct netlink_ext_ack extack = {}; |
c5d27527 | 186 | struct flow_cls_offload cls_flow; |
c9626a2c | 187 | struct nft_base_chain *basechain; |
c9626a2c | 188 | |
e211aab7 | 189 | if (!nft_is_base_chain(chain)) |
c9626a2c PNA |
190 | return -EOPNOTSUPP; |
191 | ||
e211aab7 | 192 | basechain = nft_base_chain(chain); |
be193f5e PNA |
193 | nft_flow_cls_offload_setup(&cls_flow, basechain, rule, flow, &extack, |
194 | command); | |
c9626a2c | 195 | |
b5828880 PNA |
196 | return nft_setup_cb_call(TC_SETUP_CLSFLOWER, &cls_flow, |
197 | &basechain->flow_block.cb_list); | |
c9626a2c PNA |
198 | } |
199 | ||
200 | static int nft_flow_offload_bind(struct flow_block_offload *bo, | |
201 | struct nft_base_chain *basechain) | |
202 | { | |
14bfb13f | 203 | list_splice(&bo->cb_list, &basechain->flow_block.cb_list); |
c9626a2c PNA |
204 | return 0; |
205 | } | |
206 | ||
207 | static int nft_flow_offload_unbind(struct flow_block_offload *bo, | |
208 | struct nft_base_chain *basechain) | |
209 | { | |
210 | struct flow_block_cb *block_cb, *next; | |
bbaef955 | 211 | struct flow_cls_offload cls_flow; |
be193f5e | 212 | struct netlink_ext_ack extack; |
bbaef955 PNA |
213 | struct nft_chain *chain; |
214 | struct nft_rule *rule; | |
215 | ||
216 | chain = &basechain->chain; | |
217 | list_for_each_entry(rule, &chain->rules, list) { | |
be193f5e | 218 | memset(&extack, 0, sizeof(extack)); |
bbaef955 | 219 | nft_flow_cls_offload_setup(&cls_flow, basechain, rule, NULL, |
be193f5e | 220 | &extack, FLOW_CLS_DESTROY); |
bbaef955 PNA |
221 | nft_setup_cb_call(TC_SETUP_CLSFLOWER, &cls_flow, &bo->cb_list); |
222 | } | |
c9626a2c PNA |
223 | |
224 | list_for_each_entry_safe(block_cb, next, &bo->cb_list, list) { | |
225 | list_del(&block_cb->list); | |
226 | flow_block_cb_free(block_cb); | |
227 | } | |
228 | ||
229 | return 0; | |
230 | } | |
231 | ||
9a32669f | 232 | static int nft_block_setup(struct nft_base_chain *basechain, |
233 | struct flow_block_offload *bo, | |
234 | enum flow_block_command cmd) | |
235 | { | |
236 | int err; | |
237 | ||
238 | switch (cmd) { | |
239 | case FLOW_BLOCK_BIND: | |
240 | err = nft_flow_offload_bind(bo, basechain); | |
241 | break; | |
242 | case FLOW_BLOCK_UNBIND: | |
243 | err = nft_flow_offload_unbind(bo, basechain); | |
244 | break; | |
245 | default: | |
246 | WARN_ON_ONCE(1); | |
247 | err = -EOPNOTSUPP; | |
248 | } | |
249 | ||
250 | return err; | |
251 | } | |
252 | ||
75ceaf86 PNA |
253 | static void nft_flow_block_offload_init(struct flow_block_offload *bo, |
254 | struct net *net, | |
255 | enum flow_block_command cmd, | |
256 | struct nft_base_chain *basechain, | |
257 | struct netlink_ext_ack *extack) | |
258 | { | |
259 | memset(bo, 0, sizeof(*bo)); | |
260 | bo->net = net; | |
261 | bo->block = &basechain->flow_block; | |
262 | bo->command = cmd; | |
263 | bo->binder_type = FLOW_BLOCK_BINDER_TYPE_CLSACT_INGRESS; | |
264 | bo->extack = extack; | |
265 | INIT_LIST_HEAD(&bo->cb_list); | |
266 | } | |
267 | ||
9a32669f | 268 | static int nft_block_offload_cmd(struct nft_base_chain *chain, |
269 | struct net_device *dev, | |
270 | enum flow_block_command cmd) | |
271 | { | |
272 | struct netlink_ext_ack extack = {}; | |
75ceaf86 | 273 | struct flow_block_offload bo; |
9a32669f | 274 | int err; |
275 | ||
75ceaf86 | 276 | nft_flow_block_offload_init(&bo, dev_net(dev), cmd, chain, &extack); |
9a32669f | 277 | |
278 | err = dev->netdev_ops->ndo_setup_tc(dev, TC_SETUP_BLOCK, &bo); | |
279 | if (err < 0) | |
280 | return err; | |
281 | ||
282 | return nft_block_setup(chain, &bo, cmd); | |
283 | } | |
284 | ||
285 | static void nft_indr_block_ing_cmd(struct net_device *dev, | |
286 | struct nft_base_chain *chain, | |
287 | flow_indr_block_bind_cb_t *cb, | |
288 | void *cb_priv, | |
289 | enum flow_block_command cmd) | |
290 | { | |
291 | struct netlink_ext_ack extack = {}; | |
75ceaf86 | 292 | struct flow_block_offload bo; |
9a32669f | 293 | |
294 | if (!chain) | |
295 | return; | |
296 | ||
75ceaf86 | 297 | nft_flow_block_offload_init(&bo, dev_net(dev), cmd, chain, &extack); |
9a32669f | 298 | |
299 | cb(dev, cb_priv, TC_SETUP_BLOCK, &bo); | |
300 | ||
301 | nft_block_setup(chain, &bo, cmd); | |
302 | } | |
303 | ||
304 | static int nft_indr_block_offload_cmd(struct nft_base_chain *chain, | |
305 | struct net_device *dev, | |
306 | enum flow_block_command cmd) | |
307 | { | |
9a32669f | 308 | struct netlink_ext_ack extack = {}; |
75ceaf86 | 309 | struct flow_block_offload bo; |
9a32669f | 310 | |
75ceaf86 | 311 | nft_flow_block_offload_init(&bo, dev_net(dev), cmd, chain, &extack); |
9a32669f | 312 | |
313 | flow_indr_block_call(dev, &bo, cmd); | |
314 | ||
315 | if (list_empty(&bo.cb_list)) | |
316 | return -EOPNOTSUPP; | |
317 | ||
318 | return nft_block_setup(chain, &bo, cmd); | |
319 | } | |
320 | ||
c9626a2c PNA |
321 | #define FLOW_SETUP_BLOCK TC_SETUP_BLOCK |
322 | ||
6df5490f PNA |
323 | static int nft_chain_offload_cmd(struct nft_base_chain *basechain, |
324 | struct net_device *dev, | |
325 | enum flow_block_command cmd) | |
326 | { | |
327 | int err; | |
328 | ||
329 | if (dev->netdev_ops->ndo_setup_tc) | |
330 | err = nft_block_offload_cmd(basechain, dev, cmd); | |
331 | else | |
332 | err = nft_indr_block_offload_cmd(basechain, dev, cmd); | |
333 | ||
334 | return err; | |
335 | } | |
336 | ||
ead3952e | 337 | static int nft_flow_block_chain(struct nft_base_chain *basechain, |
d54725cd | 338 | const struct net_device *this_dev, |
ead3952e PNA |
339 | enum flow_block_command cmd) |
340 | { | |
d54725cd PNA |
341 | struct net_device *dev; |
342 | struct nft_hook *hook; | |
671312e1 | 343 | int err, i = 0; |
d54725cd PNA |
344 | |
345 | list_for_each_entry(hook, &basechain->hook_list, list) { | |
346 | dev = hook->ops.dev; | |
347 | if (this_dev && this_dev != dev) | |
348 | continue; | |
ead3952e | 349 | |
6df5490f | 350 | err = nft_chain_offload_cmd(basechain, dev, cmd); |
671312e1 PNA |
351 | if (err < 0 && cmd == FLOW_BLOCK_BIND) { |
352 | if (!this_dev) | |
353 | goto err_flow_block; | |
354 | ||
d54725cd | 355 | return err; |
671312e1 PNA |
356 | } |
357 | i++; | |
d54725cd PNA |
358 | } |
359 | ||
360 | return 0; | |
671312e1 PNA |
361 | |
362 | err_flow_block: | |
363 | list_for_each_entry(hook, &basechain->hook_list, list) { | |
364 | if (i-- <= 0) | |
365 | break; | |
366 | ||
367 | dev = hook->ops.dev; | |
368 | nft_chain_offload_cmd(basechain, dev, FLOW_BLOCK_UNBIND); | |
369 | } | |
370 | return err; | |
ead3952e PNA |
371 | } |
372 | ||
d54725cd | 373 | static int nft_flow_offload_chain(struct nft_chain *chain, u8 *ppolicy, |
c9626a2c PNA |
374 | enum flow_block_command cmd) |
375 | { | |
c9626a2c | 376 | struct nft_base_chain *basechain; |
8fc618c5 | 377 | u8 policy; |
c9626a2c PNA |
378 | |
379 | if (!nft_is_base_chain(chain)) | |
380 | return -EOPNOTSUPP; | |
381 | ||
382 | basechain = nft_base_chain(chain); | |
8fc618c5 | 383 | policy = ppolicy ? *ppolicy : basechain->policy; |
384 | ||
c9626a2c | 385 | /* Only default policy to accept is supported for now. */ |
ff175d0b | 386 | if (cmd == FLOW_BLOCK_BIND && policy == NF_DROP) |
c9626a2c PNA |
387 | return -EOPNOTSUPP; |
388 | ||
d54725cd | 389 | return nft_flow_block_chain(basechain, NULL, cmd); |
c9626a2c PNA |
390 | } |
391 | ||
392 | int nft_flow_rule_offload_commit(struct net *net) | |
393 | { | |
394 | struct nft_trans *trans; | |
395 | int err = 0; | |
8fc618c5 | 396 | u8 policy; |
c9626a2c PNA |
397 | |
398 | list_for_each_entry(trans, &net->nft.commit_list, list) { | |
399 | if (trans->ctx.family != NFPROTO_NETDEV) | |
400 | continue; | |
401 | ||
402 | switch (trans->msg_type) { | |
403 | case NFT_MSG_NEWCHAIN: | |
88c74984 PNA |
404 | if (!(trans->ctx.chain->flags & NFT_CHAIN_HW_OFFLOAD) || |
405 | nft_trans_chain_update(trans)) | |
c9626a2c PNA |
406 | continue; |
407 | ||
8fc618c5 | 408 | policy = nft_trans_chain_policy(trans); |
409 | err = nft_flow_offload_chain(trans->ctx.chain, &policy, | |
410 | FLOW_BLOCK_BIND); | |
c9626a2c PNA |
411 | break; |
412 | case NFT_MSG_DELCHAIN: | |
413 | if (!(trans->ctx.chain->flags & NFT_CHAIN_HW_OFFLOAD)) | |
414 | continue; | |
415 | ||
8fc618c5 | 416 | policy = nft_trans_chain_policy(trans); |
417 | err = nft_flow_offload_chain(trans->ctx.chain, &policy, | |
085461c8 | 418 | FLOW_BLOCK_UNBIND); |
c9626a2c PNA |
419 | break; |
420 | case NFT_MSG_NEWRULE: | |
421 | if (!(trans->ctx.chain->flags & NFT_CHAIN_HW_OFFLOAD)) | |
422 | continue; | |
423 | ||
424 | if (trans->ctx.flags & NLM_F_REPLACE || | |
23403cd8 PNA |
425 | !(trans->ctx.flags & NLM_F_APPEND)) { |
426 | err = -EOPNOTSUPP; | |
427 | break; | |
428 | } | |
e211aab7 | 429 | err = nft_flow_offload_rule(trans->ctx.chain, |
430 | nft_trans_rule(trans), | |
431 | nft_trans_flow_rule(trans), | |
432 | FLOW_CLS_REPLACE); | |
c9626a2c PNA |
433 | break; |
434 | case NFT_MSG_DELRULE: | |
435 | if (!(trans->ctx.chain->flags & NFT_CHAIN_HW_OFFLOAD)) | |
436 | continue; | |
437 | ||
e211aab7 | 438 | err = nft_flow_offload_rule(trans->ctx.chain, |
439 | nft_trans_rule(trans), | |
6ca61c7a | 440 | NULL, FLOW_CLS_DESTROY); |
c9626a2c PNA |
441 | break; |
442 | } | |
443 | ||
444 | if (err) | |
23403cd8 PNA |
445 | break; |
446 | } | |
447 | ||
448 | list_for_each_entry(trans, &net->nft.commit_list, list) { | |
449 | if (trans->ctx.family != NFPROTO_NETDEV) | |
450 | continue; | |
451 | ||
452 | switch (trans->msg_type) { | |
453 | case NFT_MSG_NEWRULE: | |
454 | if (!(trans->ctx.chain->flags & NFT_CHAIN_HW_OFFLOAD)) | |
455 | continue; | |
456 | ||
457 | nft_flow_rule_destroy(nft_trans_flow_rule(trans)); | |
458 | break; | |
459 | default: | |
460 | break; | |
461 | } | |
c9626a2c PNA |
462 | } |
463 | ||
464 | return err; | |
465 | } | |
9a32669f | 466 | |
504882db | 467 | static struct nft_chain *__nft_offload_get_chain(struct net_device *dev) |
9a32669f | 468 | { |
504882db | 469 | struct nft_base_chain *basechain; |
9a32669f | 470 | struct net *net = dev_net(dev); |
d54725cd | 471 | struct nft_hook *hook, *found; |
9a32669f | 472 | const struct nft_table *table; |
504882db | 473 | struct nft_chain *chain; |
9a32669f | 474 | |
504882db | 475 | list_for_each_entry(table, &net->nft.tables, list) { |
9a32669f | 476 | if (table->family != NFPROTO_NETDEV) |
477 | continue; | |
478 | ||
504882db | 479 | list_for_each_entry(chain, &table->chains, list) { |
480 | if (!nft_is_base_chain(chain) || | |
481 | !(chain->flags & NFT_CHAIN_HW_OFFLOAD)) | |
482 | continue; | |
483 | ||
d54725cd | 484 | found = NULL; |
504882db | 485 | basechain = nft_base_chain(chain); |
d54725cd PNA |
486 | list_for_each_entry(hook, &basechain->hook_list, list) { |
487 | if (hook->ops.dev != dev) | |
488 | continue; | |
489 | ||
490 | found = hook; | |
491 | break; | |
492 | } | |
493 | if (!found) | |
504882db | 494 | continue; |
495 | ||
496 | return chain; | |
9a32669f | 497 | } |
498 | } | |
504882db | 499 | |
500 | return NULL; | |
501 | } | |
502 | ||
503 | static void nft_indr_block_cb(struct net_device *dev, | |
504 | flow_indr_block_bind_cb_t *cb, void *cb_priv, | |
505 | enum flow_block_command cmd) | |
506 | { | |
507 | struct net *net = dev_net(dev); | |
508 | struct nft_chain *chain; | |
509 | ||
510 | mutex_lock(&net->nft.commit_mutex); | |
511 | chain = __nft_offload_get_chain(dev); | |
512 | if (chain) { | |
513 | struct nft_base_chain *basechain; | |
514 | ||
515 | basechain = nft_base_chain(chain); | |
516 | nft_indr_block_ing_cmd(dev, basechain, cb, cb_priv, cmd); | |
517 | } | |
518 | mutex_unlock(&net->nft.commit_mutex); | |
9a32669f | 519 | } |
3474a2c6 | 520 | |
06d392cb | 521 | static int nft_offload_netdev_event(struct notifier_block *this, |
522 | unsigned long event, void *ptr) | |
523 | { | |
524 | struct net_device *dev = netdev_notifier_info_to_dev(ptr); | |
525 | struct net *net = dev_net(dev); | |
526 | struct nft_chain *chain; | |
527 | ||
528 | mutex_lock(&net->nft.commit_mutex); | |
529 | chain = __nft_offload_get_chain(dev); | |
530 | if (chain) | |
bbaef955 PNA |
531 | nft_flow_block_chain(nft_base_chain(chain), dev, |
532 | FLOW_BLOCK_UNBIND); | |
533 | ||
06d392cb | 534 | mutex_unlock(&net->nft.commit_mutex); |
535 | ||
536 | return NOTIFY_DONE; | |
537 | } | |
538 | ||
3474a2c6 PNA |
539 | static struct flow_indr_block_ing_entry block_ing_entry = { |
540 | .cb = nft_indr_block_cb, | |
541 | .list = LIST_HEAD_INIT(block_ing_entry.list), | |
542 | }; | |
543 | ||
06d392cb | 544 | static struct notifier_block nft_offload_netdev_notifier = { |
545 | .notifier_call = nft_offload_netdev_event, | |
546 | }; | |
547 | ||
548 | int nft_offload_init(void) | |
3474a2c6 | 549 | { |
06d392cb | 550 | int err; |
551 | ||
552 | err = register_netdevice_notifier(&nft_offload_netdev_notifier); | |
553 | if (err < 0) | |
554 | return err; | |
555 | ||
3474a2c6 | 556 | flow_indr_add_block_ing_cb(&block_ing_entry); |
06d392cb | 557 | |
558 | return 0; | |
3474a2c6 PNA |
559 | } |
560 | ||
561 | void nft_offload_exit(void) | |
562 | { | |
563 | flow_indr_del_block_ing_cb(&block_ing_entry); | |
06d392cb | 564 | unregister_netdevice_notifier(&nft_offload_netdev_notifier); |
3474a2c6 | 565 | } |