Initial release
diff --git a/extensions/ebt_inat.c b/extensions/ebt_inat.c
new file mode 100644
index 0000000..4ec16b5
--- /dev/null
+++ b/extensions/ebt_inat.c
@@ -0,0 +1,379 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <netinet/ether.h>
+#include <getopt.h>
+#include <ctype.h>
+#include "../include/ebtables_u.h"
+#include <linux/netfilter_bridge/ebt_inat.h>
+
+static int s_sub_supplied, d_sub_supplied;
+
+#define NAT_S '1'
+#define NAT_D '1'
+#define NAT_S_SUB '2'
+#define NAT_D_SUB '2'
+#define NAT_S_TARGET '3'
+#define NAT_D_TARGET '3'
+static struct option opts_s[] =
+{
+	{ "isnat-list"     , required_argument, 0, NAT_S },
+	{ "isnat-sub"      , required_argument, 0, NAT_S_SUB },
+	{ "isnat-default-target"   , required_argument, 0, NAT_S_TARGET },
+	{ 0 }
+};
+
+static struct option opts_d[] =
+{
+	{ "idnat-list"     , required_argument, 0, NAT_D },
+	{ "idnat-sub"      , required_argument, 0, NAT_D_SUB },
+	{ "idnat-default-target"   , required_argument, 0, NAT_D_TARGET },
+	{ 0 }
+};
+
+static void print_help_common(const char *cas)
+{
+	printf(
+"isnat options:\n"
+" --i%1.1snat-list                    : indexed list of MAC addresses\n"
+" --i%1.1snat-sub                     : /24 subnet to which the rule apply\n"
+" --i%1.1snat-default-target target   : ACCEPT, DROP, RETURN or CONTINUE\n"
+"Indexed list of addresses is as follows:\n"
+"\tlist := chunk\n"
+"\tlist := list chunk\n"
+"\tchunk := pair ','\n"
+"\tpair := index '=' action\n"
+"\taction := mac_addr\n"
+"\taction := mac_addr '+'\n"
+"\taction := '!'\n"
+"where\n"
+"\tindex -- an integer [0..255]\n"
+"\tmac_addr -- a MAC address in format xx:xx:xx:xx:xx:xx\n"
+"If '!' at some index is specified, packets with last %s IP address byte\n"
+"equal to index are DROPped. If there is a MAC address, they are %1.1snatted\n"
+"to this and the target is CONTINUE. If this MAC is followed by '+', the\n"
+"target is ACCEPT.\n"
+"For example,\n"
+"--idnat-list 2=20:21:22:23:24:25,4=!,7=30:31:32:33:34:35,\n"
+"is valid.\n"
+"The subnet MUST be specified. Only packets with 3 first bytes of their\n"
+"%s address are considered. --i%1.1snat-sub parameter must begin with\n"
+"3 integers separated by dots. Only they are considered, the rest is ignored.\n"
+"No matter if you write '192.168.42.', '192.168.42.0', '192.168.42.12',\n"
+"'192.168.42.0/24', '192.168.42.0/23' or '192.168.42.i%1.1snat_sucks!!!',\n"
+"The numbers 192, 168 and 42 are taken and it behaves like a /24 IP subnet.\n"
+"--i%1.1snat-default-target affects only the packet not matching the subnet.\n",
+		cas, cas, cas, cas, cas, cas, cas, cas,
+		cas, cas, cas, cas, cas, cas, cas, cas
+	);
+}
+
+static void print_help_s()
+{
+	print_help_common("src");
+}
+
+static void print_help_d()
+{
+	print_help_common("dest");
+}
+
+static void init_s(struct ebt_entry_target *target)
+{
+	struct ebt_inat_info *natinfo = (struct ebt_inat_info *)target->data;
+
+	s_sub_supplied = 0;
+	memset(natinfo, 0, sizeof(struct ebt_inat_info));
+	natinfo->target = EBT_CONTINUE;
+	return;
+}
+
+static void init_d(struct ebt_entry_target *target)
+{
+	struct ebt_inat_info *natinfo = (struct ebt_inat_info *)target->data;
+
+	d_sub_supplied = 0;
+	memset(natinfo, 0, sizeof(struct ebt_inat_info));
+	natinfo->target = EBT_CONTINUE;
+	return;
+}
+
+static void parse_list(const char *arg, struct ebt_inat_info *info)
+{
+	int i;
+	char c;
+	int count = 0;
+	int now_index = 1;
+	int index;
+	char buf[4];
+	unsigned char mac[6];
+	int ibuf = 0;
+	int imac = 0;
+	int target;
+	memset(buf, 0, 4);
+	i = 0;
+	while (1) {
+		c = arg[i];
+		if (now_index) {
+			if (isdigit(c)) {
+				buf[ibuf++] = c;
+				if (ibuf > 3) {
+					print_error("Index too long at position %d", i);
+				}
+				goto next;
+			}
+			if (c == '=') {
+				if (ibuf == 0) {
+					print_error("Integer index expected before '=' at position %d", i);
+				}
+				buf[ibuf] = 0;
+				ibuf = 0;
+				index = atoi(buf);
+				if (index < 0 || 255 < index) {
+					print_error("Index out of range [0..255], namely %d", index);
+				}
+				now_index = 0;
+				memset(mac, 0, 6);
+				imac = 0;
+				target = EBT_CONTINUE;
+				goto next;
+			}
+			if (c == '\0') {
+				goto next;
+			}
+			print_error("Unexpected '%c' where integer or '=' expected", c);
+		}
+		else {
+			if (isxdigit(c)) {
+				buf[ibuf++] = c;
+				if (ibuf > 2) {
+					print_error("MAC address chunk too long at position %d", i);
+				}
+				goto next;
+			}
+			if (c == ':' || c == ',' || c == '\0') {
+				buf[ibuf] = 0;
+				ibuf = 0;
+				mac[imac++] = strtol(buf, 0, 16);
+				if (c == ',' || c == '\0') {
+					info->a[index].enabled = 1;
+					info->a[index].target = target;
+					memcpy(info->a[index].mac, mac, 6);
+					now_index = 1;
+					count++;
+					goto next;
+				}
+				if (c == ':' && imac >= 6) {
+					print_error("Too many MAC address chunks at position %d", i);
+				}
+				goto next;
+			}
+			if (c == '!') {
+				target = EBT_DROP;
+				goto next;
+			}
+			if (c == '+') {
+				target = EBT_ACCEPT;
+				goto next;
+			}
+			print_error("Unexpected '%c' where hex digit, '!', '+', ',' or end of string expected", c);
+		}
+	next:
+		if (!c) break;
+		i++;
+	}
+	if (count == 0) {
+		print_error("List empty");
+	}
+}
+
+static uint32_t parse_ip(const char *s)
+{
+	int a0, a1, a2;
+	char ip[4];
+	sscanf(s, "%d.%d.%d", &a0, &a1, &a2);
+	ip[0] = a0;
+	ip[1] = a1;
+	ip[2] = a2;
+	ip[3] = 0;
+	return *(uint32_t*)ip;
+}
+
+#define OPT_ISNAT         0x01
+#define OPT_ISNAT_SUB     0x02
+#define OPT_ISNAT_TARGET  0x04
+static int parse_s(int c, char **argv, int argc,
+   const struct ebt_u_entry *entry, unsigned int *flags,
+   struct ebt_entry_target **target)
+{
+	struct ebt_inat_info *natinfo = (struct ebt_inat_info *)(*target)->data;
+
+	switch (c) {
+	case NAT_S:
+		check_option(flags, OPT_ISNAT);
+		parse_list(optarg, natinfo);
+		break;
+	case NAT_S_TARGET:
+		check_option(flags, OPT_ISNAT_TARGET);
+		if (FILL_TARGET(optarg, natinfo->target))
+			print_error("Illegal --isnat-default-target target");
+		break;
+	case NAT_S_SUB:
+		natinfo->ip_subnet = parse_ip(optarg);
+		s_sub_supplied = 1;
+		break;
+	default:
+		return 0;
+	}
+	return 1;
+}
+
+#define OPT_IDNAT        0x01
+#define OPT_IDNAT_SUB    0x02
+#define OPT_IDNAT_TARGET 0x04
+static int parse_d(int c, char **argv, int argc,
+   const struct ebt_u_entry *entry, unsigned int *flags,
+   struct ebt_entry_target **target)
+{
+	struct ebt_inat_info *natinfo = (struct ebt_inat_info *)(*target)->data;
+
+	switch (c) {
+	case NAT_D:
+		check_option(flags, OPT_IDNAT);
+		parse_list(optarg, natinfo);
+		break;
+	case NAT_D_TARGET:
+		check_option(flags, OPT_IDNAT_TARGET);
+		if (FILL_TARGET(optarg, natinfo->target))
+			print_error("Illegal --idnat-default-target target");
+		break;
+	case NAT_D_SUB:
+		natinfo->ip_subnet = parse_ip(optarg);
+		d_sub_supplied = 1;
+		break;
+	default:
+		return 0;
+	}
+	return 1;
+}
+
+static void final_check_s(const struct ebt_u_entry *entry,
+   const struct ebt_entry_target *target, const char *name,
+   unsigned int hookmask, unsigned int time)
+{
+	struct ebt_inat_info *natinfo = (struct ebt_inat_info *)target->data;
+
+	if (BASE_CHAIN && natinfo->target == EBT_RETURN)
+		print_error("--isnat-default-target RETURN not allowed on base chain");
+	CLEAR_BASE_CHAIN_BIT;
+	if ((hookmask & ~(1 << NF_BR_POST_ROUTING)) || strcmp(name, "nat"))
+		print_error("Wrong chain for isnat");
+	if (time == 0 && s_sub_supplied == 0)
+		print_error("No isnat subnet supplied");
+}
+
+static void final_check_d(const struct ebt_u_entry *entry,
+   const struct ebt_entry_target *target, const char *name,
+   unsigned int hookmask, unsigned int time)
+{
+	struct ebt_inat_info *natinfo = (struct ebt_inat_info *)target->data;
+
+	if (BASE_CHAIN && natinfo->target == EBT_RETURN)
+		print_error("--idnat-default-target RETURN not allowed on base chain");
+	CLEAR_BASE_CHAIN_BIT;
+	if (((hookmask & ~((1 << NF_BR_PRE_ROUTING) | (1 << NF_BR_LOCAL_OUT)))
+	   || strcmp(name, "nat")) &&
+	   ((hookmask & ~(1 << NF_BR_BROUTING)) || strcmp(name, "broute")))
+		print_error("Wrong chain for idnat");
+	if (time == 0 && d_sub_supplied == 0)
+		print_error("No idnat subnet supplied");
+}
+
+static void print_list(const struct ebt_inat_info *info)
+{
+	int i;
+	for (i = 0; i < 256; i++) {
+		if (info->a[i].enabled) {
+			printf("%d=", i);
+			if (info->a[i].target == EBT_DROP) {
+				printf("!");
+			}
+			else {
+				if (info->a[i].target == EBT_ACCEPT) {
+					printf("+");
+				}
+				printf("%s", ether_ntoa((struct ether_addr *)info->a[i].mac));
+			}
+			printf(",");
+		}
+	}
+}
+
+static void print_s(const struct ebt_u_entry *entry,
+   const struct ebt_entry_target *target)
+{
+	struct ebt_inat_info *info = (struct ebt_inat_info *)target->data;
+
+	unsigned char sub[4];
+	*(uint32_t*)sub = info->ip_subnet;
+	printf("--isnat-sub %u.%u.%u.0/24", sub[0], sub[1], sub[2]);
+	printf(" --isnat-list ");
+	print_list(info);
+	printf(" --isnat-default-target %s", TARGET_NAME(info->target));
+}
+
+static void print_d(const struct ebt_u_entry *entry,
+   const struct ebt_entry_target *target)
+{
+	struct ebt_inat_info *info = (struct ebt_inat_info *)target->data;
+
+	unsigned char sub[4];
+	*(uint32_t*)sub = info->ip_subnet;
+	printf("--idnat-sub %u.%u.%u.0/24", sub[0], sub[1], sub[2]);
+	printf(" --idnat-list ");
+	print_list(info);
+	printf(" --idnat-default-target %s", TARGET_NAME(info->target));
+}
+
+static int compare(const struct ebt_entry_target *t1,
+   const struct ebt_entry_target *t2)
+{
+	struct ebt_inat_info *natinfo1 = (struct ebt_inat_info *)t1->data;
+	struct ebt_inat_info *natinfo2 = (struct ebt_inat_info *)t2->data;
+
+
+	return !memcmp(natinfo1, natinfo2, sizeof(struct ebt_inat_info));
+}
+
+static struct ebt_u_target isnat_target =
+{
+	EBT_ISNAT_TARGET,
+	sizeof(struct ebt_inat_info),
+	print_help_s,
+	init_s,
+	parse_s,
+	final_check_s,
+	print_s,
+	compare,
+	opts_s,
+};
+
+static struct ebt_u_target idnat_target =
+{
+	EBT_IDNAT_TARGET,
+	sizeof(struct ebt_inat_info),
+	print_help_d,
+	init_d,
+	parse_d,
+	final_check_d,
+	print_d,
+	compare,
+	opts_d
+};
+
+static void _init(void) __attribute__ ((constructor));
+static void _init(void)
+{
+	register_target(&isnat_target);
+	register_target(&idnat_target);
+}