linux/net/netfilter/ipset/ip_set_hash_gen.h

/* SPDX-License-Identifier: GPL-2.0-only */
/* Copyright (C) 2013 Jozsef Kadlecsik <[email protected]> */

#ifndef _IP_SET_HASH_GEN_H
#define _IP_SET_HASH_GEN_H

#include <linux/rcupdate.h>
#include <linux/rcupdate_wait.h>
#include <linux/jhash.h>
#include <linux/types.h>
#include <linux/netfilter/nfnetlink.h>
#include <linux/netfilter/ipset/ip_set.h>

#define __ipset_dereference(p)
#define ipset_dereference_nfnl(p)
#define ipset_dereference_set(p, set)
#define ipset_dereference_bh_nfnl(p)

/* Hashing which uses arrays to resolve clashing. The hash table is resized
 * (doubled) when searching becomes too long.
 * Internally jhash is used with the assumption that the size of the
 * stored data is a multiple of sizeof(u32).
 *
 * Readers and resizing
 *
 * Resizing can be triggered by userspace command only, and those
 * are serialized by the nfnl mutex. During resizing the set is
 * read-locked, so the only possible concurrent operations are
 * the kernel side readers. Those must be protected by proper RCU locking.
 */

/* Number of elements to store in an initial array block */
#define AHASH_INIT_SIZE
/* Max number of elements to store in an array block */
#define AHASH_MAX_SIZE
/* Max muber of elements in the array block when tuned */
#define AHASH_MAX_TUNED
#define AHASH_MAX(h)

/* A hash bucket */
struct hbucket {};

/* Region size for locking == 2^HTABLE_REGION_BITS */
#define HTABLE_REGION_BITS
#define ahash_numof_locks(htable_bits)
#define ahash_sizeof_regions(htable_bits)
#define ahash_region(n, htable_bits)
#define ahash_bucket_start(h,  htable_bits)
#define ahash_bucket_end(h,  htable_bits)

struct htable_gc {};

/* The hash table: the table size stored here in order to make resizing easy */
struct htable {};

#define hbucket(h, i)
#define ext_size(n, dsize)

#ifndef IPSET_NET_COUNT
#define IPSET_NET_COUNT
#endif

/* Book-keeping of the prefixes added to the set */
struct net_prefixes {};

/* Compute the hash table size */
static size_t
htable_size(u8 hbits)
{}

#ifdef IP_SET_HASH_WITH_NETS
#if IPSET_NET_COUNT > 1
#define __CIDR
#else
#define __CIDR
#endif

/* cidr + 1 is stored in net_prefixes to support /0 */
#define NCIDR_PUT
#define NCIDR_GET

#ifdef IP_SET_HASH_WITH_NETS_PACKED
/* When cidr is packed with nomatch, cidr - 1 is stored in the data entry */
#define DCIDR_PUT
#define DCIDR_GET
#else
#define DCIDR_PUT
#define DCIDR_GET
#endif

#define INIT_CIDR

#ifdef IP_SET_HASH_WITH_NET0
/* cidr from 0 to HOST_MASK value and c = cidr + 1 */
#define NLEN
#define CIDR_POS
#else
/* cidr from 1 to HOST_MASK value and c = cidr + 1 */
#define NLEN
#define CIDR_POS
#endif

#else
#define NLEN
#endif /* IP_SET_HASH_WITH_NETS */

#define SET_ELEM_EXPIRED(set, d)

#if defined(IP_SET_HASH_WITH_NETMASK) || defined(IP_SET_HASH_WITH_BITMASK)
static const union nf_inet_addr onesmask =;

static const union nf_inet_addr zeromask =;
#endif

#endif /* _IP_SET_HASH_GEN_H */

#ifndef MTYPE
#error "MTYPE is not defined!"
#endif

#ifndef HTYPE
#error "HTYPE is not defined!"
#endif

#ifndef HOST_MASK
#error "HOST_MASK is not defined!"
#endif

/* Family dependent templates */

#undef ahash_data
#undef mtype_data_equal
#undef mtype_do_data_match
#undef mtype_data_set_flags
#undef mtype_data_reset_elem
#undef mtype_data_reset_flags
#undef mtype_data_netmask
#undef mtype_data_list
#undef mtype_data_next
#undef mtype_elem

#undef mtype_ahash_destroy
#undef mtype_ext_cleanup
#undef mtype_add_cidr
#undef mtype_del_cidr
#undef mtype_ahash_memsize
#undef mtype_flush
#undef mtype_destroy
#undef mtype_same_set
#undef mtype_kadt
#undef mtype_uadt

