/*
 * (C) 2009 by Pablo Neira Ayuso <pablo@netfilter.org>
 *
 * 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.
 */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <getopt.h>
#include <stddef.h>

#include <xtables.h>
#include <linux/netfilter/x_tables.h>
#include <linux/netfilter/xt_cluster.h>

/* hack to keep for check */
static unsigned int total_nodes;
static unsigned int node_mask;

static void
cluster_help(void)
{
	printf(
"cluster match options:\n"
"  --cluster-total-nodes <num>		Set number of total nodes in cluster\n"
"  [!] --cluster-local-node <num>	Set the local node number\n"
"  [!] --cluster-local-nodemask <num>	Set the local node mask\n"
"  --cluster-hash-seed <num>		Set seed value of the Jenkins hash\n");
}

enum {
	CLUSTER_OPT_TOTAL_NODES,
	CLUSTER_OPT_LOCAL_NODE,
	CLUSTER_OPT_NODE_MASK,
	CLUSTER_OPT_HASH_SEED,
};

static const struct option cluster_opts[] = {
	{ "cluster-total-nodes", 	1, NULL, CLUSTER_OPT_TOTAL_NODES },
	{ "cluster-local-node", 	1, NULL, CLUSTER_OPT_LOCAL_NODE },
	{ "cluster-local-nodemask",	1, NULL, CLUSTER_OPT_NODE_MASK },
	{ "cluster-hash-seed",		1, NULL, CLUSTER_OPT_HASH_SEED },
	{ .name = NULL }
};

static int 
cluster_parse(int c, char **argv, int invert, unsigned int *flags,
	      const void *entry, struct xt_entry_match **match)
{
	struct xt_cluster_match_info *info = (void *)(*match)->data;
	unsigned int num;

	switch (c) {
	case CLUSTER_OPT_TOTAL_NODES:
		if (*flags & (1 << c)) {
			xtables_error(PARAMETER_PROBLEM,
				      "Can only specify "
				      "`--cluster-total-nodes' once");
		}
		if (!xtables_strtoui(optarg, NULL, &num, 1,
				     XT_CLUSTER_NODES_MAX)) {
			xtables_error(PARAMETER_PROBLEM,
				      "Unable to parse `%s' in "
				      "`--cluster-total-nodes'", optarg);
		}
		total_nodes = num;
		info->total_nodes = total_nodes = num;
		*flags |= 1 << c;
		break;
	case CLUSTER_OPT_LOCAL_NODE:
		if (*flags & (1 << c)) {
			xtables_error(PARAMETER_PROBLEM,
				      "Can only specify "
				      "`--cluster-local-node' once");
		}
		if (*flags & (1 << CLUSTER_OPT_NODE_MASK)) {
			xtables_error(PARAMETER_PROBLEM, "You cannot use "
				      "`--cluster-local-nodemask' and "
				      "`--cluster-local-node'");
		}
		xtables_check_inverse(optarg, &invert, &optind, 0, argv);

		if (!xtables_strtoui(optarg, NULL, &num, 1,
				     XT_CLUSTER_NODES_MAX)) {
			xtables_error(PARAMETER_PROBLEM,
				      "Unable to parse `%s' in "
				      "`--cluster-local-node'", optarg);
		}
		if (invert)
			info->flags |= (1 << XT_CLUSTER_F_INV);

		info->node_mask = node_mask = (1 << (num - 1));
		*flags |= 1 << c;
		break;
	case CLUSTER_OPT_NODE_MASK:
		if (*flags & (1 << c)) {
			xtables_error(PARAMETER_PROBLEM,
				      "Can only specify "
				      "`--cluster-local-node' once");
		}
		if (*flags & (1 << CLUSTER_OPT_LOCAL_NODE)) {
			xtables_error(PARAMETER_PROBLEM, "You cannot use "
				      "`--cluster-local-nodemask' and "
				      "`--cluster-local-node'");
		}
		xtables_check_inverse(optarg, &invert, &optind, 0, argv);

		if (!xtables_strtoui(optarg, NULL, &num, 1,
				     XT_CLUSTER_NODES_MAX)) {
			xtables_error(PARAMETER_PROBLEM,
				      "Unable to parse `%s' in "
				      "`--cluster-local-node'", optarg);
		}
		if (invert)
			info->flags |= (1 << XT_CLUSTER_F_INV);

		info->node_mask = node_mask = num;
		*flags |= 1 << c;
		break;

	case CLUSTER_OPT_HASH_SEED:
		if (*flags & (1 << c)) {
			xtables_error(PARAMETER_PROBLEM,
				      "Can only specify "
				      "`--cluster-hash-seed' once");
		}
		if (!xtables_strtoui(optarg, NULL, &num, 0, UINT32_MAX)) {
			xtables_error(PARAMETER_PROBLEM,
				      "Unable to parse `%s'", optarg);
		}
		info->hash_seed = num;
		*flags |= 1 << c;
		break;
	default:
		return 0;
	}

	return 1;
}

