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 <stdbool.h>
9#include <stdio.h>
10#include <string.h>
11#include <xtables.h>
12#include <linux/ip_vs.h>
13#include <linux/netfilter/xt_ipvs.h>
14
15enum {
16	/* For xt_ipvs: make sure this matches up with %XT_IPVS_*'s order */
17	O_IPVS = 0,
18	O_VPROTO,
19	O_VADDR,
20	O_VPORT,
21	O_VDIR,
22	O_VMETHOD,
23	O_VPORTCTL,
24};
25
26#define s struct xt_ipvs_mtinfo
27static const struct xt_option_entry ipvs_mt_opts[] = {
28	{.name = "ipvs", .id = O_IPVS, .type = XTTYPE_NONE,
29	 .flags = XTOPT_INVERT},
30	{.name = "vproto", .id = O_VPROTO, .type = XTTYPE_STRING,
31	 .flags = XTOPT_INVERT | XTOPT_PUT, XTOPT_POINTER(s, l4proto)},
32	{.name = "vaddr", .id = O_VADDR, .type = XTTYPE_HOSTMASK,
33	 .flags = XTOPT_INVERT},
34	{.name = "vport", .id = O_VPORT, .type = XTTYPE_PORT,
35	 .flags = XTOPT_NBO | XTOPT_INVERT | XTOPT_PUT,
36	 XTOPT_POINTER(s, vport)},
37	{.name = "vdir", .id = O_VDIR, .type = XTTYPE_STRING},
38	{.name = "vmethod", .id = O_VMETHOD, .type = XTTYPE_STRING,
39	 .flags = XTOPT_INVERT},
40	{.name = "vportctl", .id = O_VPORTCTL, .type = XTTYPE_PORT,
41	 .flags = XTOPT_NBO | XTOPT_INVERT | XTOPT_PUT,
42	 XTOPT_POINTER(s, vportctl)},
43	XTOPT_TABLEEND,
44};
45#undef s
46
47static void ipvs_mt_help(void)
48{
49	printf(
50"IPVS match options:\n"
51"[!] --ipvs                      packet belongs to an IPVS connection\n"
52"\n"
53"Any of the following options implies --ipvs (even negated)\n"
54"[!] --vproto protocol           VIP protocol to match; by number or name,\n"
55"                                e.g. \"tcp\"\n"
56"[!] --vaddr address[/mask]      VIP address to match\n"
57"[!] --vport port                VIP port to match; by number or name,\n"
58"                                e.g. \"http\"\n"
59"    --vdir {ORIGINAL|REPLY}     flow direction of packet\n"
60"[!] --vmethod {GATE|IPIP|MASQ}  IPVS forwarding method used\n"
61"[!] --vportctl port             VIP port of the controlling connection to\n"
62"                                match, e.g. 21 for FTP\n"
63		);
64}
65
66static void ipvs_mt_parse(struct xt_option_call *cb)
67{
68	struct xt_ipvs_mtinfo *data = cb->data;
69
70	xtables_option_parse(cb);
71	switch (cb->entry->id) {
72	case O_VPROTO:
73		data->l4proto = cb->val.protocol;
74		break;
75	case O_VADDR:
76		memcpy(&data->vaddr, &cb->val.haddr, sizeof(cb->val.haddr));
77		memcpy(&data->vmask, &cb->val.hmask, sizeof(cb->val.hmask));
78		break;
79	case O_VDIR:
80		if (strcasecmp(cb->arg, "ORIGINAL") == 0) {
81			data->bitmask |= XT_IPVS_DIR;
82			data->invert   &= ~XT_IPVS_DIR;
83		} else if (strcasecmp(cb->arg, "REPLY") == 0) {
84			data->bitmask |= XT_IPVS_DIR;
85			data->invert  |= XT_IPVS_DIR;
86		} else {
87			xtables_param_act(XTF_BAD_VALUE,
88					  "ipvs", "--vdir", cb->arg);
89		}
90		break;
91	case O_VMETHOD:
92		if (strcasecmp(cb->arg, "GATE") == 0)
93			data->fwd_method = IP_VS_CONN_F_DROUTE;
94		else if (strcasecmp(cb->arg, "IPIP") == 0)
95			data->fwd_method = IP_VS_CONN_F_TUNNEL;
96		else if (strcasecmp(cb->arg, "MASQ") == 0)
97			data->fwd_method = IP_VS_CONN_F_MASQ;
98		else
99			xtables_param_act(XTF_BAD_VALUE,
100					  "ipvs", "--vmethod", cb->arg);
101		break;
102	}
103	data->bitmask |= 1 << cb->entry->id;
104	if (cb->invert)
105		data->invert |= 1 << cb->entry->id;
106}
107
108static void ipvs_mt_check(struct xt_fcheck_call *cb)
109{
110	struct xt_ipvs_mtinfo *info = cb->data;
111
112	if (cb->xflags == 0)
113		xtables_error(PARAMETER_PROBLEM,
114			      "IPVS: At least one option is required");
115	if (info->bitmask & XT_IPVS_ONCE_MASK) {
116		if (info->invert & XT_IPVS_IPVS_PROPERTY)
117			xtables_error(PARAMETER_PROBLEM,
118				      "! --ipvs cannot be together with"
119				      " other options");
120		info->bitmask |= XT_IPVS_IPVS_PROPERTY;
121	}
122}
123
124/* Shamelessly copied from libxt_conntrack.c */
125static void ipvs_mt_dump_addr(const union nf_inet_addr *addr,
126			      const union nf_inet_addr *mask,
127			      unsigned int family, bool numeric)
128{
129	char buf[BUFSIZ];
130
131	if (family == NFPROTO_IPV4) {
132		if (!numeric && addr->ip == 0) {
133			printf(" anywhere");
134			return;
135		}
136		if (numeric)
137			strcpy(buf, xtables_ipaddr_to_numeric(&addr->in));
138		else
139			strcpy(buf, xtables_ipaddr_to_anyname(&addr->in));
140		strcat(buf, xtables_ipmask_to_numeric(&mask->in));
141		printf(" %s", buf);
142	} else if (family == NFPROTO_IPV6) {
143		if (!numeric && addr->ip6[0] == 0 && addr->ip6[1] == 0 &&
144		    addr->ip6[2] == 0 && addr->ip6[3] == 0) {
145			printf(" anywhere");
146			return;
147		}
148		if (numeric)
149			strcpy(buf, xtables_ip6addr_to_numeric(&addr->in6));
150		else
151			strcpy(buf, xtables_ip6addr_to_anyname(&addr->in6));
152		strcat(buf, xtables_ip6mask_to_numeric(&mask->in6));
153		printf(" %s", buf);
154	}
155}
156
157static void ipvs_mt_dump(const void *ip, const struct xt_ipvs_mtinfo *data,
158			 unsigned int family, bool numeric, const char *prefix)
159{
160	if (data->bitmask == XT_IPVS_IPVS_PROPERTY) {
161		if (data->invert & XT_IPVS_IPVS_PROPERTY)
162			printf(" !");
163		printf(" %sipvs", prefix);
164	}
165
166	if (data->bitmask & XT_IPVS_PROTO) {
167		if (data->invert & XT_IPVS_PROTO)
168			printf(" !");
169		printf(" %sproto %u", prefix, data->l4proto);
170	}
171
172	if (data->bitmask & XT_IPVS_VADDR) {
173		if (data->invert & XT_IPVS_VADDR)
174			printf(" !");
175
176		printf(" %svaddr", prefix);
177		ipvs_mt_dump_addr(&data->vaddr, &data->vmask, family, numeric);
178	}
179
180	if (data->bitmask & XT_IPVS_VPORT) {
181		if (data->invert & XT_IPVS_VPORT)
182			printf(" !");
183
184		printf(" %svport %u", prefix, ntohs(data->vport));
185	}
186
187	if (data->bitmask & XT_IPVS_DIR) {
188		if (data->invert & XT_IPVS_DIR)
189			printf(" %svdir REPLY", prefix);
190		else
191			printf(" %svdir ORIGINAL", prefix);
192	}
193
194	if (data->bitmask & XT_IPVS_METHOD) {
195		if (data->invert & XT_IPVS_METHOD)
196			printf(" !");
197
198		printf(" %svmethod", prefix);
199		switch (data->fwd_method) {
200		case IP_VS_CONN_F_DROUTE:
201			printf(" GATE");
202			break;
203		case IP_VS_CONN_F_TUNNEL:
204			printf(" IPIP");
205			break;
206		case IP_VS_CONN_F_MASQ:
207			printf(" MASQ");
208			break;
209		default:
210			/* Hu? */
211			printf(" UNKNOWN");
212			break;
213		}
214	}
215
216	if (data->bitmask & XT_IPVS_VPORTCTL) {
217		if (data->invert & XT_IPVS_VPORTCTL)
218			printf(" !");
219
220		printf(" %svportctl %u", prefix, ntohs(data->vportctl));
221	}
222}
223
224static void ipvs_mt4_print(const void *ip, const struct xt_entry_match *match,
225			   int numeric)
226{
227	const struct xt_ipvs_mtinfo *data = (const void *)match->data;
228	ipvs_mt_dump(ip, data, NFPROTO_IPV4, numeric, "");
229}
230
231static void ipvs_mt6_print(const void *ip, const struct xt_entry_match *match,
232			   int numeric)
233{
234	const struct xt_ipvs_mtinfo *data = (const void *)match->data;
235	ipvs_mt_dump(ip, data, NFPROTO_IPV6, numeric, "");
236}
237
238static void ipvs_mt4_save(const void *ip, const struct xt_entry_match *match)
239{
240	const struct xt_ipvs_mtinfo *data = (const void *)match->data;
241	ipvs_mt_dump(ip, data, NFPROTO_IPV4, true, "--");
242}
243
244static void ipvs_mt6_save(const void *ip, const struct xt_entry_match *match)
245{
246	const struct xt_ipvs_mtinfo *data = (const void *)match->data;
247	ipvs_mt_dump(ip, data, NFPROTO_IPV6, true, "--");
248}
249
250static struct xtables_match ipvs_matches_reg[] = {
251	{
252		.version       = XTABLES_VERSION,
253		.name          = "ipvs",
254		.revision      = 0,
255		.family        = NFPROTO_IPV4,
256		.size          = XT_ALIGN(sizeof(struct xt_ipvs_mtinfo)),
257		.userspacesize = XT_ALIGN(sizeof(struct xt_ipvs_mtinfo)),
258		.help          = ipvs_mt_help,
259		.x6_parse      = ipvs_mt_parse,
260		.x6_fcheck     = ipvs_mt_check,
261		.print         = ipvs_mt4_print,
262		.save          = ipvs_mt4_save,
263		.x6_options    = ipvs_mt_opts,
264	},
265	{
266		.version       = XTABLES_VERSION,
267		.name          = "ipvs",
268		.revision      = 0,
269		.family        = NFPROTO_IPV6,
270		.size          = XT_ALIGN(sizeof(struct xt_ipvs_mtinfo)),
271		.userspacesize = XT_ALIGN(sizeof(struct xt_ipvs_mtinfo)),
272		.help          = ipvs_mt_help,
273		.x6_parse      = ipvs_mt_parse,
274		.x6_fcheck     = ipvs_mt_check,
275		.print         = ipvs_mt6_print,
276		.save          = ipvs_mt6_save,
277		.x6_options    = ipvs_mt_opts,
278	},
279};
280
281void _init(void)
282{
283	xtables_register_matches(ipvs_matches_reg,
284				 ARRAY_SIZE(ipvs_matches_reg));
285}
286