![Florian Westphal](/assets/img/avatar_default.png)
Continue to use nftnl subsys mutex to protect (un)registration of hook types, expressions and so on, but force batch operations to do their own locking. This allows distinct net namespaces to perform transactions in parallel. Signed-off-by: Florian Westphal <fw@strlen.de> Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
404 lines
9.6 KiB
C
404 lines
9.6 KiB
C
#include <linux/init.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/netdevice.h>
|
|
#include <net/net_namespace.h>
|
|
#include <net/netfilter/nf_tables.h>
|
|
#include <linux/netfilter_ipv4.h>
|
|
#include <linux/netfilter_ipv6.h>
|
|
#include <linux/netfilter_bridge.h>
|
|
#include <linux/netfilter_arp.h>
|
|
#include <net/netfilter/nf_tables_ipv4.h>
|
|
#include <net/netfilter/nf_tables_ipv6.h>
|
|
|
|
#ifdef CONFIG_NF_TABLES_IPV4
|
|
static unsigned int nft_do_chain_ipv4(void *priv,
|
|
struct sk_buff *skb,
|
|
const struct nf_hook_state *state)
|
|
{
|
|
struct nft_pktinfo pkt;
|
|
|
|
nft_set_pktinfo(&pkt, skb, state);
|
|
nft_set_pktinfo_ipv4(&pkt, skb);
|
|
|
|
return nft_do_chain(&pkt, priv);
|
|
}
|
|
|
|
static const struct nft_chain_type nft_chain_filter_ipv4 = {
|
|
.name = "filter",
|
|
.type = NFT_CHAIN_T_DEFAULT,
|
|
.family = NFPROTO_IPV4,
|
|
.hook_mask = (1 << NF_INET_LOCAL_IN) |
|
|
(1 << NF_INET_LOCAL_OUT) |
|
|
(1 << NF_INET_FORWARD) |
|
|
(1 << NF_INET_PRE_ROUTING) |
|
|
(1 << NF_INET_POST_ROUTING),
|
|
.hooks = {
|
|
[NF_INET_LOCAL_IN] = nft_do_chain_ipv4,
|
|
[NF_INET_LOCAL_OUT] = nft_do_chain_ipv4,
|
|
[NF_INET_FORWARD] = nft_do_chain_ipv4,
|
|
[NF_INET_PRE_ROUTING] = nft_do_chain_ipv4,
|
|
[NF_INET_POST_ROUTING] = nft_do_chain_ipv4,
|
|
},
|
|
};
|
|
|
|
static void nft_chain_filter_ipv4_init(void)
|
|
{
|
|
nft_register_chain_type(&nft_chain_filter_ipv4);
|
|
}
|
|
static void nft_chain_filter_ipv4_fini(void)
|
|
{
|
|
nft_unregister_chain_type(&nft_chain_filter_ipv4);
|
|
}
|
|
|
|
#else
|
|
static inline void nft_chain_filter_ipv4_init(void) {}
|
|
static inline void nft_chain_filter_ipv4_fini(void) {}
|
|
#endif /* CONFIG_NF_TABLES_IPV4 */
|
|
|
|
#ifdef CONFIG_NF_TABLES_ARP
|
|
static unsigned int nft_do_chain_arp(void *priv, struct sk_buff *skb,
|
|
const struct nf_hook_state *state)
|
|
{
|
|
struct nft_pktinfo pkt;
|
|
|
|
nft_set_pktinfo(&pkt, skb, state);
|
|
nft_set_pktinfo_unspec(&pkt, skb);
|
|
|
|
return nft_do_chain(&pkt, priv);
|
|
}
|
|
|
|
static const struct nft_chain_type nft_chain_filter_arp = {
|
|
.name = "filter",
|
|
.type = NFT_CHAIN_T_DEFAULT,
|
|
.family = NFPROTO_ARP,
|
|
.owner = THIS_MODULE,
|
|
.hook_mask = (1 << NF_ARP_IN) |
|
|
(1 << NF_ARP_OUT),
|
|
.hooks = {
|
|
[NF_ARP_IN] = nft_do_chain_arp,
|
|
[NF_ARP_OUT] = nft_do_chain_arp,
|
|
},
|
|
};
|
|
|
|
static void nft_chain_filter_arp_init(void)
|
|
{
|
|
nft_register_chain_type(&nft_chain_filter_arp);
|
|
}
|
|
|
|
static void nft_chain_filter_arp_fini(void)
|
|
{
|
|
nft_unregister_chain_type(&nft_chain_filter_arp);
|
|
}
|
|
#else
|
|
static inline void nft_chain_filter_arp_init(void) {}
|
|
static inline void nft_chain_filter_arp_fini(void) {}
|
|
#endif /* CONFIG_NF_TABLES_ARP */
|
|
|
|
#ifdef CONFIG_NF_TABLES_IPV6
|
|
static unsigned int nft_do_chain_ipv6(void *priv,
|
|
struct sk_buff *skb,
|
|
const struct nf_hook_state *state)
|
|
{
|
|
struct nft_pktinfo pkt;
|
|
|
|
nft_set_pktinfo(&pkt, skb, state);
|
|
nft_set_pktinfo_ipv6(&pkt, skb);
|
|
|
|
return nft_do_chain(&pkt, priv);
|
|
}
|
|
|
|
static const struct nft_chain_type nft_chain_filter_ipv6 = {
|
|
.name = "filter",
|
|
.type = NFT_CHAIN_T_DEFAULT,
|
|
.family = NFPROTO_IPV6,
|
|
.hook_mask = (1 << NF_INET_LOCAL_IN) |
|
|
(1 << NF_INET_LOCAL_OUT) |
|
|
(1 << NF_INET_FORWARD) |
|
|
(1 << NF_INET_PRE_ROUTING) |
|
|
(1 << NF_INET_POST_ROUTING),
|
|
.hooks = {
|
|
[NF_INET_LOCAL_IN] = nft_do_chain_ipv6,
|
|
[NF_INET_LOCAL_OUT] = nft_do_chain_ipv6,
|
|
[NF_INET_FORWARD] = nft_do_chain_ipv6,
|
|
[NF_INET_PRE_ROUTING] = nft_do_chain_ipv6,
|
|
[NF_INET_POST_ROUTING] = nft_do_chain_ipv6,
|
|
},
|
|
};
|
|
|
|
static void nft_chain_filter_ipv6_init(void)
|
|
{
|
|
nft_register_chain_type(&nft_chain_filter_ipv6);
|
|
}
|
|
|
|
static void nft_chain_filter_ipv6_fini(void)
|
|
{
|
|
nft_unregister_chain_type(&nft_chain_filter_ipv6);
|
|
}
|
|
#else
|
|
static inline void nft_chain_filter_ipv6_init(void) {}
|
|
static inline void nft_chain_filter_ipv6_fini(void) {}
|
|
#endif /* CONFIG_NF_TABLES_IPV6 */
|
|
|
|
#ifdef CONFIG_NF_TABLES_INET
|
|
static unsigned int nft_do_chain_inet(void *priv, struct sk_buff *skb,
|
|
const struct nf_hook_state *state)
|
|
{
|
|
struct nft_pktinfo pkt;
|
|
|
|
nft_set_pktinfo(&pkt, skb, state);
|
|
|
|
switch (state->pf) {
|
|
case NFPROTO_IPV4:
|
|
nft_set_pktinfo_ipv4(&pkt, skb);
|
|
break;
|
|
case NFPROTO_IPV6:
|
|
nft_set_pktinfo_ipv6(&pkt, skb);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return nft_do_chain(&pkt, priv);
|
|
}
|
|
|
|
static const struct nft_chain_type nft_chain_filter_inet = {
|
|
.name = "filter",
|
|
.type = NFT_CHAIN_T_DEFAULT,
|
|
.family = NFPROTO_INET,
|
|
.hook_mask = (1 << NF_INET_LOCAL_IN) |
|
|
(1 << NF_INET_LOCAL_OUT) |
|
|
(1 << NF_INET_FORWARD) |
|
|
(1 << NF_INET_PRE_ROUTING) |
|
|
(1 << NF_INET_POST_ROUTING),
|
|
.hooks = {
|
|
[NF_INET_LOCAL_IN] = nft_do_chain_inet,
|
|
[NF_INET_LOCAL_OUT] = nft_do_chain_inet,
|
|
[NF_INET_FORWARD] = nft_do_chain_inet,
|
|
[NF_INET_PRE_ROUTING] = nft_do_chain_inet,
|
|
[NF_INET_POST_ROUTING] = nft_do_chain_inet,
|
|
},
|
|
};
|
|
|
|
static void nft_chain_filter_inet_init(void)
|
|
{
|
|
nft_register_chain_type(&nft_chain_filter_inet);
|
|
}
|
|
|
|
static void nft_chain_filter_inet_fini(void)
|
|
{
|
|
nft_unregister_chain_type(&nft_chain_filter_inet);
|
|
}
|
|
#else
|
|
static inline void nft_chain_filter_inet_init(void) {}
|
|
static inline void nft_chain_filter_inet_fini(void) {}
|
|
#endif /* CONFIG_NF_TABLES_IPV6 */
|
|
|
|
#ifdef CONFIG_NF_TABLES_BRIDGE
|
|
static unsigned int
|
|
nft_do_chain_bridge(void *priv,
|
|
struct sk_buff *skb,
|
|
const struct nf_hook_state *state)
|
|
{
|
|
struct nft_pktinfo pkt;
|
|
|
|
nft_set_pktinfo(&pkt, skb, state);
|
|
|
|
switch (eth_hdr(skb)->h_proto) {
|
|
case htons(ETH_P_IP):
|
|
nft_set_pktinfo_ipv4_validate(&pkt, skb);
|
|
break;
|
|
case htons(ETH_P_IPV6):
|
|
nft_set_pktinfo_ipv6_validate(&pkt, skb);
|
|
break;
|
|
default:
|
|
nft_set_pktinfo_unspec(&pkt, skb);
|
|
break;
|
|
}
|
|
|
|
return nft_do_chain(&pkt, priv);
|
|
}
|
|
|
|
static const struct nft_chain_type nft_chain_filter_bridge = {
|
|
.name = "filter",
|
|
.type = NFT_CHAIN_T_DEFAULT,
|
|
.family = NFPROTO_BRIDGE,
|
|
.hook_mask = (1 << NF_BR_PRE_ROUTING) |
|
|
(1 << NF_BR_LOCAL_IN) |
|
|
(1 << NF_BR_FORWARD) |
|
|
(1 << NF_BR_LOCAL_OUT) |
|
|
(1 << NF_BR_POST_ROUTING),
|
|
.hooks = {
|
|
[NF_BR_PRE_ROUTING] = nft_do_chain_bridge,
|
|
[NF_BR_LOCAL_IN] = nft_do_chain_bridge,
|
|
[NF_BR_FORWARD] = nft_do_chain_bridge,
|
|
[NF_BR_LOCAL_OUT] = nft_do_chain_bridge,
|
|
[NF_BR_POST_ROUTING] = nft_do_chain_bridge,
|
|
},
|
|
};
|
|
|
|
static void nft_chain_filter_bridge_init(void)
|
|
{
|
|
nft_register_chain_type(&nft_chain_filter_bridge);
|
|
}
|
|
|
|
static void nft_chain_filter_bridge_fini(void)
|
|
{
|
|
nft_unregister_chain_type(&nft_chain_filter_bridge);
|
|
}
|
|
#else
|
|
static inline void nft_chain_filter_bridge_init(void) {}
|
|
static inline void nft_chain_filter_bridge_fini(void) {}
|
|
#endif /* CONFIG_NF_TABLES_BRIDGE */
|
|
|
|
#ifdef CONFIG_NF_TABLES_NETDEV
|
|
static unsigned int nft_do_chain_netdev(void *priv, struct sk_buff *skb,
|
|
const struct nf_hook_state *state)
|
|
{
|
|
struct nft_pktinfo pkt;
|
|
|
|
nft_set_pktinfo(&pkt, skb, state);
|
|
|
|
switch (skb->protocol) {
|
|
case htons(ETH_P_IP):
|
|
nft_set_pktinfo_ipv4_validate(&pkt, skb);
|
|
break;
|
|
case htons(ETH_P_IPV6):
|
|
nft_set_pktinfo_ipv6_validate(&pkt, skb);
|
|
break;
|
|
default:
|
|
nft_set_pktinfo_unspec(&pkt, skb);
|
|
break;
|
|
}
|
|
|
|
return nft_do_chain(&pkt, priv);
|
|
}
|
|
|
|
static const struct nft_chain_type nft_chain_filter_netdev = {
|
|
.name = "filter",
|
|
.type = NFT_CHAIN_T_DEFAULT,
|
|
.family = NFPROTO_NETDEV,
|
|
.hook_mask = (1 << NF_NETDEV_INGRESS),
|
|
.hooks = {
|
|
[NF_NETDEV_INGRESS] = nft_do_chain_netdev,
|
|
},
|
|
};
|
|
|
|
static void nft_netdev_event(unsigned long event, struct net_device *dev,
|
|
struct nft_ctx *ctx)
|
|
{
|
|
struct nft_base_chain *basechain = nft_base_chain(ctx->chain);
|
|
|
|
switch (event) {
|
|
case NETDEV_UNREGISTER:
|
|
if (strcmp(basechain->dev_name, dev->name) != 0)
|
|
return;
|
|
|
|
__nft_release_basechain(ctx);
|
|
break;
|
|
case NETDEV_CHANGENAME:
|
|
if (dev->ifindex != basechain->ops.dev->ifindex)
|
|
return;
|
|
|
|
strncpy(basechain->dev_name, dev->name, IFNAMSIZ);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static int nf_tables_netdev_event(struct notifier_block *this,
|
|
unsigned long event, void *ptr)
|
|
{
|
|
struct net_device *dev = netdev_notifier_info_to_dev(ptr);
|
|
struct nft_table *table;
|
|
struct nft_chain *chain, *nr;
|
|
struct nft_ctx ctx = {
|
|
.net = dev_net(dev),
|
|
};
|
|
|
|
if (event != NETDEV_UNREGISTER &&
|
|
event != NETDEV_CHANGENAME)
|
|
return NOTIFY_DONE;
|
|
|
|
ctx.net = maybe_get_net(ctx.net);
|
|
if (!ctx.net)
|
|
return NOTIFY_DONE;
|
|
|
|
mutex_lock(&ctx.net->nft.commit_mutex);
|
|
list_for_each_entry(table, &ctx.net->nft.tables, list) {
|
|
if (table->family != NFPROTO_NETDEV)
|
|
continue;
|
|
|
|
ctx.family = table->family;
|
|
ctx.table = table;
|
|
list_for_each_entry_safe(chain, nr, &table->chains, list) {
|
|
if (!nft_is_base_chain(chain))
|
|
continue;
|
|
|
|
ctx.chain = chain;
|
|
nft_netdev_event(event, dev, &ctx);
|
|
}
|
|
}
|
|
mutex_unlock(&ctx.net->nft.commit_mutex);
|
|
put_net(ctx.net);
|
|
|
|
return NOTIFY_DONE;
|
|
}
|
|
|
|
static struct notifier_block nf_tables_netdev_notifier = {
|
|
.notifier_call = nf_tables_netdev_event,
|
|
};
|
|
|
|
static int nft_chain_filter_netdev_init(void)
|
|
{
|
|
int err;
|
|
|
|
nft_register_chain_type(&nft_chain_filter_netdev);
|
|
|
|
err = register_netdevice_notifier(&nf_tables_netdev_notifier);
|
|
if (err)
|
|
goto err_register_netdevice_notifier;
|
|
|
|
return 0;
|
|
|
|
err_register_netdevice_notifier:
|
|
nft_unregister_chain_type(&nft_chain_filter_netdev);
|
|
|
|
return err;
|
|
}
|
|
|
|
static void nft_chain_filter_netdev_fini(void)
|
|
{
|
|
nft_unregister_chain_type(&nft_chain_filter_netdev);
|
|
unregister_netdevice_notifier(&nf_tables_netdev_notifier);
|
|
}
|
|
#else
|
|
static inline int nft_chain_filter_netdev_init(void) { return 0; }
|
|
static inline void nft_chain_filter_netdev_fini(void) {}
|
|
#endif /* CONFIG_NF_TABLES_NETDEV */
|
|
|
|
int __init nft_chain_filter_init(void)
|
|
{
|
|
int err;
|
|
|
|
err = nft_chain_filter_netdev_init();
|
|
if (err < 0)
|
|
return err;
|
|
|
|
nft_chain_filter_ipv4_init();
|
|
nft_chain_filter_ipv6_init();
|
|
nft_chain_filter_arp_init();
|
|
nft_chain_filter_inet_init();
|
|
nft_chain_filter_bridge_init();
|
|
|
|
return 0;
|
|
}
|
|
|
|
void __exit nft_chain_filter_fini(void)
|
|
{
|
|
nft_chain_filter_bridge_fini();
|
|
nft_chain_filter_inet_fini();
|
|
nft_chain_filter_arp_fini();
|
|
nft_chain_filter_ipv6_fini();
|
|
nft_chain_filter_ipv4_fini();
|
|
nft_chain_filter_netdev_fini();
|
|
}
|