The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#define BUILD_TARGET
#define MODULE_DATATYPE struct ip_nat_multi_range
#define MODULE_NAME "REDIRECT"

#define __USE_GNU
#include "../module_iface.h"
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <linux/netfilter_ipv4/ip_tables.h>
#include <linux/netfilter_ipv4/ip_nat.h>
#include <netinet/in.h>

static void setup(void *myinfo, unsigned int *nfcache) {
	MODULE_DATATYPE *info = (void *)((MODULE_ENTRYTYPE *)myinfo)->data;

	info->rangesize = 1;
	*nfcache |= NFC_UNKNOWN;
}

static int parse_nat_range(char *string, struct ip_nat_range *range,
		struct ipt_entry *entry) {
	char *sep, *extent, *temp;
	int port;

	if(entry->ip.proto != IPPROTO_TCP && entry->ip.proto != IPPROTO_UDP) {
		SET_ERRSTR("to-ports: Protocol must be TCP or UDP to specify ports");
		return(FALSE);
	}
	
	sep = strchr(string, '-');

	port = strtoul(string, &extent, 10);
	if(extent < (sep ? sep : (string + strlen(string))) || port == 0 ||
					port > USHRT_MAX)
		return(FALSE);

	range->min.tcp.port = range->max.tcp.port = htons(port);

	if(sep) {
		temp = sep + 1;
		port = strtoul(temp, &extent, 10);

		if(extent < (temp + strlen(temp)) || port == 0 || port > USHRT_MAX ||
						port < range->min.tcp.port)
			return(FALSE);

		range->max.tcp.port = htons(port);
	}

	return(TRUE);
}

static int parse_field(char *field, SV *value, void *myinfo,
		unsigned int *nfcache, struct ipt_entry *entry, int *flags) {
	MODULE_DATATYPE *info = (void *)(*(MODULE_ENTRYTYPE **)myinfo)->data;
	
	if(strcmp(field, "to-ports"))
		return(FALSE);

	if(SvIOK(value))
		info->range->min.tcp.port = info->range->max.tcp.port = 
				htons(SvIV(value));
	else if(SvPOK(value)) {
		char *str, *temp;
		STRLEN len;

		temp = SvPV(value, len);
		str = malloc(len + 1);
		strncpy(str, temp, len);
		str[len] = '\0';

		if(!parse_nat_range(str, info->range, entry)) {
			if(!SvOK(ERROR_SV))
				SET_ERRSTR("%s: Unable to parse value", field);
			free(str);
			return(FALSE);
		}
		free(str);
	}
	else {
		SET_ERRSTR("%s: Arg must be string or integer", field);
		return(FALSE);
	}

	info->range->flags |= IP_NAT_RANGE_PROTO_SPECIFIED;

	return(TRUE);
}

static SV *sv_from_nat_range(struct ip_nat_range *range) {
	char *string;
	
	if(range->flags & IP_NAT_RANGE_PROTO_SPECIFIED) {
		SV *sv;
		if(range->min.tcp.port < range->max.tcp.port) {
			asprintf(&string, "%u-%u", ntohs(range->min.tcp.port),
							ntohs(range->max.tcp.port));
			sv = newSVpv(string, 0);
			free(string);
		}
		else
			sv = newSViv(ntohs(range->min.tcp.port));
		return sv;
	}
	return(NULL);
}

static void get_fields(HV *ent_hash, void *myinfo, struct ipt_entry *entry) {
	MODULE_DATATYPE *info = (void *)((MODULE_ENTRYTYPE *)myinfo)->data;
	SV *sv;

	if((sv = sv_from_nat_range(info->range)))
		hv_store(ent_hash, "to-ports", 8, sv, 0);
}

ModuleDef _module = {
	.type			= MODULE_TYPE,
	.name			= MODULE_NAME,
	.size			= IPT_ALIGN(sizeof(MODULE_DATATYPE)),
	.size_uspace	= IPT_ALIGN(sizeof(MODULE_DATATYPE)),
	.setup			= setup,
	.parse_field	= parse_field,
	.get_fields		= get_fields,
};

ModuleDef *init(void) {
	return(&_module);
}
/* vim: ts=4
 */