]>
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 | if (event != NETDEV_UNREGISTER) |
329 | return; | |
02c7b25e | 330 | |
d54725cd PNA |
331 | list_for_each_entry(hook, &basechain->hook_list, list) { |
332 | if (hook->ops.dev == dev) | |
333 | found = hook; | |
334 | ||
335 | n++; | |
02c7b25e | 336 | } |
d54725cd PNA |
337 | if (!found) |
338 | return; | |
339 | ||
340 | if (n > 1) { | |
341 | nf_unregister_net_hook(ctx->net, &found->ops); | |
342 | list_del_rcu(&found->list); | |
343 | kfree_rcu(found, rcu); | |
344 | return; | |
345 | } | |
346 | ||
347 | /* UNREGISTER events are also happening on netns exit. | |
348 | * | |
349 | * Although nf_tables core releases all tables/chains, only this event | |
350 | * handler provides guarantee that hook->ops.dev is still accessible, | |
351 | * so we cannot skip exiting net namespaces. | |
352 | */ | |
353 | __nft_release_basechain(ctx); | |
02c7b25e PNA |
354 | } |
355 | ||
356 | static int nf_tables_netdev_event(struct notifier_block *this, | |
357 | unsigned long event, void *ptr) | |
358 | { | |
359 | struct net_device *dev = netdev_notifier_info_to_dev(ptr); | |
0854db2a | 360 | struct nftables_pernet *nft_net; |
02c7b25e PNA |
361 | struct nft_table *table; |
362 | struct nft_chain *chain, *nr; | |
363 | struct nft_ctx ctx = { | |
364 | .net = dev_net(dev), | |
365 | }; | |
366 | ||
367 | if (event != NETDEV_UNREGISTER && | |
368 | event != NETDEV_CHANGENAME) | |
369 | return NOTIFY_DONE; | |
370 | ||
d59d2f82 | 371 | nft_net = nft_pernet(ctx.net); |
0854db2a FW |
372 | mutex_lock(&nft_net->commit_mutex); |
373 | list_for_each_entry(table, &nft_net->tables, list) { | |
02c7b25e PNA |
374 | if (table->family != NFPROTO_NETDEV) |
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 | ||
383 | ctx.chain = chain; | |
384 | nft_netdev_event(event, dev, &ctx); | |
385 | } | |
386 | } | |
0854db2a | 387 | mutex_unlock(&nft_net->commit_mutex); |
02c7b25e PNA |
388 | |
389 | return NOTIFY_DONE; | |
390 | } | |
391 | ||
392 | static struct notifier_block nf_tables_netdev_notifier = { | |
393 | .notifier_call = nf_tables_netdev_event, | |
394 | }; | |
395 | ||
396 | static int nft_chain_filter_netdev_init(void) | |
397 | { | |
398 | int err; | |
399 | ||
400 | nft_register_chain_type(&nft_chain_filter_netdev); | |
401 | ||
402 | err = register_netdevice_notifier(&nf_tables_netdev_notifier); | |
403 | if (err) | |
404 | goto err_register_netdevice_notifier; | |
405 | ||
406 | return 0; | |
407 | ||
408 | err_register_netdevice_notifier: | |
409 | nft_unregister_chain_type(&nft_chain_filter_netdev); | |
410 | ||
411 | return err; | |
412 | } | |
413 | ||
414 | static void nft_chain_filter_netdev_fini(void) | |
415 | { | |
416 | nft_unregister_chain_type(&nft_chain_filter_netdev); | |
417 | unregister_netdevice_notifier(&nf_tables_netdev_notifier); | |
418 | } | |
419 | #else | |
420 | static inline int nft_chain_filter_netdev_init(void) { return 0; } | |
421 | static inline void nft_chain_filter_netdev_fini(void) {} | |
422 | #endif /* CONFIG_NF_TABLES_NETDEV */ | |
423 | ||
424 | int __init nft_chain_filter_init(void) | |
425 | { | |
426 | int err; | |
427 | ||
428 | err = nft_chain_filter_netdev_init(); | |
429 | if (err < 0) | |
430 | return err; | |
431 | ||
432 | nft_chain_filter_ipv4_init(); | |
433 | nft_chain_filter_ipv6_init(); | |
434 | nft_chain_filter_arp_init(); | |
435 | nft_chain_filter_inet_init(); | |
436 | nft_chain_filter_bridge_init(); | |
437 | ||
438 | return 0; | |
439 | } | |
440 | ||
d209df3e | 441 | void nft_chain_filter_fini(void) |
02c7b25e PNA |
442 | { |
443 | nft_chain_filter_bridge_fini(); | |
444 | nft_chain_filter_inet_fini(); | |
445 | nft_chain_filter_arp_fini(); | |
446 | nft_chain_filter_ipv6_fini(); | |
447 | nft_chain_filter_ipv4_fini(); | |
448 | nft_chain_filter_netdev_fini(); | |
449 | } |