static void
cluster_check(unsigned int flags)
{
	if ((flags & ((1 << CLUSTER_OPT_TOTAL_NODES) |
		      (1 << CLUSTER_OPT_LOCAL_NODE) |
		      (1 << CLUSTER_OPT_HASH_SEED)))
		== ((1 << CLUSTER_OPT_TOTAL_NODES) |
		    (1 << CLUSTER_OPT_LOCAL_NODE) |
		    (1 << CLUSTER_OPT_HASH_SEED))) {
		if (node_mask >= (1ULL << total_nodes)) {
			xtables_error(PARAMETER_PROBLEM,
				      "cluster match: "
				      "`--cluster-local-node' "
				      "must be <= `--cluster-total-nodes'");
		}
		return;
	}
	if ((flags & ((1 << CLUSTER_OPT_TOTAL_NODES) |
		      (1 << CLUSTER_OPT_NODE_MASK) |
		      (1 << CLUSTER_OPT_HASH_SEED)))
		== ((1 << CLUSTER_OPT_TOTAL_NODES) |
		    (1 << CLUSTER_OPT_NODE_MASK) |
		    (1 << CLUSTER_OPT_HASH_SEED))) {
		if (node_mask >= (1ULL << total_nodes)) {
			xtables_error(PARAMETER_PROBLEM,
				      "cluster match: "
				      "`--cluster-local-nodemask' too big "
				      "for `--cluster-total-nodes'");
		}
		return;
	}
	if (!(flags & (1 << CLUSTER_OPT_TOTAL_NODES))) {
		xtables_error(PARAMETER_PROBLEM,
			      "cluster match: `--cluster-total-nodes' "
			      "is missing");
	}
	if (!(flags & (1 << CLUSTER_OPT_HASH_SEED))) {
		xtables_error(PARAMETER_PROBLEM,
			      "cluster match: `--cluster-hash-seed' "
			      "is missing");
	}
	if (!(flags & ((1 << (CLUSTER_OPT_LOCAL_NODE) |
		       (1 << (CLUSTER_OPT_NODE_MASK)))))) {
		xtables_error(PARAMETER_PROBLEM,
			      "cluster match: `--cluster-local-node' or"
			      "`--cluster-local-nodemask' is missing");
	}
}

static void
cluster_print(const void *ip, const struct xt_entry_match *match, int numeric)
{
	const struct xt_cluster_match_info *info = (void *)match->data;

	printf("cluster ");
	if (info->flags & XT_CLUSTER_F_INV)
		printf("!node_mask=0x%08x ", info->node_mask);
	else
		printf("node_mask=0x%08x ", info->node_mask);

	printf("total_nodes=%u hash_seed=0x%08x ", 
		info->total_nodes, info->hash_seed);
}

static void
cluster_save(const void *ip, const struct xt_entry_match *match)
{
	const struct xt_cluster_match_info *info = (void *)match->data;

	if (info->flags & XT_CLUSTER_F_INV)
		printf("! --cluster-local-nodemask 0x%08x ", info->node_mask);
	else
		printf("--cluster-local-nodemask 0x%08x ", info->node_mask);

	printf("--cluster-total-nodes %u --cluster-hash-seed 0x%08x ",
		info->total_nodes, info->hash_seed);
}

static struct xtables_match cluster_mt_reg = {
	.family		= NFPROTO_UNSPEC,
	.name		= "cluster",
	.version	= XTABLES_VERSION,
	.size		= XT_ALIGN(sizeof(struct xt_cluster_match_info)),
	.userspacesize  = XT_ALIGN(sizeof(struct xt_cluster_match_info)),
 	.help		= cluster_help,
	.parse		= cluster_parse,
	.final_check	= cluster_check,
	.print		= cluster_print,
	.save		= cluster_save,
	.extra_opts	= cluster_opts,
};

void _init(void)
{
	xtables_register_match(&cluster_mt_reg);
}