#undef mtype_add
#undef mtype_del
#undef mtype_test_cidrs
#undef mtype_test
#undef mtype_uref
#undef mtype_resize
#undef mtype_ext_size
#undef mtype_resize_ad
#undef mtype_head
#undef mtype_list
#undef mtype_gc_do
#undef mtype_gc
#undef mtype_gc_init
#undef mtype_cancel_gc
#undef mtype_variant
#undef mtype_data_match

#undef htype
#undef HKEY

#define mtype_data_equal
#ifdef IP_SET_HASH_WITH_NETS
#define mtype_do_data_match
#else
#define mtype_do_data_match(d)
#endif
#define mtype_data_set_flags
#define mtype_data_reset_elem
#define mtype_data_reset_flags
#define mtype_data_netmask
#define mtype_data_list
#define mtype_data_next
#define mtype_elem

#define mtype_ahash_destroy
#define mtype_ext_cleanup
#define mtype_add_cidr
#define mtype_del_cidr
#define mtype_ahash_memsize
#define mtype_flush
#define mtype_destroy
#define mtype_same_set
#define mtype_kadt
#define mtype_uadt

#define mtype_add
#define mtype_del
#define mtype_test_cidrs
#define mtype_test
#define mtype_uref
#define mtype_resize
#define mtype_ext_size
#define mtype_resize_ad
#define mtype_head
#define mtype_list
#define mtype_gc_do
#define mtype_gc
#define mtype_gc_init
#define mtype_cancel_gc
#define mtype_variant
#define mtype_data_match

#ifndef HKEY_DATALEN
#define HKEY_DATALEN
#endif

#define htype

#define HKEY(data, initval, htable_bits)

/* The generic hash structure */
struct htype {};

/* ADD|DEL entries saved during resize */
struct mtype_resize_ad {};

#ifdef IP_SET_HASH_WITH_NETS
/* Network cidr size book keeping when the hash stores different
 * sized networks. cidr == real cidr + 1 to support /0.
 */
static void
mtype_add_cidr(struct ip_set *set, struct htype *h, u8 cidr, u8 n)
{
	int i, j;

	spin_lock_bh(&set->lock);
	/* Add in increasing prefix order, so larger cidr first */
	for (i = 0, j = -1; i < NLEN && h->nets[i].cidr[n]; i++) {
		if (j != -1) {
			continue;
		} else if (h->nets[i].cidr[n] < cidr) {
			j = i;
		} else if (h->nets[i].cidr[n] == cidr) {
			h->nets[CIDR_POS(cidr)].nets[n]++;
			goto unlock;
		}
	}
	if (j != -1) {
		for (; i > j; i--)
			h->nets[i].cidr[n] = h->nets[i - 1].cidr[n];
	}
	h->nets[i].cidr[n] = cidr;
	h->nets[CIDR_POS(cidr)].nets[n] = 1;
unlock:
	spin_unlock_bh(&set->lock);
}

static void
mtype_del_cidr(struct ip_set *set, struct htype *h, u8 cidr, u8 n)
{
	u8 i, j, net_end = NLEN - 1;

	spin_lock_bh(&set->lock);
	for (i = 0; i < NLEN; i++) {
		if (h->nets[i].cidr[n] != cidr)
			continue;
		h->nets[CIDR_POS(cidr)].nets[n]--;
		if (h->nets[CIDR_POS(cidr)].nets[n] > 0)
			goto unlock;
		for (j = i; j < net_end && h->nets[j].cidr[n]; j++)
			h->nets[j].cidr[n] = h->nets[j + 1].cidr[n];
		h->nets[j].cidr[n] = 0;
		goto unlock;
	}
unlock:
	spin_unlock_bh(&set->lock);
}
#endif

/* Calculate the actual memory size of the set data */
static size_t
mtype_ahash_memsize(const struct htype *h, const struct htable *t)
{}

/* Get the ith element from the array block n */
#define ahash_data(n, i, dsize)

static void
mtype_ext_cleanup(struct ip_set *set, struct hbucket *n)
{}

/* Flush a hash type of set: destroy all elements */
static void
mtype_flush(struct ip_set *set)
{}

/* Destroy the hashtable part of the set */
static void
mtype_ahash_destroy(struct ip_set *set, struct htable *t, bool ext_destroy)
{}

/* Destroy a hash type of set */
static void
mtype_destroy(struct ip_set *set)
{}

static bool
mtype_same_set(const struct ip_set *a, const struct ip_set *b)
{}

