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