1/* Shared library add-on to iptables to add source-NAT support. */
2#include <stdio.h>
3#include <netdb.h>
4#include <string.h>
5#include <stdlib.h>
6#include <getopt.h>
7#include <iptables.h>
8#include <linux/netfilter_ipv4/ip_tables.h>
9#include <linux/netfilter_ipv4/ip_nat_rule.h>
10
11/* Source NAT data consists of a multi-range, indicating where to map
12   to. */
13struct ipt_natinfo
14{
15	struct ipt_entry_target t;
16	struct ip_nat_multi_range mr;
17};
18
19/* Function which prints out usage message. */
20static void
21help(void)
22{
23	printf(
24"SNAT v%s options:\n"
25" --to-source <ipaddr>[-<ipaddr>][:port-port]\n"
26"				Address to map source to.\n"
27"				(You can use this more than once)\n\n",
28IPTABLES_VERSION);
29}
30
31static struct option opts[] = {
32	{ "to-source", 1, 0, '1' },
33	{ 0 }
34};
35
36/* Initialize the target. */
37static void
38init(struct ipt_entry_target *t, unsigned int *nfcache)
39{
40	/* Can't cache this */
41	*nfcache |= NFC_UNKNOWN;
42}
43
44static struct ipt_natinfo *
45append_range(struct ipt_natinfo *info, const struct ip_nat_range *range)
46{
47	unsigned int size;
48
49	/* One rangesize already in struct ipt_natinfo */
50	size = IPT_ALIGN(sizeof(*info) + info->mr.rangesize * sizeof(*range));
51
52	info = realloc(info, size);
53	if (!info)
54		exit_error(OTHER_PROBLEM, "Out of memory\n");
55
56	info->t.u.target_size = size;
57	info->mr.range[info->mr.rangesize] = *range;
58	info->mr.rangesize++;
59
60	return info;
61}
62
63/* Ranges expected in network order. */
64static struct ipt_entry_target *
65parse_to(char *arg, int portok, struct ipt_natinfo *info)
66{
67	struct ip_nat_range range;
68	char *colon, *dash;
69	struct in_addr *ip;
70
71	memset(&range, 0, sizeof(range));
72	colon = strchr(arg, ':');
73
74	if (colon) {
75		int port;
76
77		if (!portok)
78			exit_error(PARAMETER_PROBLEM,
79				   "Need TCP or UDP with port specification");
80
81		range.flags |= IP_NAT_RANGE_PROTO_SPECIFIED;
82
83		port = atoi(colon+1);
84		if (port == 0 || port > 65535)
85			exit_error(PARAMETER_PROBLEM,
86				   "Port `%s' not valid\n", colon+1);
87
88		dash = strchr(colon, '-');
89		if (!dash) {
90			range.min.tcp.port
91				= range.max.tcp.port
92				= htons(port);
93		} else {
94			int maxport;
95
96			maxport = atoi(dash + 1);
97			if (maxport == 0 || maxport > 65535)
98				exit_error(PARAMETER_PROBLEM,
99					   "Port `%s' not valid\n", dash+1);
100			if (maxport < port)
101				/* People are stupid. */
102				exit_error(PARAMETER_PROBLEM,
103					   "Port range `%s' funky\n", colon+1);
104			range.min.tcp.port = htons(port);
105			range.max.tcp.port = htons(maxport);
106		}
107		/* Starts with a colon? No IP info...*/
108		if (colon == arg)
109			return &(append_range(info, &range)->t);
110		*colon = '\0';
111	}
112
113	range.flags |= IP_NAT_RANGE_MAP_IPS;
114	dash = strchr(arg, '-');
115	if (colon && dash && dash > colon)
116		dash = NULL;
117
118	if (dash)
119		*dash = '\0';
120
121	ip = dotted_to_addr(arg);
122	if (!ip)
123		exit_error(PARAMETER_PROBLEM, "Bad IP address `%s'\n",
124			   arg);
125	range.min_ip = ip->s_addr;
126	if (dash) {
127		ip = dotted_to_addr(dash+1);
128		if (!ip)
129			exit_error(PARAMETER_PROBLEM, "Bad IP address `%s'\n",
130				   dash+1);
131		range.max_ip = ip->s_addr;
132	} else
133		range.max_ip = range.min_ip;
134
135	return &(append_range(info, &range)->t);
136}
137
138/* Function which parses command options; returns true if it
139   ate an option */
140static int
141parse(int c, char **argv, int invert, unsigned int *flags,
142      const struct ipt_entry *entry,
143      struct ipt_entry_target **target)
144{
145	struct ipt_natinfo *info = (void *)*target;
146	int portok;
147
148	if (entry->ip.proto == IPPROTO_TCP
149	    || entry->ip.proto == IPPROTO_UDP)
150		portok = 1;
151	else
152		portok = 0;
153
154	switch (c) {
155	case '1':
156		if (check_inverse(optarg, &invert, NULL, 0))
157			exit_error(PARAMETER_PROBLEM,
158				   "Unexpected `!' after --to-source");
159
160		*target = parse_to(optarg, portok, info);
161		*flags = 1;
162		return 1;
163
164	default:
165		return 0;
166	}
167}
168
169/* Final check; must have specfied --to-source. */
170static void final_check(unsigned int flags)
171{
172	if (!flags)
173		exit_error(PARAMETER_PROBLEM,
174			   "You must specify --to-source");
175}
176
177static void print_range(const struct ip_nat_range *r)
178{
179	if (r->flags & IP_NAT_RANGE_MAP_IPS) {
180		struct in_addr a;
181
182		a.s_addr = r->min_ip;
183		printf("%s", addr_to_dotted(&a));
184		if (r->max_ip != r->min_ip) {
185			a.s_addr = r->max_ip;
186			printf("-%s", addr_to_dotted(&a));
187		}
188	}
189	if (r->flags & IP_NAT_RANGE_PROTO_SPECIFIED) {
190		printf(":");
191		printf("%hu", ntohs(r->min.tcp.port));
192		if (r->max.tcp.port != r->min.tcp.port)
193			printf("-%hu", ntohs(r->max.tcp.port));
194	}
195}
196
197/* Prints out the targinfo. */
198static void
199print(const struct ipt_ip *ip,
200      const struct ipt_entry_target *target,
201      int numeric)
202{
203	struct ipt_natinfo *info = (void *)target;
204	unsigned int i = 0;
205
206	printf("to:");
207	for (i = 0; i < info->mr.rangesize; i++) {
208		print_range(&info->mr.range[i]);
209		printf(" ");
210	}
211}
212
213/* Saves the union ipt_targinfo in parsable form to stdout. */
214static void
215save(const struct ipt_ip *ip, const struct ipt_entry_target *target)
216{
217	struct ipt_natinfo *info = (void *)target;
218	unsigned int i = 0;
219
220	for (i = 0; i < info->mr.rangesize; i++) {
221		printf("--to-source ");
222		print_range(&info->mr.range[i]);
223		printf(" ");
224	}
225}
226
227static
228struct iptables_target snat
229= { NULL,
230    "SNAT",
231    IPTABLES_VERSION,
232    IPT_ALIGN(sizeof(struct ip_nat_multi_range)),
233    IPT_ALIGN(sizeof(struct ip_nat_multi_range)),
234    &help,
235    &init,
236    &parse,
237    &final_check,
238    &print,
239    &save,
240    opts
241};
242
243void _init(void)
244{
245	register_target(&snat);
246}
247