/* Shared library add-on to iptables to add addrtype matching support 
 *
 * Copyright (c) 2003-2013 Patrick McHardy <kaber@trash.net>
 * 
 * This program is released under the terms of GNU GPL */
#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <xtables.h>
#include <linux/netfilter/xt_addrtype.h>

enum {
	O_SRC_TYPE = 0,
	O_DST_TYPE,
	O_LIMIT_IFACE_IN,
	O_LIMIT_IFACE_OUT,
	F_SRC_TYPE        = 1 << O_SRC_TYPE,
	F_DST_TYPE        = 1 << O_DST_TYPE,
	F_LIMIT_IFACE_IN  = 1 << O_LIMIT_IFACE_IN,
	F_LIMIT_IFACE_OUT = 1 << O_LIMIT_IFACE_OUT,
};

/* from linux/rtnetlink.h, must match order of enumeration */
static const char *const rtn_names[] = {
	"UNSPEC",
	"UNICAST",
	"LOCAL",
	"BROADCAST",
	"ANYCAST",
	"MULTICAST",
	"BLACKHOLE",
	"UNREACHABLE",
	"PROHIBIT",
	"THROW",
	"NAT",
	"XRESOLVE",
	NULL
};

static void addrtype_help_types(void)
{
	int i;

	for (i = 0; rtn_names[i]; i++)
		printf("                                %s\n", rtn_names[i]);
}

static void addrtype_help_v0(void)
{
	printf(
"Address type match options:\n"
" [!] --src-type type[,...]      Match source address type\n"
" [!] --dst-type type[,...]      Match destination address type\n"
"\n"
"Valid types:           \n");
	addrtype_help_types();
}

static void addrtype_help_v1(void)
{
	printf(
"Address type match options:\n"
" [!] --src-type type[,...]      Match source address type\n"
" [!] --dst-type type[,...]      Match destination address type\n"
"     --limit-iface-in           Match only on the packet's incoming device\n"
"     --limit-iface-out          Match only on the packet's outgoing device\n"
"\n"
"Valid types:           \n");
	addrtype_help_types();
}

static int
parse_type(const char *name, size_t len, uint16_t *mask)
{
	int i;

	for (i = 0; rtn_names[i]; i++)
		if (strncasecmp(name, rtn_names[i], len) == 0) {
			/* build up bitmask for kernel module */
			*mask |= (1 << i);
			return 1;
		}

	return 0;
}

static void parse_types(const char *arg, uint16_t *mask)
{
	const char *comma;

	while ((comma = strchr(arg, ',')) != NULL) {
		if (comma == arg || !parse_type(arg, comma-arg, mask))
			xtables_error(PARAMETER_PROBLEM,
			           "addrtype: bad type `%s'", arg);
		arg = comma + 1;
	}

	if (strlen(arg) == 0 || !parse_type(arg, strlen(arg), mask))
		xtables_error(PARAMETER_PROBLEM, "addrtype: bad type \"%s\"", arg);
}
	
static void addrtype_parse_v0(struct xt_option_call *cb)
{
	struct xt_addrtype_info *info = cb->data;

	xtables_option_parse(cb);
	switch (cb->entry->id) {
	case O_SRC_TYPE:
		parse_types(cb->arg, &info->source);
		if (cb->invert)
			info->invert_source = 1;
		break;
	case O_DST_TYPE:
		parse_types(cb->arg, &info->dest);
		if (cb->invert)
			info->invert_dest = 1;
		break;
	}
}

static void addrtype_parse_v1(struct xt_option_call *cb)
{
	struct xt_addrtype_info_v1 *info = cb->data;

	xtables_option_parse(cb);
	switch (cb->entry->id) {
	case O_SRC_TYPE:
		parse_types(cb->arg, &info->source);
		if (cb->invert)
			info->flags |= XT_ADDRTYPE_INVERT_SOURCE;
		break;
	case O_DST_TYPE:
		parse_types(cb->arg, &info->dest);
		if (cb->invert)
			info->flags |= XT_ADDRTYPE_INVERT_DEST;
		break;
	case O_LIMIT_IFACE_IN:
		info->flags |= XT_ADDRTYPE_LIMIT_IFACE_IN;
		break;
	case O_LIMIT_IFACE_OUT:
		info->flags |= XT_ADDRTYPE_LIMIT_IFACE_OUT;
		break;
	}
}

static void addrtype_check(struct xt_fcheck_call *cb)
{
	if (!(cb->xflags & (F_SRC_TYPE | F_DST_TYPE)))
		xtables_error(PARAMETER_PROBLEM,
			   "addrtype: you must specify --src-type or --dst-type");
}

