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 
15 enum {
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
27 static 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 
ipvs_mt_help(void)47 static 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 
ipvs_mt_parse(struct xt_option_call * cb)66 static 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 
ipvs_mt_check(struct xt_fcheck_call * cb)108 static 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 */
ipvs_mt_dump_addr(const union nf_inet_addr * addr,const union nf_inet_addr * mask,unsigned int family,bool numeric)125 static 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 
ipvs_mt_dump(const void * ip,const struct xt_ipvs_mtinfo * data,unsigned int family,bool numeric,const char * prefix)157 static 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 
ipvs_mt4_print(const void * ip,const struct xt_entry_match * match,int numeric)224 static 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 
ipvs_mt6_print(const void * ip,const struct xt_entry_match * match,int numeric)231 static 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 
ipvs_mt4_save(const void * ip,const struct xt_entry_match * match)238 static 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 
ipvs_mt6_save(const void * ip,const struct xt_entry_match * match)244 static 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 
250 static 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 
_init(void)281 void _init(void)
282 {
283 	xtables_register_matches(ipvs_matches_reg,
284 				 ARRAY_SIZE(ipvs_matches_reg));
285 }
286