static void
mtype_gc_do(struct ip_set *set, struct htype *h, struct htable *t, u32 r)
{}

static void
mtype_gc(struct work_struct *work)
{}

static void
mtype_gc_init(struct htable_gc *gc)
{}

static void
mtype_cancel_gc(struct ip_set *set)
{}

static int
mtype_add(struct ip_set *set, void *value, const struct ip_set_ext *ext,
	  struct ip_set_ext *mext, u32 flags);
static int
mtype_del(struct ip_set *set, void *value, const struct ip_set_ext *ext,
	  struct ip_set_ext *mext, u32 flags);

/* Resize a hash: create a new hash table with doubling the hashsize
 * and inserting the elements to it. Repeat until we succeed or
 * fail due to memory pressures.
 */
static int
mtype_resize(struct ip_set *set, bool retried)
{}

/* Get the current number of elements and ext_size in the set  */
static void
mtype_ext_size(struct ip_set *set, u32 *elements, size_t *ext_size)
{}

/* Add an element to a hash and update the internal counters when succeeded,
 * otherwise report the proper error code.
 */
static int
mtype_add(struct ip_set *set, void *value, const struct ip_set_ext *ext,
	  struct ip_set_ext *mext, u32 flags)
{}

/* Delete an element from the hash and free up space if possible.
 */
static int
mtype_del(struct ip_set *set, void *value, const struct ip_set_ext *ext,
	  struct ip_set_ext *mext, u32 flags)
{}

static int
mtype_data_match(struct mtype_elem *data, const struct ip_set_ext *ext,
		 struct ip_set_ext *mext, struct ip_set *set, u32 flags)
{}

#ifdef IP_SET_HASH_WITH_NETS
/* Special test function which takes into account the different network
 * sizes added to the set
 */
static int
mtype_test_cidrs(struct ip_set *set, struct mtype_elem *d,
		 const struct ip_set_ext *ext,
		 struct ip_set_ext *mext, u32 flags)
{
	struct htype *h = set->data;
	struct htable *t = rcu_dereference_bh(h->table);
	struct hbucket *n;
	struct mtype_elem *data;
#if IPSET_NET_COUNT == 2
	struct mtype_elem orig = *d;
	int ret, i, j = 0, k;
#else
	int ret, i, j = 0;
#endif
	u32 key, multi = 0;

	pr_debug("test by nets\n");
	for (; j < NLEN && h->nets[j].cidr[0] && !multi; j++) {
#if IPSET_NET_COUNT == 2
		mtype_data_reset_elem(d, &orig);
		mtype_data_netmask(d, NCIDR_GET(h->nets[j].cidr[0]), false);
		for (k = 0; k < NLEN && h->nets[k].cidr[1] && !multi;
		     k++) {
			mtype_data_netmask(d, NCIDR_GET(h->nets[k].cidr[1]),
					   true);
#else
		mtype_data_netmask(d, NCIDR_GET(h->nets[j].cidr[0]));
#endif
		key = HKEY(d, h->initval, t->htable_bits);
		n = rcu_dereference_bh(hbucket(t, key));
		if (!n)
			continue;
		for (i = 0; i < n->pos; i++) {
			if (!test_bit(i, n->used))
				continue;
			data = ahash_data(n, i, set->dsize);
			if (!mtype_data_equal(data, d, &multi))
				continue;
			ret = mtype_data_match(data, ext, mext, set, flags);
			if (ret != 0)
				return ret;
#ifdef IP_SET_HASH_WITH_MULTI
			/* No match, reset multiple match flag */
			multi = 0;
#endif
		}
#if IPSET_NET_COUNT == 2
		}
#endif
	}
	return 0;
}
#endif

/* Test whether the element is added to the set */
static int
mtype_test(struct ip_set *set, void *value, const struct ip_set_ext *ext,
	   struct ip_set_ext *mext, u32 flags)
{}

/* Reply a HEADER request: fill out the header part of the set */
static int
mtype_head(struct ip_set *set, struct sk_buff *skb)
{}

/* Make possible to run dumping parallel with resizing */
static void
mtype_uref(struct ip_set *set, struct netlink_callback *cb, bool start)
{}

/* Reply a LIST/SAVE request: dump the elements of the specified set */
static int
mtype_list(const struct ip_set *set,
	   struct sk_buff *skb, struct netlink_callback *cb)
{}

static int
IPSET_TOKEN(MTYPE, _kadt)(struct ip_set *set, const struct sk_buff *skb,
			  const struct xt_action_param *par,
			  enum ipset_adt adt, struct ip_set_adt_opt *opt);