static void print_types(uint16_t mask)
{
	const char *sep = "";
	int i;

	for (i = 0; rtn_names[i]; i++)
		if (mask & (1 << i)) {
			printf("%s%s", sep, rtn_names[i]);
			sep = ",";
		}
}

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

	printf(" ADDRTYPE match");
	if (info->source) {
		printf(" src-type ");
		if (info->invert_source)
			printf("!");
		print_types(info->source);
	}
	if (info->dest) {
		printf(" dst-type");
		if (info->invert_dest)
			printf("!");
		print_types(info->dest);
	}
}

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

	printf(" ADDRTYPE match");
	if (info->source) {
		printf(" src-type ");
		if (info->flags & XT_ADDRTYPE_INVERT_SOURCE)
			printf("!");
		print_types(info->source);
	}
	if (info->dest) {
		printf(" dst-type ");
		if (info->flags & XT_ADDRTYPE_INVERT_DEST)
			printf("!");
		print_types(info->dest);
	}
	if (info->flags & XT_ADDRTYPE_LIMIT_IFACE_IN)
		printf(" limit-in");
	if (info->flags & XT_ADDRTYPE_LIMIT_IFACE_OUT)
		printf(" limit-out");
}

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

	if (info->source) {
		if (info->invert_source)
			printf(" !");
		printf(" --src-type ");
		print_types(info->source);
	}
	if (info->dest) {
		if (info->invert_dest)
			printf(" !");
		printf(" --dst-type ");
		print_types(info->dest);
	}
}

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

	if (info->source) {
		if (info->flags & XT_ADDRTYPE_INVERT_SOURCE)
			printf(" !");
		printf(" --src-type ");
		print_types(info->source);
	}
	if (info->dest) {
		if (info->flags & XT_ADDRTYPE_INVERT_DEST)
			printf(" !");
		printf(" --dst-type ");
		print_types(info->dest);
	}
	if (info->flags & XT_ADDRTYPE_LIMIT_IFACE_IN)
		printf(" --limit-iface-in");
	if (info->flags & XT_ADDRTYPE_LIMIT_IFACE_OUT)
		printf(" --limit-iface-out");
}

static const char *const rtn_lnames[] = {
	"unspec",
	"unicast",
	"local",
	"broadcast",
	"anycast",
	"multicast",
	"blackhole",
	"unreachable",
	"prohibit",
	NULL,
};

static bool multiple_bits_set(uint16_t val)
{
	int first = ffs(val);

	return first && (val >> first) > 0;
}

static int addrtype_xlate(struct xt_xlate *xl,
			  const struct xt_xlate_mt_params *params)
{
	const struct xt_addrtype_info_v1 *info =
		(const void *)params->match->data;
	const char *sep = "";
	bool need_braces;
	uint16_t val;
	int i;

	xt_xlate_add(xl, "fib ");

	if (info->source) {
		xt_xlate_add(xl, "saddr ");
		val = info->source;
	} else {
		xt_xlate_add(xl, "daddr ");
		val = info->dest;
	}

	if (info->flags & XT_ADDRTYPE_LIMIT_IFACE_IN)
		xt_xlate_add(xl, ". iif ");
	else if (info->flags & XT_ADDRTYPE_LIMIT_IFACE_OUT)
		xt_xlate_add(xl, ". oif ");

	xt_xlate_add(xl, "type ");

	if (info->flags & (XT_ADDRTYPE_INVERT_SOURCE | XT_ADDRTYPE_INVERT_DEST))
			xt_xlate_add(xl, "!= ");

	need_braces = multiple_bits_set(val);

	if (need_braces)
		xt_xlate_add(xl, "{ ");

	for (i = 0; rtn_lnames[i]; i++) {
		if (val & (1 << i)) {
			xt_xlate_add(xl, "%s%s", sep, rtn_lnames[i]);
			sep = ", ";
		}
	}

	if (need_braces)
		xt_xlate_add(xl, " }");

	return 1;
}

static const struct xt_option_entry addrtype_opts_v0[] = {
	{.name = "src-type", .id = O_SRC_TYPE, .type = XTTYPE_STRING,
	 .flags = XTOPT_INVERT},
	{.name = "dst-type", .id = O_DST_TYPE, .type = XTTYPE_STRING,
	 .flags = XTOPT_INVERT},
	XTOPT_TABLEEND,
};

static const struct xt_option_entry addrtype_opts_v1[] = {
	{.name = "src-type", .id = O_SRC_TYPE, .type = XTTYPE_STRING,
	 .flags = XTOPT_INVERT},
	{.name = "dst-type", .id = O_DST_TYPE, .type = XTTYPE_STRING,
	 .flags = XTOPT_INVERT},
	{.name = "limit-iface-in", .id = O_LIMIT_IFACE_IN,
	 .type = XTTYPE_NONE, .excl = F_LIMIT_IFACE_OUT},
	{.name = "limit-iface-out", .id = O_LIMIT_IFACE_OUT,
	 .type = XTTYPE_NONE, .excl = F_LIMIT_IFACE_IN},
	XTOPT_TABLEEND,
};

static struct xtables_match addrtype_mt_reg[] = {
	{
		.name          = "addrtype",
		.version       = XTABLES_VERSION,
		.family        = NFPROTO_IPV4,
		.size          = XT_ALIGN(sizeof(struct xt_addrtype_info)),
		.userspacesize = XT_ALIGN(sizeof(struct xt_addrtype_info)),
		.help          = addrtype_help_v0,
		.print         = addrtype_print_v0,
		.save          = addrtype_save_v0,
		.x6_parse      = addrtype_parse_v0,
		.x6_fcheck     = addrtype_check,
		.x6_options    = addrtype_opts_v0,
	},
	{
		.name          = "addrtype",
		.revision      = 1,
		.version       = XTABLES_VERSION,
		.family        = NFPROTO_UNSPEC,
		.size          = XT_ALIGN(sizeof(struct xt_addrtype_info_v1)),
		.userspacesize = XT_ALIGN(sizeof(struct xt_addrtype_info_v1)),
		.help          = addrtype_help_v1,
		.print         = addrtype_print_v1,
		.save          = addrtype_save_v1,
		.x6_parse      = addrtype_parse_v1,
		.x6_fcheck     = addrtype_check,
		.x6_options    = addrtype_opts_v1,
		.xlate         = addrtype_xlate,
	},
};


void _init(void) 
{
	xtables_register_matches(addrtype_mt_reg, ARRAY_SIZE(addrtype_mt_reg));
}
