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