Commit | Line | Data |
---|---|---|
02c7b25e PNA |
1 | #include <linux/init.h> |
2 | #include <linux/kernel.h> | |
3 | #include <linux/netdevice.h> | |
4 | #include <net/net_namespace.h> | |
5 | #include <net/netfilter/nf_tables.h> | |
6 | #include <linux/netfilter_ipv4.h> | |
7 | #include <linux/netfilter_ipv6.h> | |
8 | #include <linux/netfilter_bridge.h> | |
9 | #include <linux/netfilter_arp.h> | |
10 | #include <net/netfilter/nf_tables_ipv4.h> | |
11 | #include <net/netfilter/nf_tables_ipv6.h> | |
12 | ||
13 | #ifdef CONFIG_NF_TABLES_IPV4 | |
14 | static unsigned int nft_do_chain_ipv4(void *priv, | |
15 | struct sk_buff *skb, | |
16 | const struct nf_hook_state *state) | |
17 | { | |
18 | struct nft_pktinfo pkt; | |
19 | ||
20 | nft_set_pktinfo(&pkt, skb, state); | |
f06ad944 | 21 | nft_set_pktinfo_ipv4(&pkt); |
02c7b25e PNA |
22 | |
23 | return nft_do_chain(&pkt, priv); | |
24 | } | |
25 | ||
26 | static const struct nft_chain_type nft_chain_filter_ipv4 = { | |
27 | .name = "filter", | |
28 | .type = NFT_CHAIN_T_DEFAULT, | |
29 | .family = NFPROTO_IPV4, | |
30 | .hook_mask = (1 << NF_INET_LOCAL_IN) | | |
31 | (1 << NF_INET_LOCAL_OUT) | | |
32 | (1 << NF_INET_FORWARD) | | |
33 | (1 << NF_INET_PRE_ROUTING) | | |
34 | (1 << NF_INET_POST_ROUTING), | |
35 | .hooks = { | |
36 | [NF_INET_LOCAL_IN] = nft_do_chain_ipv4, | |
37 | [NF_INET_LOCAL_OUT] = nft_do_chain_ipv4, | |
38 | [NF_INET_FORWARD] = nft_do_chain_ipv4, | |
39 | [NF_INET_PRE_ROUTING] = nft_do_chain_ipv4, | |
40 | [NF_INET_POST_ROUTING] = nft_do_chain_ipv4, | |
41 | }, | |
42 | }; | |
43 | ||
44 | static void nft_chain_filter_ipv4_init(void) | |
45 | { | |
46 | nft_register_chain_type(&nft_chain_filter_ipv4); | |
47 | } | |
48 | static void nft_chain_filter_ipv4_fini(void) | |
49 | { | |
50 | nft_unregister_chain_type(&nft_chain_filter_ipv4); | |
51 | } | |
52 | ||
53 | #else | |
54 | static inline void nft_chain_filter_ipv4_init(void) {} | |
55 | static inline void nft_chain_filter_ipv4_fini(void) {} | |
56 | #endif /* CONFIG_NF_TABLES_IPV4 */ | |
57 | ||
58 | #ifdef CONFIG_NF_TABLES_ARP | |
59 | static unsigned int nft_do_chain_arp(void *priv, struct sk_buff *skb, | |
60 | const struct nf_hook_state *state) | |
61 | { | |
62 | struct nft_pktinfo pkt; | |
63 | ||
64 | nft_set_pktinfo(&pkt, skb, state); | |
f06ad944 | 65 | nft_set_pktinfo_unspec(&pkt); |
02c7b25e PNA |
66 | |
67 | return nft_do_chain(&pkt, priv); | |
68 | } | |
69 | ||
70 | static const struct nft_chain_type nft_chain_filter_arp = { | |
71 | .name = "filter", | |
72 | .type = NFT_CHAIN_T_DEFAULT, | |
73 | .family = NFPROTO_ARP, | |
74 | .owner = THIS_MODULE, | |
75 | .hook_mask = (1 << NF_ARP_IN) | | |
76 | (1 << NF_ARP_OUT), | |
77 | .hooks = { | |
78 | [NF_ARP_IN] = nft_do_chain_arp, | |
79 | [NF_ARP_OUT] = nft_do_chain_arp, | |
80 | }, | |
81 | }; | |
82 | ||
83 | static void nft_chain_filter_arp_init(void) | |
84 | { | |
85 | nft_register_chain_type(&nft_chain_filter_arp); | |
86 | } | |
87 | ||
88 | static void nft_chain_filter_arp_fini(void) | |
89 | { | |
90 | nft_unregister_chain_type(&nft_chain_filter_arp); | |
91 | } | |
92 | #else | |
93 | static inline void nft_chain_filter_arp_init(void) {} | |
94 | static inline void nft_chain_filter_arp_fini(void) {} | |
95 | #endif /* CONFIG_NF_TABLES_ARP */ | |
96 | ||
97 | #ifdef CONFIG_NF_TABLES_IPV6 | |
98 | static unsigned int nft_do_chain_ipv6(void *priv, | |
99 | struct sk_buff *skb, | |
100 | const struct nf_hook_state *state) | |
101 | { | |
102 | struct nft_pktinfo pkt; | |
103 | ||
104 | nft_set_pktinfo(&pkt, skb, state); | |
f06ad944 | 105 | nft_set_pktinfo_ipv6(&pkt); |
02c7b25e PNA |
106 | |
107 | return nft_do_chain(&pkt, priv); | |
108 | } | |
109 | ||
110 | static const struct nft_chain_type nft_chain_filter_ipv6 = { | |
111 | .name = "filter", | |
112 | .type = NFT_CHAIN_T_DEFAULT, | |
113 | .family = NFPROTO_IPV6, | |
114 | .hook_mask = (1 << NF_INET_LOCAL_IN) | | |
115 | (1 << NF_INET_LOCAL_OUT) | | |
116 | (1 << NF_INET_FORWARD) | | |
117 | (1 << NF_INET_PRE_ROUTING) | | |
118 | (1 << NF_INET_POST_ROUTING), | |
119 | .hooks = { | |
120 | [NF_INET_LOCAL_IN] = nft_do_chain_ipv6, | |
121 | [NF_INET_LOCAL_OUT] = nft_do_chain_ipv6, | |
122 | [NF_INET_FORWARD] = nft_do_chain_ipv6, | |
123 | [NF_INET_PRE_ROUTING] = nft_do_chain_ipv6, | |
124 | [NF_INET_POST_ROUTING] = nft_do_chain_ipv6, | |
125 | }, | |
126 | }; | |
127 | ||
128 | static void nft_chain_filter_ipv6_init(void) | |
129 | { | |
130 | nft_register_chain_type(&nft_chain_filter_ipv6); | |
131 | } | |
132 | ||
133 | static void nft_chain_filter_ipv6_fini(void) | |
134 | { | |
135 | nft_unregister_chain_type(&nft_chain_filter_ipv6); | |
136 | } | |
137 | #else | |
138 | static inline void nft_chain_filter_ipv6_init(void) {} | |
139 | static inline void nft_chain_filter_ipv6_fini(void) {} | |
140 | #endif /* CONFIG_NF_TABLES_IPV6 */ | |
141 | ||
142 | #ifdef CONFIG_NF_TABLES_INET | |
143 | static unsigned int nft_do_chain_inet(void *priv, struct sk_buff *skb, | |
144 | const struct nf_hook_state *state) | |
145 | { | |
146 | struct nft_pktinfo pkt; | |
147 | ||
148 | nft_set_pktinfo(&pkt, skb, state); | |
149 | ||
150 | switch (state->pf) { | |
151 | case NFPROTO_IPV4: | |
f06ad944 | 152 | nft_set_pktinfo_ipv4(&pkt); |
02c7b25e PNA |
153 | break; |
154 | case NFPROTO_IPV6: | |
f06ad944 | 155 | nft_set_pktinfo_ipv6(&pkt); |
02c7b25e PNA |
156 | break; |
157 | default: | |
158 | break; | |
159 | } | |
160 | ||
161 | return nft_do_chain(&pkt, priv); | |
162 | } | |
163 | ||
d3519cb8 PNA |
164 | static unsigned int nft_do_chain_inet_ingress(void *priv, struct sk_buff *skb, |
165 | const struct nf_hook_state *state) | |
166 | { | |
167 | struct nf_hook_state ingress_state = *state; | |
168 | struct nft_pktinfo pkt; | |
169 | ||
170 | switch (skb->protocol) { | |
171 | case htons(ETH_P_IP): | |
172 | /* Original hook is NFPROTO_NETDEV and NF_NETDEV_INGRESS. */ | |
173 | ingress_state.pf = NFPROTO_IPV4; | |
174 | ingress_state.hook = NF_INET_INGRESS; | |
175 | nft_set_pktinfo(&pkt, skb, &ingress_state); | |
176 | ||
f06ad944 | 177 | if (nft_set_pktinfo_ipv4_ingress(&pkt) < 0) |
d3519cb8 PNA |
178 | return NF_DROP; |
179 | break; | |
180 | case htons(ETH_P_IPV6): | |
181 | ingress_state.pf = NFPROTO_IPV6; | |
182 | ingress_state.hook = NF_INET_INGRESS; | |
183 | nft_set_pktinfo(&pkt, skb, &ingress_state); | |
184 | ||
f06ad944 | 185 | if (nft_set_pktinfo_ipv6_ingress(&pkt) < 0) |
d3519cb8 PNA |
186 | return NF_DROP; |
187 | break; | |
188 | default: | |
189 | return NF_ACCEPT; | |
190 | } | |
191 | ||
192 | return nft_do_chain(&pkt, priv); | |
193 | } | |
194 | ||
02c7b25e PNA |
195 | static const struct nft_chain_type nft_chain_filter_inet = { |
196 | .name = "filter", | |
197 | .type = NFT_CHAIN_T_DEFAULT, | |
198 | .family = NFPROTO_INET, | |
d3519cb8 PNA |
199 | .hook_mask = (1 << NF_INET_INGRESS) | |
200 | (1 << NF_INET_LOCAL_IN) | | |
02c7b25e PNA |
201 | (1 << NF_INET_LOCAL_OUT) | |
202 | (1 << NF_INET_FORWARD) | | |
203 | (1 << NF_INET_PRE_ROUTING) | | |
204 | (1 << NF_INET_POST_ROUTING), | |
205 | .hooks = { | |
d3519cb8 | 206 | [NF_INET_INGRESS] = nft_do_chain_inet_ingress, |
02c7b25e PNA |
207 | [NF_INET_LOCAL_IN] = nft_do_chain_inet, |
208 | [NF_INET_LOCAL_OUT] = nft_do_chain_inet, | |
209 | [NF_INET_FORWARD] = nft_do_chain_inet, | |
210 | [NF_INET_PRE_ROUTING] = nft_do_chain_inet, | |
211 | [NF_INET_POST_ROUTING] = nft_do_chain_inet, | |
212 | }, | |
213 | }; | |
214 | ||
215 | static void nft_chain_filter_inet_init(void) | |
216 | { | |
217 | nft_register_chain_type(&nft_chain_filter_inet); | |
218 | } | |
219 | ||
220 | static void nft_chain_filter_inet_fini(void) | |
221 | { | |
222 | nft_unregister_chain_type(&nft_chain_filter_inet); | |
223 | } | |
224 | #else | |
225 | static inline void nft_chain_filter_inet_init(void) {} | |
226 | static inline void nft_chain_filter_inet_fini(void) {} | |
227 | #endif /* CONFIG_NF_TABLES_IPV6 */ | |
228 | ||
dfee0e99 | 229 | #if IS_ENABLED(CONFIG_NF_TABLES_BRIDGE) |
02c7b25e PNA |
230 | static unsigned int |
231 | nft_do_chain_bridge(void *priv, | |
232 | struct sk_buff *skb, | |
233 | const struct nf_hook_state *state) | |
234 | { | |
235 | struct nft_pktinfo pkt; | |
236 | ||
237 | nft_set_pktinfo(&pkt, skb, state); | |
238 | ||
239 | switch (eth_hdr(skb)->h_proto) { | |
240 | case htons(ETH_P_IP): | |
f06ad944 | 241 | nft_set_pktinfo_ipv4_validate(&pkt); |
02c7b25e PNA |
242 | break; |
243 | case htons(ETH_P_IPV6): | |
f06ad944 | 244 | nft_set_pktinfo_ipv6_validate(&pkt); |
02c7b25e PNA |
245 | break; |
246 | default: | |
f06ad944 | 247 | nft_set_pktinfo_unspec(&pkt); |
02c7b25e PNA |
248 | break; |
249 | } | |
250 | ||
251 | return nft_do_chain(&pkt, priv); | |
252 | } | |
253 | ||
254 | static const struct nft_chain_type nft_chain_filter_bridge = { | |
255 | .name = "filter", | |
256 | .type = NFT_CHAIN_T_DEFAULT, | |
257 | .family = NFPROTO_BRIDGE, | |
258 | .hook_mask = (1 << NF_BR_PRE_ROUTING) | | |
259 | (1 << NF_BR_LOCAL_IN) | | |
260 | (1 << NF_BR_FORWARD) | | |
261 | (1 << NF_BR_LOCAL_OUT) | | |
262 | (1 << NF_BR_POST_ROUTING), | |
263 | .hooks = { | |
264 | [NF_BR_PRE_ROUTING] = nft_do_chain_bridge, | |
265 | [NF_BR_LOCAL_IN] = nft_do_chain_bridge, | |
266 | [NF_BR_FORWARD] = nft_do_chain_bridge, | |
267 | [NF_BR_LOCAL_OUT] = nft_do_chain_bridge, | |
268 | [NF_BR_POST_ROUTING] = nft_do_chain_bridge, | |
269 | }, | |
270 | }; | |
271 | ||
272 | static void nft_chain_filter_bridge_init(void) | |
273 | { | |
274 | nft_register_chain_type(&nft_chain_filter_bridge); | |
275 | } | |
276 | ||
277 | static void nft_chain_filter_bridge_fini(void) | |
278 | { | |
279 | nft_unregister_chain_type(&nft_chain_filter_bridge); | |
280 | } | |
281 | #else | |
282 | static inline void nft_chain_filter_bridge_init(void) {} | |
283 | static inline void nft_chain_filter_bridge_fini(void) {} | |
284 | #endif /* CONFIG_NF_TABLES_BRIDGE */ | |
285 | ||
286 | #ifdef CONFIG_NF_TABLES_NETDEV | |
287 | static unsigned int nft_do_chain_netdev(void *priv, struct sk_buff *skb, | |
288 | const struct nf_hook_state *state) | |
289 | { | |
290 | struct nft_pktinfo pkt; | |
291 | ||
292 | nft_set_pktinfo(&pkt, skb, state); | |
293 | ||
294 | switch (skb->protocol) { | |
295 | case htons(ETH_P_IP): | |
f06ad944 | 296 | nft_set_pktinfo_ipv4_validate(&pkt); |
02c7b25e PNA |
297 | break; |
298 | case htons(ETH_P_IPV6): | |
f06ad944 | 299 | nft_set_pktinfo_ipv6_validate(&pkt); |
02c7b25e PNA |
300 | break; |
301 | default: | |
f06ad944 | 302 | nft_set_pktinfo_unspec(&pkt); |
02c7b25e PNA |
303 | break; |
304 | } | |
305 | ||
306 | return nft_do_chain(&pkt, priv); | |
307 | } | |
308 | ||
309 | static const struct nft_chain_type nft_chain_filter_netdev = { | |
310 | .name = "filter", | |
311 | .type = NFT_CHAIN_T_DEFAULT, | |
312 | .family = NFPROTO_NETDEV, | |
42df6e1d LW |
313 | .hook_mask = (1 << NF_NETDEV_INGRESS) | |
314 | (1 << NF_NETDEV_EGRESS), | |
02c7b25e PNA |
315 | .hooks = { |
316 | [NF_NETDEV_INGRESS] = nft_do_chain_netdev, | |
42df6e1d | 317 | [NF_NETDEV_EGRESS] = nft_do_chain_netdev, |
02c7b25e PNA |
318 | }, |
319 | }; | |
320 | ||
321 | static void nft_netdev_event(unsigned long event, struct net_device *dev, | |
322 | struct nft_ctx *ctx) | |
323 | { | |
324 | struct nft_base_chain *basechain = nft_base_chain(ctx->chain); | |
d54725cd PNA |
325 | struct nft_hook *hook, *found = NULL; |
326 | int n = 0; | |
02c7b25e | 327 | |
d54725cd PNA |
328 | list_for_each_entry(hook, &basechain->hook_list, list) { |
329 | if (hook->ops.dev == dev) | |
330 | found = hook; | |
331 | ||
332 | n++; | |
02c7b25e | 333 | } |
d54725cd PNA |
334 | if (!found) |
335 | return; | |
336 | ||
337 | if (n > 1) { | |
8e30abc9 PNA |
338 | if (!(ctx->chain->table->flags & NFT_TABLE_F_DORMANT)) |
339 | nf_unregister_net_hook(ctx->net, &found->ops); | |
340 | ||
d54725cd PNA |
341 | list_del_rcu(&found->list); |
342 | kfree_rcu(found, rcu); | |
343 | return; | |
344 | } | |
345 | ||
dc1c9fd4 FW |
346 | /* UNREGISTER events are also happening on netns exit. |
347 | * | |
348 | * Although nf_tables core releases all tables/chains, only this event | |
349 | * handler provides guarantee that hook->ops.dev is still accessible, | |
350 | * so we cannot skip exiting net namespaces. | |
351 | */ | |
d54725cd | 352 | __nft_release_basechain(ctx); |
02c7b25e PNA |
353 | } |
354 | ||
355 | static int nf_tables_netdev_event(struct notifier_block *this, | |
356 | unsigned long event, void *ptr) | |
357 | { | |
358 | struct net_device *dev = netdev_notifier_info_to_dev(ptr); | |
01acb2e8 | 359 | struct nft_base_chain *basechain; |
0854db2a | 360 | struct nftables_pernet *nft_net; |
02c7b25e | 361 | struct nft_chain *chain, *nr; |
01acb2e8 | 362 | struct nft_table *table; |
02c7b25e PNA |
363 | struct nft_ctx ctx = { |
364 | .net = dev_net(dev), | |
365 | }; | |
366 | ||
6e20eef4 | 367 | if (event != NETDEV_UNREGISTER) |
02c7b25e PNA |
368 | return NOTIFY_DONE; |
369 | ||
d59d2f82 | 370 | nft_net = nft_pernet(ctx.net); |
0854db2a FW |
371 | mutex_lock(&nft_net->commit_mutex); |
372 | list_for_each_entry(table, &nft_net->tables, list) { | |
01acb2e8 PNA |
373 | if (table->family != NFPROTO_NETDEV && |
374 | table->family != NFPROTO_INET) | |
02c7b25e PNA |
375 | continue; |
376 | ||
377 | ctx.family = table->family; | |
378 | ctx.table = table; | |
379 | list_for_each_entry_safe(chain, nr, &table->chains, list) { | |
380 | if (!nft_is_base_chain(chain)) | |
381 | continue; | |
382 | ||
01acb2e8 PNA |
383 | basechain = nft_base_chain(chain); |
384 | if (table->family == NFPROTO_INET && | |
385 | basechain->ops.hooknum != NF_INET_INGRESS) | |
386 | continue; | |
387 | ||
02c7b25e PNA |
388 | ctx.chain = chain; |
389 | nft_netdev_event(event, dev, &ctx); | |
390 | } | |
391 | } | |
0854db2a | 392 | mutex_unlock(&nft_net->commit_mutex); |
02c7b25e PNA |
393 | |
394 | return NOTIFY_DONE; | |
395 | } | |
396 | ||
397 | static struct notifier_block nf_tables_netdev_notifier = { | |
398 | .notifier_call = nf_tables_netdev_event, | |
399 | }; | |
400 | ||
401 | static int nft_chain_filter_netdev_init(void) | |
402 | { | |
403 | int err; | |
404 | ||
405 | nft_register_chain_type(&nft_chain_filter_netdev); | |
406 | ||
407 | err = register_netdevice_notifier(&nf_tables_netdev_notifier); | |
408 | if (err) | |
409 | goto err_register_netdevice_notifier; | |
410 | ||
411 | return 0; | |
412 | ||
413 | err_register_netdevice_notifier: | |
414 | nft_unregister_chain_type(&nft_chain_filter_netdev); | |
415 | ||
416 | return err; | |
417 | } | |
418 | ||
419 | static void nft_chain_filter_netdev_fini(void) | |
420 | { | |
421 | nft_unregister_chain_type(&nft_chain_filter_netdev); | |
422 | unregister_netdevice_notifier(&nf_tables_netdev_notifier); | |
423 | } | |
424 | #else | |
425 | static inline int nft_chain_filter_netdev_init(void) { return 0; } | |
426 | static inline void nft_chain_filter_netdev_fini(void) {} | |
427 | #endif /* CONFIG_NF_TABLES_NETDEV */ | |
428 | ||
429 | int __init nft_chain_filter_init(void) | |
430 | { | |
431 | int err; | |
432 | ||
433 | err = nft_chain_filter_netdev_init(); | |
434 | if (err < 0) | |
435 | return err; | |
436 | ||
437 | nft_chain_filter_ipv4_init(); | |
438 | nft_chain_filter_ipv6_init(); | |
439 | nft_chain_filter_arp_init(); | |
440 | nft_chain_filter_inet_init(); | |
441 | nft_chain_filter_bridge_init(); | |
442 | ||
443 | return 0; | |
444 | } | |
445 | ||
d209df3e | 446 | void nft_chain_filter_fini(void) |
02c7b25e PNA |
447 | { |
448 | nft_chain_filter_bridge_fini(); | |
449 | nft_chain_filter_inet_fini(); | |
450 | nft_chain_filter_arp_fini(); | |
451 | nft_chain_filter_ipv6_fini(); | |
452 | nft_chain_filter_ipv4_fini(); | |
453 | nft_chain_filter_netdev_fini(); | |
454 | } |