static int
IPSET_TOKEN(MTYPE, _uadt)(struct ip_set *set, struct nlattr *tb[],
			  enum ipset_adt adt, u32 *lineno, u32 flags,
			  bool retried);

static const struct ip_set_type_variant mtype_variant =;

#ifdef IP_SET_EMIT_CREATE
static int
IPSET_TOKEN(HTYPE, _create)(struct net *net, struct ip_set *set,
			    struct nlattr *tb[], u32 flags)
{
	u32 hashsize = IPSET_DEFAULT_HASHSIZE, maxelem = IPSET_DEFAULT_MAXELEM;
#ifdef IP_SET_HASH_WITH_MARKMASK
	u32 markmask;
#endif
	u8 hbits;
#if defined(IP_SET_HASH_WITH_NETMASK) || defined(IP_SET_HASH_WITH_BITMASK)
	int ret __attribute__((unused)) = 0;
	u8 netmask = set->family == NFPROTO_IPV4 ? 32 : 128;
	union nf_inet_addr bitmask = onesmask;
#endif
	size_t hsize;
	struct htype *h;
	struct htable *t;
	u32 i;

	pr_debug("Create set %s with family %s\n",
		 set->name, set->family == NFPROTO_IPV4 ? "inet" : "inet6");

#ifdef IP_SET_PROTO_UNDEF
	if (set->family != NFPROTO_UNSPEC)
		return -IPSET_ERR_INVALID_FAMILY;
#else
	if (!(set->family == NFPROTO_IPV4 || set->family == NFPROTO_IPV6))
		return -IPSET_ERR_INVALID_FAMILY;
#endif

	if (unlikely(!ip_set_optattr_netorder(tb, IPSET_ATTR_HASHSIZE) ||
		     !ip_set_optattr_netorder(tb, IPSET_ATTR_MAXELEM) ||
		     !ip_set_optattr_netorder(tb, IPSET_ATTR_TIMEOUT) ||
		     !ip_set_optattr_netorder(tb, IPSET_ATTR_CADT_FLAGS)))
		return -IPSET_ERR_PROTOCOL;

#ifdef IP_SET_HASH_WITH_MARKMASK
	/* Separated condition in order to avoid directive in argument list */
	if (unlikely(!ip_set_optattr_netorder(tb, IPSET_ATTR_MARKMASK)))
		return -IPSET_ERR_PROTOCOL;

	markmask = 0xffffffff;
	if (tb[IPSET_ATTR_MARKMASK]) {
		markmask = ntohl(nla_get_be32(tb[IPSET_ATTR_MARKMASK]));
		if (markmask == 0)
			return -IPSET_ERR_INVALID_MARKMASK;
	}
#endif

#ifdef IP_SET_HASH_WITH_NETMASK
	if (tb[IPSET_ATTR_NETMASK]) {
		netmask = nla_get_u8(tb[IPSET_ATTR_NETMASK]);

		if ((set->family == NFPROTO_IPV4 && netmask > 32) ||
		    (set->family == NFPROTO_IPV6 && netmask > 128) ||
		    netmask == 0)
			return -IPSET_ERR_INVALID_NETMASK;

		/* we convert netmask to bitmask and store it */
		if (set->family == NFPROTO_IPV4)
			bitmask.ip = ip_set_netmask(netmask);
		else
			ip6_netmask(&bitmask, netmask);
	}
#endif

#ifdef IP_SET_HASH_WITH_BITMASK
	if (tb[IPSET_ATTR_BITMASK]) {
		/* bitmask and netmask do the same thing, allow only one of these options */
		if (tb[IPSET_ATTR_NETMASK])
			return -IPSET_ERR_BITMASK_NETMASK_EXCL;

		if (set->family == NFPROTO_IPV4) {
			ret = ip_set_get_ipaddr4(tb[IPSET_ATTR_BITMASK], &bitmask.ip);
			if (ret || !bitmask.ip)
				return -IPSET_ERR_INVALID_NETMASK;
		} else if (set->family == NFPROTO_IPV6) {
			ret = ip_set_get_ipaddr6(tb[IPSET_ATTR_BITMASK], &bitmask);
			if (ret || ipv6_addr_any(&bitmask.in6))
				return -IPSET_ERR_INVALID_NETMASK;
		}

		if (nf_inet_addr_cmp(&bitmask, &zeromask))
			return -IPSET_ERR_INVALID_NETMASK;
	}
#endif

