libxt_ipvs.c revision c36d05e42406966440e3644110d3d2504c4b165c
1/*
2 * Shared library add-on to iptables to add IPVS matching.
3 *
4 * Detailed doc is in the kernel module source net/netfilter/xt_ipvs.c
5 *
6 * Author: Hannes Eder <heder@google.com>
7 */
8#include <sys/types.h>
9#include <assert.h>
10#include <ctype.h>
11#include <errno.h>
12#include <getopt.h>
13#include <netdb.h>
14#include <stdlib.h>
15#include <stdio.h>
16#include <string.h>
17#include <xtables.h>
18#include <linux/ip_vs.h>
19#include <linux/netfilter/xt_ipvs.h>
20
21static const struct option ipvs_mt_opts[] = {
22	{ .name = "ipvs",     .has_arg = false, .val = '0' },
23	{ .name = "vproto",   .has_arg = true,  .val = '1' },
24	{ .name = "vaddr",    .has_arg = true,  .val = '2' },
25	{ .name = "vport",    .has_arg = true,  .val = '3' },
26	{ .name = "vdir",     .has_arg = true,  .val = '4' },
27	{ .name = "vmethod",  .has_arg = true,  .val = '5' },
28	{ .name = "vportctl", .has_arg = true,  .val = '6' },
29	{ .name = NULL }
30};
31
32static void ipvs_mt_help(void)
33{
34	printf(
35"IPVS match options:\n"
36"[!] --ipvs                      packet belongs to an IPVS connection\n"
37"\n"
38"Any of the following options implies --ipvs (even negated)\n"
39"[!] --vproto protocol           VIP protocol to match; by number or name,\n"
40"                                e.g. \"tcp\"\n"
41"[!] --vaddr address[/mask]      VIP address to match\n"
42"[!] --vport port                VIP port to match; by number or name,\n"
43"                                e.g. \"http\"\n"
44"    --vdir {ORIGINAL|REPLY}     flow direction of packet\n"
45"[!] --vmethod {GATE|IPIP|MASQ}  IPVS forwarding method used\n"
46"[!] --vportctl port             VIP port of the controlling connection to\n"
47"                                match, e.g. 21 for FTP\n"
48		);
49}
50
51static void ipvs_mt_parse_addr_and_mask(const char *arg,
52					union nf_inet_addr *address,
53					union nf_inet_addr *mask,
54					unsigned int family)
55{
56	struct in_addr *addr = NULL;
57	struct in6_addr *addr6 = NULL;
58	unsigned int naddrs = 0;
59
60	if (family == NFPROTO_IPV4) {
61		xtables_ipparse_any(arg, &addr, &mask->in, &naddrs);
62		if (naddrs > 1)
63			xtables_error(PARAMETER_PROBLEM,
64				      "multiple IP addresses not allowed");
65		if (naddrs == 1)
66			memcpy(&address->in, addr, sizeof(*addr));
67	} else if (family == NFPROTO_IPV6) {
68		xtables_ip6parse_any(arg, &addr6, &mask->in6, &naddrs);
69		if (naddrs > 1)
70			xtables_error(PARAMETER_PROBLEM,
71				      "multiple IP addresses not allowed");
72		if (naddrs == 1)
73			memcpy(&address->in6, addr6, sizeof(*addr6));
74	} else {
75		/* Hu? */
76		assert(false);
77	}
78}
79
80/* Function which parses command options; returns true if it ate an option */
81static int ipvs_mt_parse(int c, char **argv, int invert, unsigned int *flags,
82			 const void *entry, struct xt_entry_match **match,
83			 unsigned int family)
84{
85	struct xt_ipvs_mtinfo *data = (void *)(*match)->data;
86	char *p = NULL;
87	u_int8_t op = 0;
88
89	if ('0' <= c && c <= '6') {
90		static const int ops[] = {
91			XT_IPVS_IPVS_PROPERTY,
92			XT_IPVS_PROTO,
93			XT_IPVS_VADDR,
94			XT_IPVS_VPORT,
95			XT_IPVS_DIR,
96			XT_IPVS_METHOD,
97			XT_IPVS_VPORTCTL
98		};
99		op = ops[c - '0'];
100	} else
101		return 0;
102
103	if (*flags & op & XT_IPVS_ONCE_MASK)
104		goto multiple_use;
105
106	switch (c) {
107	case '0': /* --ipvs */
108		/* Nothing to do here. */
109		break;
110
111	case '1': /* --vproto */
112		/* Canonicalize into lower case */
113		for (p = optarg; *p != '\0'; ++p)
114			*p = tolower(*p);
115
116		data->l4proto = xtables_parse_protocol(optarg);
117		break;
118
119	case '2': /* --vaddr */
120		ipvs_mt_parse_addr_and_mask(optarg, &data->vaddr,
121					    &data->vmask, family);
122		break;
123
124	case '3': /* --vport */
125		data->vport = htons(xtables_parse_port(optarg, "tcp"));
126		break;
127
128	case '4': /* --vdir */
129		xtables_param_act(XTF_NO_INVERT, "ipvs", "--vdir", invert);
130		if (strcasecmp(optarg, "ORIGINAL") == 0) {
131			data->bitmask |= XT_IPVS_DIR;
132			data->invert   &= ~XT_IPVS_DIR;
133		} else if (strcasecmp(optarg, "REPLY") == 0) {
134			data->bitmask |= XT_IPVS_DIR;
135			data->invert  |= XT_IPVS_DIR;
136		} else {
137			xtables_param_act(XTF_BAD_VALUE,
138					  "ipvs", "--vdir", optarg);
139		}
140		break;
141
142	case '5': /* --vmethod */
143		if (strcasecmp(optarg, "GATE") == 0)
144			data->fwd_method = IP_VS_CONN_F_DROUTE;
145		else if (strcasecmp(optarg, "IPIP") == 0)
146			data->fwd_method = IP_VS_CONN_F_TUNNEL;
147		else if (strcasecmp(optarg, "MASQ") == 0)
148			data->fwd_method = IP_VS_CONN_F_MASQ;
149		else
150			xtables_param_act(XTF_BAD_VALUE,
151					  "ipvs", "--vmethod", optarg);
152		break;
153
154	case '6': /* --vportctl */
155		data->vportctl = htons(xtables_parse_port(optarg, "tcp"));
156		break;
157
158	default:
159		/* Hu? How did we come here? */
160		assert(false);
161		return 0;
162	}
163
164	if (op & XT_IPVS_ONCE_MASK) {
165		if (data->invert & XT_IPVS_IPVS_PROPERTY)
166			xtables_error(PARAMETER_PROBLEM,
167				      "! --ipvs cannot be together with"
168				      " other options");
169		data->bitmask |= XT_IPVS_IPVS_PROPERTY;
170	}
171
172	data->bitmask |= op;
173	if (invert)
174		data->invert |= op;
175	*flags |= op;
176	return 1;
177
178multiple_use:
179	xtables_error(PARAMETER_PROBLEM,
180		      "multiple use of the same IPVS option is not allowed");
181}
182
183static int ipvs_mt4_parse(int c, char **argv, int invert, unsigned int *flags,
184			  const void *entry, struct xt_entry_match **match)
185{
186	return ipvs_mt_parse(c, argv, invert, flags, entry, match,
187			     NFPROTO_IPV4);
188}
189
190static int ipvs_mt6_parse(int c, char **argv, int invert, unsigned int *flags,
191			  const void *entry, struct xt_entry_match **match)
192{
193	return ipvs_mt_parse(c, argv, invert, flags, entry, match,
194			     NFPROTO_IPV6);
195}
196
197static void ipvs_mt_check(unsigned int flags)
198{
199	if (flags == 0)
200		xtables_error(PARAMETER_PROBLEM,
201			      "IPVS: At least one option is required");
202}
203
204/* Shamelessly copied from libxt_conntrack.c */
205static void ipvs_mt_dump_addr(const union nf_inet_addr *addr,
206			      const union nf_inet_addr *mask,
207			      unsigned int family, bool numeric)
208{
209	char buf[BUFSIZ];
210
211	if (family == NFPROTO_IPV4) {
212		if (!numeric && addr->ip == 0) {
213			printf("anywhere ");
214			return;
215		}
216		if (numeric)
217			strcpy(buf, xtables_ipaddr_to_numeric(&addr->in));
218		else
219			strcpy(buf, xtables_ipaddr_to_anyname(&addr->in));
220		strcat(buf, xtables_ipmask_to_numeric(&mask->in));
221		printf("%s ", buf);
222	} else if (family == NFPROTO_IPV6) {
223		if (!numeric && addr->ip6[0] == 0 && addr->ip6[1] == 0 &&
224		    addr->ip6[2] == 0 && addr->ip6[3] == 0) {
225			printf("anywhere ");
226			return;
227		}
228		if (numeric)
229			strcpy(buf, xtables_ip6addr_to_numeric(&addr->in6));
230		else
231			strcpy(buf, xtables_ip6addr_to_anyname(&addr->in6));
232		strcat(buf, xtables_ip6mask_to_numeric(&mask->in6));
233		printf("%s ", buf);
234	}
235}
236
237static void ipvs_mt_dump(const void *ip, const struct xt_ipvs_mtinfo *data,
238			 unsigned int family, bool numeric, const char *prefix)
239{
240	if (data->bitmask == XT_IPVS_IPVS_PROPERTY) {
241		if (data->invert & XT_IPVS_IPVS_PROPERTY)
242			printf("! ");
243		printf("%sipvs ", prefix);
244	}
245
246	if (data->bitmask & XT_IPVS_PROTO) {
247		if (data->invert & XT_IPVS_PROTO)
248			printf("! ");
249		printf("%sproto %u ", prefix, data->l4proto);
250	}
251
252	if (data->bitmask & XT_IPVS_VADDR) {
253		if (data->invert & XT_IPVS_VADDR)
254			printf("! ");
255
256		printf("%svaddr ", prefix);
257		ipvs_mt_dump_addr(&data->vaddr, &data->vmask, family, numeric);
258	}
259
260	if (data->bitmask & XT_IPVS_VPORT) {
261		if (data->invert & XT_IPVS_VPORT)
262			printf("! ");
263
264		printf("%svport %u ", prefix, ntohs(data->vport));
265	}
266
267	if (data->bitmask & XT_IPVS_DIR) {
268		if (data->invert & XT_IPVS_DIR)
269			printf("%svdir REPLY ", prefix);
270		else
271			printf("%svdir ORIGINAL ", prefix);
272	}
273
274	if (data->bitmask & XT_IPVS_METHOD) {
275		if (data->invert & XT_IPVS_METHOD)
276			printf("! ");
277
278		printf("%svmethod ", prefix);
279		switch (data->fwd_method) {
280		case IP_VS_CONN_F_DROUTE:
281			printf("GATE ");
282			break;
283		case IP_VS_CONN_F_TUNNEL:
284			printf("IPIP ");
285			break;
286		case IP_VS_CONN_F_MASQ:
287			printf("MASQ ");
288			break;
289		default:
290			/* Hu? */
291			printf("UNKNOWN ");
292			break;
293		}
294	}
295
296	if (data->bitmask & XT_IPVS_VPORTCTL) {
297		if (data->invert & XT_IPVS_VPORTCTL)
298			printf("! ");
299
300		printf("%svportctl %u ", prefix, ntohs(data->vportctl));
301	}
302}
303
304static void ipvs_mt4_print(const void *ip, const struct xt_entry_match *match,
305			   int numeric)
306{
307	const struct xt_ipvs_mtinfo *data = (const void *)match->data;
308	ipvs_mt_dump(ip, data, NFPROTO_IPV4, numeric, "");
309}
310
311static void ipvs_mt6_print(const void *ip, const struct xt_entry_match *match,
312			   int numeric)
313{
314	const struct xt_ipvs_mtinfo *data = (const void *)match->data;
315	ipvs_mt_dump(ip, data, NFPROTO_IPV6, numeric, "");
316}
317
318static void ipvs_mt4_save(const void *ip, const struct xt_entry_match *match)
319{
320	const struct xt_ipvs_mtinfo *data = (const void *)match->data;
321	ipvs_mt_dump(ip, data, NFPROTO_IPV4, true, "--");
322}
323
324static void ipvs_mt6_save(const void *ip, const struct xt_entry_match *match)
325{
326	const struct xt_ipvs_mtinfo *data = (const void *)match->data;
327	ipvs_mt_dump(ip, data, NFPROTO_IPV6, true, "--");
328}
329
330static struct xtables_match ipvs_matches_reg[] = {
331	{
332		.version       = XTABLES_VERSION,
333		.name          = "ipvs",
334		.revision      = 0,
335		.family        = NFPROTO_IPV4,
336		.size          = XT_ALIGN(sizeof(struct xt_ipvs_mtinfo)),
337		.userspacesize = XT_ALIGN(sizeof(struct xt_ipvs_mtinfo)),
338		.help          = ipvs_mt_help,
339		.parse         = ipvs_mt4_parse,
340		.final_check   = ipvs_mt_check,
341		.print         = ipvs_mt4_print,
342		.save          = ipvs_mt4_save,
343		.extra_opts    = ipvs_mt_opts,
344	},
345	{
346		.version       = XTABLES_VERSION,
347		.name          = "ipvs",
348		.revision      = 0,
349		.family        = NFPROTO_IPV6,
350		.size          = XT_ALIGN(sizeof(struct xt_ipvs_mtinfo)),
351		.userspacesize = XT_ALIGN(sizeof(struct xt_ipvs_mtinfo)),
352		.help          = ipvs_mt_help,
353		.parse         = ipvs_mt6_parse,
354		.final_check   = ipvs_mt_check,
355		.print         = ipvs_mt6_print,
356		.save          = ipvs_mt6_save,
357		.extra_opts    = ipvs_mt_opts,
358	},
359};
360
361void _init(void)
362{
363	xtables_register_matches(ipvs_matches_reg,
364				 ARRAY_SIZE(ipvs_matches_reg));
365}
366