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); | |
21 | nft_set_pktinfo_ipv4(&pkt, skb); | |
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); | |
65 | nft_set_pktinfo_unspec(&pkt, skb); | |
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); | |
105 | nft_set_pktinfo_ipv6(&pkt, skb); | |
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: | |
152 | nft_set_pktinfo_ipv4(&pkt, skb); | |
153 | break; | |
154 | case NFPROTO_IPV6: | |
155 | nft_set_pktinfo_ipv6(&pkt, skb); | |
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 | ||
177 | if (nft_set_pktinfo_ipv4_ingress(&pkt, skb) < 0) | |
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 | ||
185 | if (nft_set_pktinfo_ipv6_ingress(&pkt, skb) < 0) | |
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): | |
241 | nft_set_pktinfo_ipv4_validate(&pkt, skb); | |
242 | break; | |
243 | case htons(ETH_P_IPV6): | |
244 | nft_set_pktinfo_ipv6_validate(&pkt, skb); | |
245 | break; | |
246 | default: | |
247 | nft_set_pktinfo_unspec(&pkt, skb); | |
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): | |
296 | nft_set_pktinfo_ipv4_validate(&pkt, skb); | |
297 | break; | |
298 | case htons(ETH_P_IPV6): | |
299 | nft_set_pktinfo_ipv6_validate(&pkt, skb); | |
300 | break; | |
301 | default: | |
302 | nft_set_pktinfo_unspec(&pkt, skb); | |
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, | |
313 | .hook_mask = (1 << NF_NETDEV_INGRESS), | |
314 | .hooks = { | |
315 | [NF_NETDEV_INGRESS] = nft_do_chain_netdev, | |
316 | }, | |
317 | }; | |
318 | ||
319 | static void nft_netdev_event(unsigned long event, struct net_device *dev, | |
320 | struct nft_ctx *ctx) | |
321 | { | |
322 | struct nft_base_chain *basechain = nft_base_chain(ctx->chain); | |
d54725cd PNA |
323 | struct nft_hook *hook, *found = NULL; |
324 | int n = 0; | |
02c7b25e | 325 | |
d54725cd PNA |
326 | if (event != NETDEV_UNREGISTER) |
327 | return; | |
02c7b25e | 328 | |
d54725cd PNA |
329 | list_for_each_entry(hook, &basechain->hook_list, list) { |
330 | if (hook->ops.dev == dev) | |
331 | found = hook; | |
332 | ||
333 | n++; | |
02c7b25e | 334 | } |
d54725cd PNA |
335 | if (!found) |
336 | return; | |
337 | ||
338 | if (n > 1) { | |
339 | nf_unregister_net_hook(ctx->net, &found->ops); | |
340 | list_del_rcu(&found->list); | |
341 | kfree_rcu(found, rcu); | |
342 | return; | |
343 | } | |
344 | ||
345 | /* UNREGISTER events are also happening on netns exit. | |
346 | * | |
347 | * Although nf_tables core releases all tables/chains, only this event | |
348 | * handler provides guarantee that hook->ops.dev is still accessible, | |
349 | * so we cannot skip exiting net namespaces. | |
350 | */ | |
351 | __nft_release_basechain(ctx); | |
02c7b25e PNA |
352 | } |
353 | ||
354 | static int nf_tables_netdev_event(struct notifier_block *this, | |
355 | unsigned long event, void *ptr) | |
356 | { | |
357 | struct net_device *dev = netdev_notifier_info_to_dev(ptr); | |
358 | struct nft_table *table; | |
359 | struct nft_chain *chain, *nr; | |
360 | struct nft_ctx ctx = { | |
361 | .net = dev_net(dev), | |
362 | }; | |
363 | ||
364 | if (event != NETDEV_UNREGISTER && | |
365 | event != NETDEV_CHANGENAME) | |
366 | return NOTIFY_DONE; | |
367 | ||
f102d66b | 368 | mutex_lock(&ctx.net->nft.commit_mutex); |
02c7b25e PNA |
369 | list_for_each_entry(table, &ctx.net->nft.tables, list) { |
370 | if (table->family != NFPROTO_NETDEV) | |
371 | continue; | |
372 | ||
373 | ctx.family = table->family; | |
374 | ctx.table = table; | |
375 | list_for_each_entry_safe(chain, nr, &table->chains, list) { | |
376 | if (!nft_is_base_chain(chain)) | |
377 | continue; | |
378 | ||
379 | ctx.chain = chain; | |
380 | nft_netdev_event(event, dev, &ctx); | |
381 | } | |
382 | } | |
f102d66b | 383 | mutex_unlock(&ctx.net->nft.commit_mutex); |
02c7b25e PNA |
384 | |
385 | return NOTIFY_DONE; | |
386 | } | |
387 | ||
388 | static struct notifier_block nf_tables_netdev_notifier = { | |
389 | .notifier_call = nf_tables_netdev_event, | |
390 | }; | |
391 | ||
392 | static int nft_chain_filter_netdev_init(void) | |
393 | { | |
394 | int err; | |
395 | ||
396 | nft_register_chain_type(&nft_chain_filter_netdev); | |
397 | ||
398 | err = register_netdevice_notifier(&nf_tables_netdev_notifier); | |
399 | if (err) | |
400 | goto err_register_netdevice_notifier; | |
401 | ||
402 | return 0; | |
403 | ||
404 | err_register_netdevice_notifier: | |
405 | nft_unregister_chain_type(&nft_chain_filter_netdev); | |
406 | ||
407 | return err; | |
408 | } | |
409 | ||
410 | static void nft_chain_filter_netdev_fini(void) | |
411 | { | |
412 | nft_unregister_chain_type(&nft_chain_filter_netdev); | |
413 | unregister_netdevice_notifier(&nf_tables_netdev_notifier); | |
414 | } | |
415 | #else | |
416 | static inline int nft_chain_filter_netdev_init(void) { return 0; } | |
417 | static inline void nft_chain_filter_netdev_fini(void) {} | |
418 | #endif /* CONFIG_NF_TABLES_NETDEV */ | |
419 | ||
420 | int __init nft_chain_filter_init(void) | |
421 | { | |
422 | int err; | |
423 | ||
424 | err = nft_chain_filter_netdev_init(); | |
425 | if (err < 0) | |
426 | return err; | |
427 | ||
428 | nft_chain_filter_ipv4_init(); | |
429 | nft_chain_filter_ipv6_init(); | |
430 | nft_chain_filter_arp_init(); | |
431 | nft_chain_filter_inet_init(); | |
432 | nft_chain_filter_bridge_init(); | |
433 | ||
434 | return 0; | |
435 | } | |
436 | ||
d209df3e | 437 | void nft_chain_filter_fini(void) |
02c7b25e PNA |
438 | { |
439 | nft_chain_filter_bridge_fini(); | |
440 | nft_chain_filter_inet_fini(); | |
441 | nft_chain_filter_arp_fini(); | |
442 | nft_chain_filter_ipv6_fini(); | |
443 | nft_chain_filter_ipv4_fini(); | |
444 | nft_chain_filter_netdev_fini(); | |
445 | } |