mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-08-26 02:39:48 +00:00
![Florian Westphal](/assets/img/avatar_default.png)
Multiple netfilter extensions store pointers to external data in their extension area struct. Examples: 1. Timeout policies 2. Connection tracking helpers. No references are taken for these. When a helper or timeout policy is removed, the conntrack table gets traversed and affected extensions are cleared. Conntrack entries not yet in the hashtable are referenced via a special list, the unconfirmed list. On removal of a policy or connection tracking helper, the unconfirmed list gets traversed an all entries are marked as dying, this prevents them from getting committed to the table at insertion time: core checks for dying bit, if set, the conntrack entry gets destroyed at confirm time. The disadvantage is that each new conntrack has to be added to the percpu unconfirmed list, and each insertion needs to remove it from this list. The list is only ever needed when a policy or helper is removed -- a rare occurrence. Add a generation ID count: Instead of adding to the list and then traversing that list on policy/helper removal, increment a counter that is stored in the extension area. For unconfirmed conntracks, the extension has the genid valid at ct allocation time. Removal of a helper/policy etc. increments the counter. At confirmation time, validate that ext->genid == global_id. If the stored number is not the same, do not allow the conntrack insertion, just like as if a confirmed-list traversal would have flagged the entry as dying. After insertion, the genid is no longer relevant (conntrack entries are now reachable via the conntrack table iterators and is set to 0. This allows removal of the percpu unconfirmed list. Signed-off-by: Florian Westphal <fw@strlen.de> Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
159 lines
4.4 KiB
C
159 lines
4.4 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/* Structure dynamic extension infrastructure
|
|
* Copyright (C) 2004 Rusty Russell IBM Corporation
|
|
* Copyright (C) 2007 Netfilter Core Team <coreteam@netfilter.org>
|
|
* Copyright (C) 2007 USAGI/WIDE Project <http://www.linux-ipv6.org>
|
|
*/
|
|
#include <linux/kernel.h>
|
|
#include <linux/kmemleak.h>
|
|
#include <linux/module.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/rcupdate.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/skbuff.h>
|
|
#include <net/netfilter/nf_conntrack_extend.h>
|
|
|
|
#include <net/netfilter/nf_conntrack_helper.h>
|
|
#include <net/netfilter/nf_conntrack_acct.h>
|
|
#include <net/netfilter/nf_conntrack_seqadj.h>
|
|
#include <net/netfilter/nf_conntrack_ecache.h>
|
|
#include <net/netfilter/nf_conntrack_zones.h>
|
|
#include <net/netfilter/nf_conntrack_timestamp.h>
|
|
#include <net/netfilter/nf_conntrack_timeout.h>
|
|
#include <net/netfilter/nf_conntrack_labels.h>
|
|
#include <net/netfilter/nf_conntrack_synproxy.h>
|
|
#include <net/netfilter/nf_conntrack_act_ct.h>
|
|
#include <net/netfilter/nf_nat.h>
|
|
|
|
#define NF_CT_EXT_PREALLOC 128u /* conntrack events are on by default */
|
|
|
|
atomic_t nf_conntrack_ext_genid __read_mostly = ATOMIC_INIT(1);
|
|
|
|
static const u8 nf_ct_ext_type_len[NF_CT_EXT_NUM] = {
|
|
[NF_CT_EXT_HELPER] = sizeof(struct nf_conn_help),
|
|
#if IS_ENABLED(CONFIG_NF_NAT)
|
|
[NF_CT_EXT_NAT] = sizeof(struct nf_conn_nat),
|
|
#endif
|
|
[NF_CT_EXT_SEQADJ] = sizeof(struct nf_conn_seqadj),
|
|
[NF_CT_EXT_ACCT] = sizeof(struct nf_conn_acct),
|
|
#ifdef CONFIG_NF_CONNTRACK_EVENTS
|
|
[NF_CT_EXT_ECACHE] = sizeof(struct nf_conntrack_ecache),
|
|
#endif
|
|
#ifdef CONFIG_NF_CONNTRACK_TIMESTAMP
|
|
[NF_CT_EXT_TSTAMP] = sizeof(struct nf_conn_acct),
|
|
#endif
|
|
#ifdef CONFIG_NF_CONNTRACK_TIMEOUT
|
|
[NF_CT_EXT_TIMEOUT] = sizeof(struct nf_conn_tstamp),
|
|
#endif
|
|
#ifdef CONFIG_NF_CONNTRACK_LABELS
|
|
[NF_CT_EXT_LABELS] = sizeof(struct nf_conn_labels),
|
|
#endif
|
|
#if IS_ENABLED(CONFIG_NETFILTER_SYNPROXY)
|
|
[NF_CT_EXT_SYNPROXY] = sizeof(struct nf_conn_synproxy),
|
|
#endif
|
|
#if IS_ENABLED(CONFIG_NET_ACT_CT)
|
|
[NF_CT_EXT_ACT_CT] = sizeof(struct nf_conn_act_ct_ext),
|
|
#endif
|
|
};
|
|
|
|
static __always_inline unsigned int total_extension_size(void)
|
|
{
|
|
/* remember to add new extensions below */
|
|
BUILD_BUG_ON(NF_CT_EXT_NUM > 10);
|
|
|
|
return sizeof(struct nf_ct_ext) +
|
|
sizeof(struct nf_conn_help)
|
|
#if IS_ENABLED(CONFIG_NF_NAT)
|
|
+ sizeof(struct nf_conn_nat)
|
|
#endif
|
|
+ sizeof(struct nf_conn_seqadj)
|
|
+ sizeof(struct nf_conn_acct)
|
|
#ifdef CONFIG_NF_CONNTRACK_EVENTS
|
|
+ sizeof(struct nf_conntrack_ecache)
|
|
#endif
|
|
#ifdef CONFIG_NF_CONNTRACK_TIMESTAMP
|
|
+ sizeof(struct nf_conn_tstamp)
|
|
#endif
|
|
#ifdef CONFIG_NF_CONNTRACK_TIMEOUT
|
|
+ sizeof(struct nf_conn_timeout)
|
|
#endif
|
|
#ifdef CONFIG_NF_CONNTRACK_LABELS
|
|
+ sizeof(struct nf_conn_labels)
|
|
#endif
|
|
#if IS_ENABLED(CONFIG_NETFILTER_SYNPROXY)
|
|
+ sizeof(struct nf_conn_synproxy)
|
|
#endif
|
|
#if IS_ENABLED(CONFIG_NET_ACT_CT)
|
|
+ sizeof(struct nf_conn_act_ct_ext)
|
|
#endif
|
|
;
|
|
}
|
|
|
|
void *nf_ct_ext_add(struct nf_conn *ct, enum nf_ct_ext_id id, gfp_t gfp)
|
|
{
|
|
unsigned int newlen, newoff, oldlen, alloc;
|
|
struct nf_ct_ext *new;
|
|
|
|
/* Conntrack must not be confirmed to avoid races on reallocation. */
|
|
WARN_ON(nf_ct_is_confirmed(ct));
|
|
|
|
/* struct nf_ct_ext uses u8 to store offsets/size */
|
|
BUILD_BUG_ON(total_extension_size() > 255u);
|
|
|
|
if (ct->ext) {
|
|
const struct nf_ct_ext *old = ct->ext;
|
|
|
|
if (__nf_ct_ext_exist(old, id))
|
|
return NULL;
|
|
oldlen = old->len;
|
|
} else {
|
|
oldlen = sizeof(*new);
|
|
}
|
|
|
|
newoff = ALIGN(oldlen, __alignof__(struct nf_ct_ext));
|
|
newlen = newoff + nf_ct_ext_type_len[id];
|
|
|
|
alloc = max(newlen, NF_CT_EXT_PREALLOC);
|
|
new = krealloc(ct->ext, alloc, gfp);
|
|
if (!new)
|
|
return NULL;
|
|
|
|
if (!ct->ext) {
|
|
memset(new->offset, 0, sizeof(new->offset));
|
|
new->gen_id = atomic_read(&nf_conntrack_ext_genid);
|
|
}
|
|
|
|
new->offset[id] = newoff;
|
|
new->len = newlen;
|
|
memset((void *)new + newoff, 0, newlen - newoff);
|
|
|
|
ct->ext = new;
|
|
return (void *)new + newoff;
|
|
}
|
|
EXPORT_SYMBOL(nf_ct_ext_add);
|
|
|
|
/* Use nf_ct_ext_find wrapper. This is only useful for unconfirmed entries. */
|
|
void *__nf_ct_ext_find(const struct nf_ct_ext *ext, u8 id)
|
|
{
|
|
unsigned int gen_id = atomic_read(&nf_conntrack_ext_genid);
|
|
unsigned int this_id = READ_ONCE(ext->gen_id);
|
|
|
|
if (!__nf_ct_ext_exist(ext, id))
|
|
return NULL;
|
|
|
|
if (this_id == 0 || ext->gen_id == gen_id)
|
|
return (void *)ext + ext->offset[id];
|
|
|
|
return NULL;
|
|
}
|
|
EXPORT_SYMBOL(__nf_ct_ext_find);
|
|
|
|
void nf_ct_ext_bump_genid(void)
|
|
{
|
|
unsigned int value = atomic_inc_return(&nf_conntrack_ext_genid);
|
|
|
|
if (value == UINT_MAX)
|
|
atomic_set(&nf_conntrack_ext_genid, 1);
|
|
|
|
msleep(HZ);
|
|
}
|