#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);
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);
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);
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);
break;
case NFPROTO_IPV6:
nft_set_pktinfo_ipv6(&pkt);
break;
default:
break;
}
return nft_do_chain(&pkt, priv);
}
static unsigned int nft_do_chain_inet_ingress(void *priv, struct sk_buff *skb,
const struct nf_hook_state *state)
{
struct nf_hook_state ingress_state = *state;
struct nft_pktinfo pkt;
switch (skb->protocol) {
case htons(ETH_P_IP):
/* Original hook is NFPROTO_NETDEV and NF_NETDEV_INGRESS. */
ingress_state.pf = NFPROTO_IPV4;
ingress_state.hook = NF_INET_INGRESS;
nft_set_pktinfo(&pkt, skb, &ingress_state);
if (nft_set_pktinfo_ipv4_ingress(&pkt) < 0)
return NF_DROP;
break;
case htons(ETH_P_IPV6):
ingress_state.pf = NFPROTO_IPV6;
ingress_state.hook = NF_INET_INGRESS;
nft_set_pktinfo(&pkt, skb, &ingress_state);
if (nft_set_pktinfo_ipv6_ingress(&pkt) < 0)
return NF_DROP;
break;
default:
return NF_ACCEPT;
}
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_INGRESS) |
(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_INGRESS] = nft_do_chain_inet_ingress,
[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 */
#if IS_ENABLED(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);
break;
case htons(ETH_P_IPV6):
nft_set_pktinfo_ipv6_validate(&pkt);
break;
default:
nft_set_pktinfo_unspec(&pkt);
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);
break;
case htons(ETH_P_IPV6):
nft_set_pktinfo_ipv6_validate(&pkt);
break;
default:
nft_set_pktinfo_unspec(&pkt);
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) |
(1 << NF_NETDEV_EGRESS),
.hooks = {
[NF_NETDEV_INGRESS] = nft_do_chain_netdev,
[NF_NETDEV_EGRESS] = 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);
struct nft_hook *hook, *found = NULL;
int n = 0;
list_for_each_entry(hook, &basechain->hook_list, list) {
if (hook->ops.dev == dev)
found = hook;
n++;
}
if (!found)
return;
if (n > 1) {
if (!(ctx->chain->table->flags & NFT_TABLE_F_DORMANT))
nf_unregister_net_hook(ctx->net, &found->ops);
list_del_rcu(&found->list);
kfree_rcu(found, rcu);
return;
}
/* UNREGISTER events are also happening on netns exit.
*
* Although nf_tables core releases all tables/chains, only this event
* handler provides guarantee that hook->ops.dev is still accessible,
* so we cannot skip exiting net namespaces.
*/
__nft_release_basechain(ctx);
}
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_base_chain *basechain;
struct nftables_pernet *nft_net;
struct nft_chain *chain, *nr;
struct nft_table *table;
struct nft_ctx ctx = {
.net = dev_net(dev),
};
if (event != NETDEV_UNREGISTER)
return NOTIFY_DONE;
nft_net = nft_pernet(ctx.net);
mutex_lock(&nft_net->commit_mutex);
list_for_each_entry(table, &nft_net->tables, list) {
if (table->family != NFPROTO_NETDEV &&
table->family != NFPROTO_INET)
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;
basechain = nft_base_chain(chain);
if (table->family == NFPROTO_INET &&
basechain->ops.hooknum != NF_INET_INGRESS)
continue;
ctx.chain = chain;
nft_netdev_event(event, dev, &ctx);
}
}
mutex_unlock(&nft_net->commit_mutex);
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 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();
}