1/*
2 * Copyright (c) 2011 Patrick McHardy <kaber@trash.net>
3 *
4 * Based on Rusty Russell's IPv4 SNAT target. Development of IPv6 NAT
5 * funded by Astaro.
6 */
7
8#include <stdio.h>
9#include <netdb.h>
10#include <string.h>
11#include <stdlib.h>
12#include <xtables.h>
13#include <iptables.h>
14#include <limits.h> /* INT_MAX in ip_tables.h */
15#include <linux/netfilter_ipv6/ip6_tables.h>
16#include <linux/netfilter/nf_nat.h>
17
18enum {
19	O_TO_SRC = 0,
20	O_RANDOM,
21	O_PERSISTENT,
22	O_X_TO_SRC,
23	F_TO_SRC   = 1 << O_TO_SRC,
24	F_RANDOM   = 1 << O_RANDOM,
25	F_X_TO_SRC = 1 << O_X_TO_SRC,
26};
27
28static void SNAT_help(void)
29{
30	printf(
31"SNAT target options:\n"
32" --to-source [<ipaddr>[-<ipaddr>]][:port[-port]]\n"
33"				Address to map source to.\n"
34"[--random] [--persistent]\n");
35}
36
37static const struct xt_option_entry SNAT_opts[] = {
38	{.name = "to-source", .id = O_TO_SRC, .type = XTTYPE_STRING,
39	 .flags = XTOPT_MAND | XTOPT_MULTI},
40	{.name = "random", .id = O_RANDOM, .type = XTTYPE_NONE},
41	{.name = "persistent", .id = O_PERSISTENT, .type = XTTYPE_NONE},
42	XTOPT_TABLEEND,
43};
44
45/* Ranges expected in network order. */
46static void
47parse_to(const char *orig_arg, int portok, struct nf_nat_range *range)
48{
49	char *arg, *start, *end = NULL, *colon = NULL, *dash, *error;
50	const struct in6_addr *ip;
51
52	arg = strdup(orig_arg);
53	if (arg == NULL)
54		xtables_error(RESOURCE_PROBLEM, "strdup");
55
56	start = strchr(arg, '[');
57	if (start == NULL) {
58		start = arg;
59		/* Lets assume one colon is port information. Otherwise its an IPv6 address */
60		colon = strchr(arg, ':');
61		if (colon && strchr(colon+1, ':'))
62			colon = NULL;
63	}
64	else {
65		start++;
66		end = strchr(start, ']');
67		if (end == NULL)
68			xtables_error(PARAMETER_PROBLEM,
69				      "Invalid address format");
70
71		*end = '\0';
72		colon = strchr(end + 1, ':');
73	}
74
75	if (colon) {
76		int port;
77
78		if (!portok)
79			xtables_error(PARAMETER_PROBLEM,
80				   "Need TCP, UDP, SCTP or DCCP with port specification");
81
82		range->flags |= NF_NAT_RANGE_PROTO_SPECIFIED;
83
84		port = atoi(colon+1);
85		if (port <= 0 || port > 65535)
86			xtables_error(PARAMETER_PROBLEM,
87				   "Port `%s' not valid\n", colon+1);
88
89		error = strchr(colon+1, ':');
90		if (error)
91			xtables_error(PARAMETER_PROBLEM,
92				   "Invalid port:port syntax - use dash\n");
93
94		dash = strchr(colon, '-');
95		if (!dash) {
96			range->min_proto.tcp.port
97				= range->max_proto.tcp.port
98				= htons(port);
99		} else {
100			int maxport;
101
102			maxport = atoi(dash + 1);
103			if (maxport <= 0 || maxport > 65535)
104				xtables_error(PARAMETER_PROBLEM,
105					   "Port `%s' not valid\n", dash+1);
106			if (maxport < port)
107				/* People are stupid. */
108				xtables_error(PARAMETER_PROBLEM,
109					   "Port range `%s' funky\n", colon+1);
110			range->min_proto.tcp.port = htons(port);
111			range->max_proto.tcp.port = htons(maxport);
112		}
113		/* Starts with colon or [] colon? No IP info...*/
114		if (colon == arg || colon == arg+2) {
115			free(arg);
116			return;
117		}
118		*colon = '\0';
119	}
120
121	range->flags |= NF_NAT_RANGE_MAP_IPS;
122	dash = strchr(start, '-');
123	if (colon && dash && dash > colon)
124		dash = NULL;
125
126	if (dash)
127		*dash = '\0';
128
129	ip = xtables_numeric_to_ip6addr(start);
130	if (!ip)
131		xtables_error(PARAMETER_PROBLEM, "Bad IP address \"%s\"\n",
132			      start);
133	range->min_addr.in6 = *ip;
134	if (dash) {
135		ip = xtables_numeric_to_ip6addr(dash + 1);
136		if (!ip)
137			xtables_error(PARAMETER_PROBLEM, "Bad IP address \"%s\"\n",
138				      dash+1);
139		range->max_addr.in6 = *ip;
140	} else
141		range->max_addr = range->min_addr;
142
143	free(arg);
144	return;
145}
146
147static void SNAT_parse(struct xt_option_call *cb)
148{
149	const struct ip6t_entry *entry = cb->xt_entry;
150	struct nf_nat_range *range = cb->data;
151	int portok;
152
153	if (entry->ipv6.proto == IPPROTO_TCP ||
154	    entry->ipv6.proto == IPPROTO_UDP ||
155	    entry->ipv6.proto == IPPROTO_SCTP ||
156	    entry->ipv6.proto == IPPROTO_DCCP ||
157	    entry->ipv6.proto == IPPROTO_ICMP)
158		portok = 1;
159	else
160		portok = 0;
161
162	xtables_option_parse(cb);
163	switch (cb->entry->id) {
164	case O_TO_SRC:
165		if (cb->xflags & F_X_TO_SRC) {
166			if (!kernel_version)
167				get_kernel_version();
168			if (kernel_version > LINUX_VERSION(2, 6, 10))
169				xtables_error(PARAMETER_PROBLEM,
170					   "SNAT: Multiple --to-source not supported");
171		}
172		parse_to(cb->arg, portok, range);
173		break;
174	case O_PERSISTENT:
175		range->flags |= NF_NAT_RANGE_PERSISTENT;
176		break;
177	}
178}
179
180static void SNAT_fcheck(struct xt_fcheck_call *cb)
181{
182	static const unsigned int f = F_TO_SRC | F_RANDOM;
183	struct nf_nat_range *range = cb->data;
184
185	if ((cb->xflags & f) == f)
186		range->flags |= NF_NAT_RANGE_PROTO_RANDOM;
187}
188
189static void print_range(const struct nf_nat_range *range)
190{
191	if (range->flags & NF_NAT_RANGE_MAP_IPS) {
192		if (range->flags & NF_NAT_RANGE_PROTO_SPECIFIED)
193			printf("[");
194		printf("%s", xtables_ip6addr_to_numeric(&range->min_addr.in6));
195		if (memcmp(&range->min_addr, &range->max_addr,
196			   sizeof(range->min_addr)))
197			printf("-%s", xtables_ip6addr_to_numeric(&range->max_addr.in6));
198		if (range->flags & NF_NAT_RANGE_PROTO_SPECIFIED)
199			printf("]");
200	}
201	if (range->flags & NF_NAT_RANGE_PROTO_SPECIFIED) {
202		printf(":");
203		printf("%hu", ntohs(range->min_proto.tcp.port));
204		if (range->max_proto.tcp.port != range->min_proto.tcp.port)
205			printf("-%hu", ntohs(range->max_proto.tcp.port));
206	}
207}
208
209static void SNAT_print(const void *ip, const struct xt_entry_target *target,
210                       int numeric)
211{
212	const struct nf_nat_range *range = (const void *)target->data;
213
214	printf(" to:");
215	print_range(range);
216	if (range->flags & NF_NAT_RANGE_PROTO_RANDOM)
217		printf(" random");
218	if (range->flags & NF_NAT_RANGE_PERSISTENT)
219		printf(" persistent");
220}
221
222static void SNAT_save(const void *ip, const struct xt_entry_target *target)
223{
224	const struct nf_nat_range *range = (const void *)target->data;
225
226	printf(" --to-source ");
227	print_range(range);
228	if (range->flags & NF_NAT_RANGE_PROTO_RANDOM)
229		printf(" --random");
230	if (range->flags & NF_NAT_RANGE_PERSISTENT)
231		printf(" --persistent");
232}
233
234static struct xtables_target snat_tg_reg = {
235	.name		= "SNAT",
236	.version	= XTABLES_VERSION,
237	.family		= NFPROTO_IPV6,
238	.revision	= 1,
239	.size		= XT_ALIGN(sizeof(struct nf_nat_range)),
240	.userspacesize	= XT_ALIGN(sizeof(struct nf_nat_range)),
241	.help		= SNAT_help,
242	.x6_parse	= SNAT_parse,
243	.x6_fcheck	= SNAT_fcheck,
244	.print		= SNAT_print,
245	.save		= SNAT_save,
246	.x6_options	= SNAT_opts,
247};
248
249void _init(void)
250{
251	xtables_register_target(&snat_tg_reg);
252}
253