The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#define INET6
#define BUILD_MATCH
#define MODULE_DATATYPE struct ip6t_udp
#define MODULE_NAME "udp"

#define __USE_GNU
#include "../module_iface.h"
#include <string.h>
#include <stdlib.h>
#include <netdb.h>
#include <stdio.h>
#include <netinet/in.h>
#include <limits.h>
#include <linux/netfilter_ipv6/ip6_tables.h>

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

	info->spts[0] = info->dpts[0] = 0;
	info->spts[1] = info->dpts[1] = USHRT_MAX;
}

static bool parse_ports_sv(SV *portval, u_int16_t *ports, int *inv,
				const char *protocol_name) {
	struct servent *service;
	int val;
	
	*inv = FALSE;
	if(SvIOK(portval)) {
		val = SvIV(portval);
		if(val < 0 || val > USHRT_MAX) {
			SET_ERRSTR("Port number out of range");
			return(FALSE);
		}
		ports[0] = ports[1] = val;
	}
	else if(SvPOK(portval)) {
		STRLEN len;
		char *temp = SvPV(portval, len);
		char *text = NULL, *sep = NULL, *base = NULL;

		base = text = malloc(len + 1);
		strncpy(text, temp, len);
		text[len] = '\0';

		if(*text == INVCHAR) {
			*inv = TRUE;
			text++;
		}
		sep = strchr(text, ':');
		if(sep == text)
			ports[0] = 0;
		else {
			if(sep)
				*sep = '\0';
			if((service = getservbyname(text, protocol_name)))
				ports[0] = ntohs(service->s_port);
			else {
				val = strtoul(text, &temp, 10);
				if((text + strlen(text) > temp) || val < 0 || val > USHRT_MAX) {
					SET_ERRSTR("Unable to parse '%s'", text);
					free(base);
					return(FALSE);
				}
				ports[0] = val;
			}
			if(temp)
				*temp = ':';
		}

		if(sep) {
			text = ++sep;
			if(*text == '\0')
				ports[1] = USHRT_MAX;
			else {
				if((service = getservbyname(text, protocol_name)))
					ports[1] = ntohs(service->s_port);
				else {
					val = strtoul(text, &temp, 10);
					if((text + strlen(text) > temp) || val < 0 ||
							val > USHRT_MAX) {
						SET_ERRSTR("Unable to parse '%s'", text);
						free(base);
						return(FALSE);
					}
					ports[1] = val;
				}
			}
		}
		else
			ports[1] = ports[0];
		free(base);
	}
	else {
		SET_ERRSTR("Must be passed as integer or string");
		return(FALSE);
	}
	return(TRUE);
}

static int parse_field(char *field, SV *value, void *myinfo,
		unsigned int *nfcache, struct ip6t_entry *entry, int *flags) {
	MODULE_DATATYPE *info = (void *)(*(MODULE_ENTRYTYPE **)myinfo)->data;
	int inv;

	if(!strcmp(field, "source-port")) {
		if(!parse_ports_sv(value, info->spts, &inv, "udp")) {
			char *temp = strdup(SvPV_nolen(ERROR_SV));
			SET_ERRSTR("%s: %s", field, temp);
			free(temp);
			return(FALSE);
		}

		if(inv)
			info->invflags |= IP6T_UDP_INV_SRCPT;

		*nfcache |= NFC_IP6_SRC_PT;
		return(TRUE);
	}
	else if(!strcmp(field, "destination-port")) {
		if(!parse_ports_sv(value, info->dpts, &inv, "udp")) {
			char *temp = strdup(SvPV_nolen(ERROR_SV));
			SET_ERRSTR("%s: %s", field, temp);
			free(temp);
			return(FALSE);
		}

		if(inv)
			info->invflags |= IP6T_UDP_INV_DSTPT;

		*nfcache |= NFC_IP6_DST_PT;
		return(TRUE);
	}
	return(FALSE);
}

static SV *build_sv_from_portrange(u_int16_t *ports, bool inv) {
	char *temp, *temp2;
	struct servent *service;
	SV *sv;
	
	if((service = getservbyport(htons(ports[0]), "udp")))
		temp = strdup(service->s_name);
	else {
		if(ports[0] == ports[1] && !inv)
			return(newSViv(ports[0]));
		asprintf(&temp, "%u", ports[0]);
	}
	if(ports[0] != ports[1]) {
		if((service = getservbyport(htons(ports[1]), "udp")))
			asprintf(&temp2, "%s:%s", temp, service->s_name);
		else
			asprintf(&temp2, "%s:%u", temp, ports[1]);
		free(temp);
		temp = temp2;
	}
	if(inv) {
		asprintf(&temp2, "%c%s", INVCHAR, temp);
		free(temp);
		temp = temp2;
	}
	sv = newSVpv(temp, 0);
	free(temp);
	return(sv);
}

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

	if(info->spts[0] != 0 || info->spts[1] != USHRT_MAX) {
		sv = build_sv_from_portrange(info->spts,
				info->invflags & IP6T_UDP_INV_SRCPT);
		hv_store(ent_hash, "source-port", 11, sv, 0);
	}
	if(info->dpts[0] != 0 || info->dpts[1] != USHRT_MAX) {
		sv = build_sv_from_portrange(info->dpts,
				info->invflags & IP6T_UDP_INV_DSTPT);
		hv_store(ent_hash, "destination-port", 16, sv, 0);
	}
}

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

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