#include <linux/module.h>
#include <linux/init.h>
#include <linux/list.h>
#include <linux/skbuff.h>
#include <linux/netlink.h>
#include <linux/netfilter.h>
#include <linux/netfilter/nfnetlink.h>
#include <linux/netfilter/nf_tables.h>
#include <net/netfilter/nf_tables_core.h>
#include <net/netfilter/nf_tables.h>
#include <net/net_namespace.h>
#include <net/sock.h>
static LIST_HEAD(nf_tables_expressions);
/**
* nft_register_afinfo - register nf_tables address family info
*
* @afi: address family info to register
*
* Register the address family for use with nf_tables. Returns zero on
* success or a negative errno code otherwise.
*/
int nft_register_afinfo(struct net *net, struct nft_af_info *afi)
{
INIT_LIST_HEAD(&afi->tables);
nfnl_lock(NFNL_SUBSYS_NFTABLES);
list_add_tail(&afi->list, &net->nft.af_info);
nfnl_unlock(NFNL_SUBSYS_NFTABLES);
return 0;
}
EXPORT_SYMBOL_GPL(nft_register_afinfo);
/**
* nft_unregister_afinfo - unregister nf_tables address family info
*
* @afi: address family info to unregister
*
* Unregister the address family for use with nf_tables.
*/
void nft_unregister_afinfo(struct nft_af_info *afi)
{
nfnl_lock(NFNL_SUBSYS_NFTABLES);
list_del(&afi->list);
nfnl_unlock(NFNL_SUBSYS_NFTABLES);
}
EXPORT_SYMBOL_GPL(nft_unregister_afinfo);
static struct nft_af_info *nft_afinfo_lookup(struct net *net, int family)
{
struct nft_af_info *afi;
list_for_each_entry(afi, &net->nft.af_info, list) {
if (afi->family == family)
return afi;
}
return NULL;
}
static struct nft_af_info *
nf_tables_afinfo_lookup(struct net *net, int family, bool autoload)
{
struct nft_af_info *afi;
afi = nft_afinfo_lookup(net, family);
if (afi != NULL)
return afi;
#ifdef CONFIG_MODULES
if (autoload) {
nfnl_unlock(NFNL_SUBSYS_NFTABLES);
request_module("nft-afinfo-%u", family);
nfnl_lock(NFNL_SUBSYS_NFTABLES);
afi = nft_afinfo_lookup(net, family);
if (afi != NULL)
return ERR_PTR(-EAGAIN);
}
#endif
return ERR_PTR(-EAFNOSUPPORT);
}
/*
* Tables
*/
static struct nft_table *nft_table_lookup(const struct nft_af_info *afi,
const struct nlattr *nla)
{
struct nft_table *table;
list_for_each_entry(table, &afi->tables, list) {
if (!nla_strcmp(nla, table->name))
return table;
}
return NULL;
}
static struct nft_table *nf_tables_table_lookup(const struct nft_af_info *afi,
const struct nlattr *nla)
{
struct nft_table *table;
if (nla == NULL)
return ERR_PTR(-EINVAL);
table = nft_table_lookup(afi, nla);
if (table != NULL)
return table;
return ERR_PTR(-ENOENT);
}
static inline u64 nf_tables_alloc_handle(struct nft_table *table)
{
return ++table->hgenerator;
}
static struct nf_chain_type *chain_type[AF_MAX][NFT_CHAIN_T_MAX];
static int __nf_tables_chain_type_lookup(int family, const struct nlattr *nla)
{
int i;
for (i=0; i<NFT_CHAIN_T_MAX; i++) {
if (chain_type[family][i] != NULL &&
!nla_strcmp(nla, chain_type[family][i]->name))
return i;
}
return -1;
}
static int nf_tables_chain_type_lookup(const struct nft_af_info *afi,
const struct nlattr *nla,
bool autoload)
{
int type;
type = __nf_tables_chain_type_lookup(afi->family, nla);
#ifdef CONFIG_MODULES
if (type < 0 && autoload) {
nfnl_unlock(NFNL_SUBSYS_NFTABLES);
request_module("nft-chain-%u-%*.s", afi->family,
nla_len(nla)-1, (const char *)nla_data(nla));
nfnl_lock(NFNL_SUBSYS_NFTABLES);
type = __nf_tables_chain_type_lookup(afi->family, nla);
}
#endif
return type;
}
static const struct nla_policy nft_table_policy[NFTA_TABLE_MAX + 1] = {
[NFTA_TABLE_NAME] = { .type = NLA_STRING },
[NFTA_TABLE_FLAGS] = { .type = NLA_U32 },
};
static int nf_tables_fill_table_info(struct sk_buff *skb, u32 portid, u32 seq,
int event, u32 flags, int family,
const struct nft_table *table)
{
struct nlmsghdr *nlh;
struct nfgenmsg *nfmsg;
event |= NFNL_SUBSYS_NFTABLES << 8;
nlh = nlmsg_put(skb, portid, seq, event, sizeof(struct nfgenmsg), flags);
if (nlh == NULL)
goto nla_put_failure;
nfmsg = nlmsg_data(nlh);
nfmsg->nfgen_family = family;
nfmsg->version = NFNETLINK_V0;
nfmsg->res_id = 0;
if (nla_put_string(skb, NFTA_TABLE_NAME, table->name) ||
nla_put_be32(skb, NFTA_TABLE_FLAGS, htonl(table->flags)))
goto nla_put_failure;
return nlmsg_end(skb, nlh);
nla_put_failure:
nlmsg_trim(skb, nlh);
return -1;
}
static int nf_tables_table_notify(const struct sk_buff *oskb,
const struct nlmsghdr *nlh,
const struct nft_table *table,
int event, int family)
{
struct sk_buff *skb;
u32 portid = oskb ? NETLINK_CB(oskb).portid : 0;
u32 seq = nlh ? nlh->nlmsg_seq : 0;
struct net *net = oskb ? sock_net(oskb->sk) : &init_net;
bool report;
int err;
report = nlh ? nlmsg_report(nlh) : false;
if (!report && !nfnetlink_has_listeners(net, NFNLGRP_NFTABLES))
return 0;
err = -ENOBUFS;
skb = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
if (skb == NULL)
goto err;
err = nf_tables_fill_table_info(skb, portid, seq, event, 0,
family, table);
if (err < 0) {
kfree_skb(skb);
goto err;
}
err = nfnetlink_send(skb, net, portid, NFNLGRP_NFTABLES, report,
GFP_KERNEL);
err:
if (err < 0)
nfnetlink_set_err(net, portid, NFNLGRP_NFTABLES, err);
return err;
}
static int nf_tables_dump_tables(struct sk_buff *skb,
struct netlink_callback *cb)
{
const struct nfgenmsg *nfmsg = nlmsg_data(cb->nlh);
const struct nft_af_info *afi;
const struct nft_table *table;
unsigned int idx = 0, s_idx = cb->args[0];
struct net *net = sock_net(skb->sk);
int family = nfmsg->nfgen_family;
list_for_each_entry(afi, &net->nft.af_info, list) {
if (family != NFPROTO_UNSPEC && family != afi->family)
continue;
list_for_each_entry(table, &afi->tables, list) {
if (idx < s_idx)
goto cont;
if (idx > s_idx)
memset(&cb->args[1], 0,
sizeof(cb->args) - sizeof(cb->args[0]));
if (nf_tables_fill_table_info(skb,
NETLINK_CB(cb->skb).portid,
cb->nlh->nlmsg_seq,
NFT_MSG_NEWTABLE,
NLM_F_MULTI,
afi->family, table) < 0)
goto done;
cont:
idx++;
}
}
done:
cb->args[0] = idx;
return skb->len;
}
static int nf_tables_gettable(struct sock *nlsk, struct sk_buff *skb,
const struct nlmsghdr *nlh,
const struct nlattr * const nla[])
{
const struct nfgenmsg *nfmsg = nlmsg_data(nlh);
const struct nft_af_info *afi;
const struct nft_table *table;
struct sk_buff *skb2;
struct net *net = sock_net(skb->sk);
int family = nfmsg->nfgen_family;
int err;
if (nlh->nlmsg_flags & NLM_F_DUMP) {
struct netlink_dump_control c = {
.dump = nf_tables_dump_tables,
};
return netlink_dump_start(nlsk, skb, nlh, &c);
}
afi = nf_tables_afinfo_lookup(net, family, false);
if (IS_ERR(afi))
return PTR_ERR(afi);
table = nf_tables_table_lookup(afi, nla[NFTA_TABLE_NAME]);
if (IS_ERR(table))
return PTR_ERR(table);
skb2 = alloc_skb(NLMSG_GOODSIZE, GFP_KERNEL);
if (!skb2)
return -ENOMEM;
err = nf_tables_fill_table_info(skb2, NETLINK_CB(skb).portid,
nlh->nlmsg_seq, NFT_MSG_NEWTABLE, 0,
family, table);
if (err < 0)
goto err;
return nlmsg_unicast(nlsk, skb2, NETLINK_CB(skb).portid);
err:
kfree_skb(skb2);
return err;
}
static int nf_tables_table_enable(struct nft_table *table)
{
struct nft_chain *chain;
int err, i = 0;
list_for_each_entry(chain, &table->chains, list) {
if (!(chain->flags & NFT_BASE_CHAIN))
continue;
err = nf_register_hook(&nft_base_chain(chain)->ops);
if (err < 0)
goto err;
i++;
}
return 0;
err:
list_for_each_entry(chain, &table->chains, list) {
if (!(chain->flags & NFT_BASE_CHAIN))
continue;
if (i-- <= 0)
break;
nf_unregister_hook(&nft_base_chain(chain)->ops);
}
return err;
}
static int nf_tables_table_disable(struct nft_table *table)
{
struct nft_chain *chain;
list_for_each_entry(chain, &table->chains, list) {
if (chain->flags & NFT_BASE_CHAIN)
nf_unregister_hook(&nft_base_chain(chain)->ops);
}