/*
* Copyright (c) 2007-2009 Patrick McHardy <kaber@trash.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* Development of this code funded by Astaro AG (http://www.astaro.com/)
*/
#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_rcu(&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_rcu(&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);
}
static void nft_ctx_init(struct nft_ctx *ctx,
const struct sk_buff *skb,
const struct nlmsghdr *nlh,
struct nft_af_info *afi,
struct nft_table *table,
struct nft_chain *chain,
const struct nlattr * const *nla)
{
ctx->net = sock_net(skb->sk);
ctx->afi = afi;
ctx->table = table;
ctx->chain = chain;
ctx->nla = nla;
ctx->portid = NETLINK_CB(skb).portid;
ctx->report = nlmsg_report(nlh);
ctx->seq = nlh->nlmsg_seq;
}
static struct nft_trans *nft_trans_alloc(struct nft_ctx *ctx, int msg_type,
u32 size)
{
struct nft_trans *trans;
trans = kzalloc(sizeof(struct nft_trans) + size, GFP_KERNEL);
if (trans == NULL)
return NULL;
trans->msg_type = msg_type;
trans->ctx = *ctx;
return trans;
}
static void nft_trans_destroy(struct nft_trans *trans)
{
list_del(&trans->list);
kfree(trans);
}
static void nf_tables_unregister_hooks(const struct nft_table *table,
const struct nft_chain *chain,
unsigned int hook_nops)
{
if (!(table->flags & NFT_TABLE_F_DORMANT) &&
chain->flags & NFT_BASE_CHAIN)
nf_unregister_hooks(nft_base_chain(chain)->ops, hook_nops);
}
/* Internal table flags */
#define NFT_TABLE_INACTIVE (1 << 15)
static int nft_trans_table_add(struct nft_ctx *ctx, int msg_type)
{
struct nft_trans *trans;
trans = nft_trans_alloc(ctx, msg_type, sizeof(struct nft_trans_table));
if (trans == NULL)
return -ENOMEM;
if (msg_type == NFT_MSG_NEWTABLE)
ctx->table->flags |= NFT_TABLE_INACTIVE;
list_add_tail(&trans->list, &ctx->net->nft.commit_list);
return 0;
}
static int nft_deltable(struct nft_ctx *ctx)
{
int err;
err = nft_trans_table_add(ctx, NFT_MSG_DELTABLE);
if (err < 0)
return err;
list_del_rcu(&ctx->table->list);
return err;
}
static int nft_trans_chain_add(struct nft_ctx *ctx, int msg_type)
{
struct nft_trans *trans;
trans = nft_trans_alloc(ctx, msg_type, sizeof(struct nft_trans_chain));
if (trans == NULL)
return -ENOMEM;
if (msg_type == NFT_MSG_NEWCHAIN)
ctx->chain->flags |= NFT_CHAIN_INACTIVE;
list_add_tail(&trans->list, &ctx->net->nft.commit_list);
return 0;
}
static int nft_delchain(struct nft_ctx *ctx)
{
int err;
err = nft_trans_chain_add(ctx, NFT_MSG_DELCHAIN);
if (err < 0)
return err;
ctx->table->use--;
list_del_rcu(&ctx->chain->list);
return err;
}
static inline bool
nft_rule_is_active(struct net *net, const struct nft_rule *rule)
{
return (rule->genmask & (1 << net->nft.gencursor)) == 0;
}
static inline int gencursor_next(struct net *net)
{
return net->nft.gencursor+1 == 1 ? 1 : 0;
}
static inline int
nft_rule_is_active_next(struct net *net, const struct nft_rule *rule)
{
return (rule->genmask & (1 << gencursor_next(net))) == 0;
}
static inline void
nft_rule_activate_next(struct net *net, struct nft_rule *rule)
{
/* Now inactive, will be active in the future */
rule->genmask = (1 << net->nft.gencursor);
}
static inline void
nft_rule_deactivate_next(struct net *net, struct nft_rule *rule)
{
rule->genmask = (1 << gencursor_next(net));
}
static inline void nft_rule_clear(struct net *net, struct nft_rule *rule)
{
rule->genmask = 0;
}
static int
nf_tables_delrule_deactivate(struct nft_ctx *ctx, struct nft_rule *rule)
{
/* You cannot delete the same rule twice */
if (nft_rule_is_active_next(ctx->net, rule)) {
nft_rule_deactivate_next(ctx->net, rule);
ctx->chain->use--;
return 0;
}
return -ENOENT;
}
static struct nft_trans *nft_trans_rule_add(struct nft_ctx *ctx, int msg_type,
struct nft_rule *rule)
{
struct nft_trans *trans;
trans = nft_trans_alloc(ctx, msg_type, sizeof(struct nft_trans_rule));
if (trans == NULL)
return NULL;
nft_trans_rule(trans) = rule;
list_add_tail(&trans->list, &ctx->net->nft.commit_list);
return trans;
}
static int nft_delrule(struct nft_ctx *ctx, struct nft_rule *rule)
{
struct nft_trans *trans;
int err;
trans = nft_trans_rule_add(ctx, NFT_MSG_DELRULE, rule);
if (trans == NULL)
return -ENOMEM;
err = nf_tables_delrule_deactivate(ctx, rule);
if (err < 0) {
nft_trans_destroy(trans);
return err;
}
return 0;
}
static int nft_delrule_by_chain(struct nft_ctx *ctx)
{
struct nft_rule *rule;
int err;
list_for_each_entry(rule, &ctx->chain->rules, list) {
err = nft_delrule(ctx, rule);
if (err < 0)
return err;
}
return 0;
}
/* Internal set flag */
#define NFT_SET_INACTIVE (1 << 15)
static int nft_trans_set_add(struct nft_ctx *ctx, int msg_type,
struct nft_set *set)
{
struct nft_trans *trans;
trans = nft_trans_alloc(ctx, msg_type, sizeof(struct nft_trans_set));
if (trans == NULL)
return -ENOMEM;
if (msg_type == NFT_MSG_NEWSET && ctx->nla[NFTA_SET_ID] != NULL) {
nft_trans_set_id(trans) =
ntohl(nla_get_be32(ctx->nla[NFTA_SET_ID]));
set->flags |= NFT_SET_INACTIVE;
}
nft_trans_set(trans) = set;
list_add_tail(&trans->list, &ctx->net->nft.commit_list);
return 0;
}
static int nft_delset(struct nft_ctx *ctx, struct nft_set *set)
{
int err;
err = nft_trans_set_add(ctx, NFT_MSG_DELSET, set);
if (err < 0)
return err;
list_del_rcu(&set->list);
ctx->table->use--;
return err;
}
/*
* 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);