	if (tb[IPSET_ATTR_HASHSIZE]) {
		hashsize = ip_set_get_h32(tb[IPSET_ATTR_HASHSIZE]);
		if (hashsize < IPSET_MIMINAL_HASHSIZE)
			hashsize = IPSET_MIMINAL_HASHSIZE;
	}

	if (tb[IPSET_ATTR_MAXELEM])
		maxelem = ip_set_get_h32(tb[IPSET_ATTR_MAXELEM]);

	hsize = sizeof(*h);
	h = kzalloc(hsize, GFP_KERNEL);
	if (!h)
		return -ENOMEM;

	/* Compute htable_bits from the user input parameter hashsize.
	 * Assume that hashsize == 2^htable_bits,
	 * otherwise round up to the first 2^n value.
	 */
	hbits = fls(hashsize - 1);
	hsize = htable_size(hbits);
	if (hsize == 0) {
		kfree(h);
		return -ENOMEM;
	}
	t = ip_set_alloc(hsize);
	if (!t) {
		kfree(h);
		return -ENOMEM;
	}
	t->hregion = ip_set_alloc(ahash_sizeof_regions(hbits));
	if (!t->hregion) {
		ip_set_free(t);
		kfree(h);
		return -ENOMEM;
	}
	h->gc.set = set;
	for (i = 0; i < ahash_numof_locks(hbits); i++)
		spin_lock_init(&t->hregion[i].lock);
	h->maxelem = maxelem;
#if defined(IP_SET_HASH_WITH_NETMASK) || defined(IP_SET_HASH_WITH_BITMASK)
	h->bitmask = bitmask;
	h->netmask = netmask;
#endif
#ifdef IP_SET_HASH_WITH_MARKMASK
	h->markmask = markmask;
#endif
	if (tb[IPSET_ATTR_INITVAL])
		h->initval = ntohl(nla_get_be32(tb[IPSET_ATTR_INITVAL]));
	else
		get_random_bytes(&h->initval, sizeof(h->initval));
	h->bucketsize = AHASH_MAX_SIZE;
	if (tb[IPSET_ATTR_BUCKETSIZE]) {
		h->bucketsize = nla_get_u8(tb[IPSET_ATTR_BUCKETSIZE]);
		if (h->bucketsize < AHASH_INIT_SIZE)
			h->bucketsize = AHASH_INIT_SIZE;
		else if (h->bucketsize > AHASH_MAX_SIZE)
			h->bucketsize = AHASH_MAX_SIZE;
		else if (h->bucketsize % 2)
			h->bucketsize += 1;
	}
	t->htable_bits = hbits;
	t->maxelem = h->maxelem / ahash_numof_locks(hbits);
	RCU_INIT_POINTER(h->table, t);

	INIT_LIST_HEAD(&h->ad);
	set->data = h;
#ifndef IP_SET_PROTO_UNDEF
	if (set->family == NFPROTO_IPV4) {
#endif
		set->variant = &IPSET_TOKEN(HTYPE, 4_variant);
		set->dsize = ip_set_elem_len(set, tb,
			sizeof(struct IPSET_TOKEN(HTYPE, 4_elem)),
			__alignof__(struct IPSET_TOKEN(HTYPE, 4_elem)));
#ifndef IP_SET_PROTO_UNDEF
	} else {
		set->variant = &IPSET_TOKEN(HTYPE, 6_variant);
		set->dsize = ip_set_elem_len(set, tb,
			sizeof(struct IPSET_TOKEN(HTYPE, 6_elem)),
			__alignof__(struct IPSET_TOKEN(HTYPE, 6_elem)));
	}
#endif
	set->timeout = IPSET_NO_TIMEOUT;
	if (tb[IPSET_ATTR_TIMEOUT]) {
		set->timeout = ip_set_timeout_uget(tb[IPSET_ATTR_TIMEOUT]);
#ifndef IP_SET_PROTO_UNDEF
		if (set->family == NFPROTO_IPV4)
#endif
			IPSET_TOKEN(HTYPE, 4_gc_init)(&h->gc);
#ifndef IP_SET_PROTO_UNDEF
		else
			IPSET_TOKEN(HTYPE, 6_gc_init)(&h->gc);
#endif
	}
	pr_debug("create %s hashsize %u (%u) maxelem %u: %p(%p)\n",
		 set->name, jhash_size(t->htable_bits),
		 t->htable_bits, h->maxelem, set->data, t);

	return 0;
}
#endif /* IP_SET_EMIT_CREATE */

#undef HKEY_DATALEN