blob: 9b63939172cfa1bbbde71ec32aa669357f8818fe [file] [log] [blame]
Marc Boucher5054e852002-01-19 10:59:12 +00001/* Shared library add-on to iptables for conntrack matching support.
2 * GPL (C) 2001 Marc Boucher (marc@mbsi.ca).
3 */
4
5#include <stdio.h>
6#include <netdb.h>
7#include <string.h>
8#include <stdlib.h>
9#include <getopt.h>
10#include <ctype.h>
11#include <iptables.h>
12#include <linux/netfilter_ipv4/ip_conntrack.h>
13#include <linux/netfilter_ipv4/ip_conntrack_tuple.h>
14#include <linux/netfilter_ipv4/ipt_conntrack.h>
15
16/* Function which prints out usage message. */
17static void
18help(void)
19{
20 printf(
21"conntrack match v%s options:\n"
22" [!] --ctstate [INVALID|ESTABLISHED|NEW|RELATED|SNAT|DNAT][,...]\n"
23" State(s) to match\n"
24" [!] --ctproto proto Protocol to match; by number or name, eg. `tcp'\n"
25" --ctorigsrc [!] address[/mask]\n"
26" Original source specification\n"
27" --ctorigdst [!] address[/mask]\n"
28" Original destination specification\n"
29" --ctreplsrc [!] address[/mask]\n"
30" Reply source specification\n"
31" --ctrepldst [!] address[/mask]\n"
32" Reply destination specification\n"
33" [!] --ctstatus [NONE|EXPECTED|SEEN_REPLY|ASSURED][,...]\n"
34" Status(es) to match\n"
35" [!] --ctexpire time[:time] Match remaining lifetime in seconds against\n"
36" value or range of values (inclusive)\n"
37"\n", NETFILTER_VERSION);
38}
39
40
41
42static struct option opts[] = {
43 { "ctstate", 1, 0, '1' },
44 { "ctproto", 1, 0, '2' },
45 { "ctorigsrc", 1, 0, '3' },
46 { "ctorigdst", 1, 0, '4' },
47 { "ctreplsrc", 1, 0, '5' },
48 { "ctrepldst", 1, 0, '6' },
49 { "ctstatus", 1, 0, '7' },
50 { "ctexpire", 1, 0, '8' },
51 {0}
52};
53
54/* Initialize the match. */
55static void
56init(struct ipt_entry_match *m, unsigned int *nfcache)
57{
58 /* Can't cache this */
59 *nfcache |= NFC_UNKNOWN;
60}
61
62static int
63parse_state(const char *state, size_t strlen, struct ipt_conntrack_info *sinfo)
64{
65 if (strncasecmp(state, "INVALID", strlen) == 0)
66 sinfo->statemask |= IPT_CONNTRACK_STATE_INVALID;
67 else if (strncasecmp(state, "NEW", strlen) == 0)
68 sinfo->statemask |= IPT_CONNTRACK_STATE_BIT(IP_CT_NEW);
69 else if (strncasecmp(state, "ESTABLISHED", strlen) == 0)
70 sinfo->statemask |= IPT_CONNTRACK_STATE_BIT(IP_CT_ESTABLISHED);
71 else if (strncasecmp(state, "RELATED", strlen) == 0)
72 sinfo->statemask |= IPT_CONNTRACK_STATE_BIT(IP_CT_RELATED);
73 else if (strncasecmp(state, "SNAT", strlen) == 0)
74 sinfo->statemask |= IPT_CONNTRACK_STATE_SNAT;
75 else if (strncasecmp(state, "DNAT", strlen) == 0)
76 sinfo->statemask |= IPT_CONNTRACK_STATE_DNAT;
77 else
78 return 0;
79 return 1;
80}
81
82static void
83parse_states(const char *arg, struct ipt_conntrack_info *sinfo)
84{
85 const char *comma;
86
87 while ((comma = strchr(arg, ',')) != NULL) {
88 if (comma == arg || !parse_state(arg, comma-arg, sinfo))
89 exit_error(PARAMETER_PROBLEM, "Bad ctstate `%s'", arg);
90 arg = comma+1;
91 }
92
93 if (strlen(arg) == 0 || !parse_state(arg, strlen(arg), sinfo))
94 exit_error(PARAMETER_PROBLEM, "Bad ctstate `%s'", arg);
95}
96
97static int
98parse_status(const char *status, size_t strlen, struct ipt_conntrack_info *sinfo)
99{
100 if (strncasecmp(status, "NONE", strlen) == 0)
101 sinfo->statusmask |= 0;
102 else if (strncasecmp(status, "EXPECTED", strlen) == 0)
103 sinfo->statusmask |= IPS_EXPECTED;
104 else if (strncasecmp(status, "SEEN_REPLY", strlen) == 0)
105 sinfo->statusmask |= IPS_SEEN_REPLY;
106 else if (strncasecmp(status, "ASSURED", strlen) == 0)
107 sinfo->statusmask |= IPS_ASSURED;
108 else
109 return 0;
110 return 1;
111}
112
113static void
114parse_statuses(const char *arg, struct ipt_conntrack_info *sinfo)
115{
116 const char *comma;
117
118 while ((comma = strchr(arg, ',')) != NULL) {
119 if (comma == arg || !parse_status(arg, comma-arg, sinfo))
120 exit_error(PARAMETER_PROBLEM, "Bad ctstatus `%s'", arg);
121 arg = comma+1;
122 }
123
124 if (strlen(arg) == 0 || !parse_status(arg, strlen(arg), sinfo))
125 exit_error(PARAMETER_PROBLEM, "Bad ctstatus `%s'", arg);
126}
127
128
129static unsigned long
130parse_expire(const char *s)
131{
132 unsigned int len;
133
134 if (string_to_number(s, 0, 0xFFFFFFFF, &len) == -1)
135 exit_error(PARAMETER_PROBLEM, "expire value invalid: `%s'\n", s);
136 else
137 return len;
138}
139
140/* If a single value is provided, min and max are both set to the value */
141static void
142parse_expires(const char *s, struct ipt_conntrack_info *sinfo)
143{
144 char *buffer;
145 char *cp;
146
147 buffer = strdup(s);
148 if ((cp = strchr(buffer, ':')) == NULL)
149 sinfo->expires_min = sinfo->expires_max = parse_expire(buffer);
150 else {
151 *cp = '\0';
152 cp++;
153
154 sinfo->expires_min = buffer[0] ? parse_expire(buffer) : 0;
155 sinfo->expires_max = cp[0] ? parse_expire(cp) : 0xFFFFFFFF;
156 }
157 free(buffer);
158
159 if (sinfo->expires_min > sinfo->expires_max)
160 exit_error(PARAMETER_PROBLEM,
161 "expire min. range value `%lu' greater than max. "
162 "range value `%lu'", sinfo->expires_min, sinfo->expires_max);
163
164}
165
166/* Function which parses command options; returns true if it
167 ate an option */
168static int
169parse(int c, char **argv, int invert, unsigned int *flags,
170 const struct ipt_entry *entry,
171 unsigned int *nfcache,
172 struct ipt_entry_match **match)
173{
174 struct ipt_conntrack_info *sinfo = (struct ipt_conntrack_info *)(*match)->data;
175 char *protocol = NULL;
176 unsigned int naddrs = 0;
177 struct in_addr *addrs = NULL;
178
179
180 switch (c) {
181 case '1':
182 if (check_inverse(optarg, &invert))
183 optind++;
184
185 parse_states(argv[optind-1], sinfo);
186 if (invert) {
187 sinfo->invflags |= IPT_CONNTRACK_STATE;
188 }
189 sinfo->flags |= IPT_CONNTRACK_STATE;
190 break;
191
192 case '2':
193 if (check_inverse(optarg, &invert))
194 optind++;
195
196 if(invert)
197 sinfo->invflags |= IPT_CONNTRACK_PROTO;
198
199 /* Canonicalize into lower case */
200 for (protocol = argv[optind-1]; *protocol; protocol++)
201 *protocol = tolower(*protocol);
202
203 protocol = argv[optind-1];
204 sinfo->tuple[IP_CT_DIR_ORIGINAL].dst.protonum = parse_protocol(protocol);
205
206 if (sinfo->tuple[IP_CT_DIR_ORIGINAL].dst.protonum == 0
207 && (sinfo->invflags & IPT_INV_PROTO))
208 exit_error(PARAMETER_PROBLEM,
209 "rule would never match protocol");
210
211 sinfo->flags |= IPT_CONNTRACK_PROTO;
212 break;
213
214 case '3':
215 if (check_inverse(optarg, &invert))
216 optind++;
217
218 if (invert)
219 sinfo->invflags |= IPT_CONNTRACK_ORIGSRC;
220
221 parse_hostnetworkmask(argv[optind-1], &addrs,
222 &sinfo->sipmsk[IP_CT_DIR_ORIGINAL],
223 &naddrs);
224 if(naddrs > 1)
225 exit_error(PARAMETER_PROBLEM,
226 "multiple IP addresses not allowed");
227
228 if(naddrs == 1) {
229 sinfo->tuple[IP_CT_DIR_ORIGINAL].src.ip = addrs[0].s_addr;
230 }
231
232 sinfo->flags |= IPT_CONNTRACK_ORIGSRC;
233 break;
234
235 case '4':
236 if (check_inverse(optarg, &invert))
237 optind++;
238
239 if (invert)
240 sinfo->invflags |= IPT_CONNTRACK_ORIGDST;
241
242 parse_hostnetworkmask(argv[optind-1], &addrs,
243 &sinfo->dipmsk[IP_CT_DIR_ORIGINAL],
244 &naddrs);
245 if(naddrs > 1)
246 exit_error(PARAMETER_PROBLEM,
247 "multiple IP addresses not allowed");
248
249 if(naddrs == 1) {
250 sinfo->tuple[IP_CT_DIR_ORIGINAL].dst.ip = addrs[0].s_addr;
251 }
252
253 sinfo->flags |= IPT_CONNTRACK_ORIGDST;
254 break;
255
256 case '5':
257 if (check_inverse(optarg, &invert))
258 optind++;
259
260 if (invert)
261 sinfo->invflags |= IPT_CONNTRACK_REPLSRC;
262
263 parse_hostnetworkmask(argv[optind-1], &addrs,
264 &sinfo->sipmsk[IP_CT_DIR_REPLY],
265 &naddrs);
266 if(naddrs > 1)
267 exit_error(PARAMETER_PROBLEM,
268 "multiple IP addresses not allowed");
269
270 if(naddrs == 1) {
271 sinfo->tuple[IP_CT_DIR_REPLY].src.ip = addrs[0].s_addr;
272 }
273
274 sinfo->flags |= IPT_CONNTRACK_REPLSRC;
275 break;
276
277 case '6':
278 if (check_inverse(optarg, &invert))
279 optind++;
280
281 if (invert)
282 sinfo->invflags |= IPT_CONNTRACK_REPLDST;
283
284 parse_hostnetworkmask(argv[optind-1], &addrs,
285 &sinfo->dipmsk[IP_CT_DIR_REPLY],
286 &naddrs);
287 if(naddrs > 1)
288 exit_error(PARAMETER_PROBLEM,
289 "multiple IP addresses not allowed");
290
291 if(naddrs == 1) {
292 sinfo->tuple[IP_CT_DIR_REPLY].dst.ip = addrs[0].s_addr;
293 }
294
295 sinfo->flags |= IPT_CONNTRACK_REPLDST;
296 break;
297
298 case '7':
299 if (check_inverse(optarg, &invert))
300 optind++;
301
302 parse_statuses(argv[optind-1], sinfo);
303 if (invert) {
304 sinfo->invflags |= IPT_CONNTRACK_STATUS;
305 }
306 sinfo->flags |= IPT_CONNTRACK_STATUS;
307 break;
308
309 case '8':
310 if (check_inverse(optarg, &invert))
311 optind++;
312
313 parse_expires(argv[optind-1], sinfo);
314 if (invert) {
315 sinfo->invflags |= IPT_CONNTRACK_EXPIRES;
316 }
317 sinfo->flags |= IPT_CONNTRACK_EXPIRES;
318 break;
319
320 default:
321 return 0;
322 }
323
324 *flags = sinfo->flags;
325 return 1;
326}
327
328static void
329final_check(unsigned int flags)
330{
331 if (!flags)
332 exit_error(PARAMETER_PROBLEM, "You must specify one or more options");
333}
334
335static void
336print_state(unsigned int statemask)
337{
338 const char *sep = "";
339
340 if (statemask & IPT_CONNTRACK_STATE_INVALID) {
341 printf("%sINVALID", sep);
342 sep = ",";
343 }
344 if (statemask & IPT_CONNTRACK_STATE_BIT(IP_CT_NEW)) {
345 printf("%sNEW", sep);
346 sep = ",";
347 }
348 if (statemask & IPT_CONNTRACK_STATE_BIT(IP_CT_RELATED)) {
349 printf("%sRELATED", sep);
350 sep = ",";
351 }
352 if (statemask & IPT_CONNTRACK_STATE_BIT(IP_CT_ESTABLISHED)) {
353 printf("%sESTABLISHED", sep);
354 sep = ",";
355 }
356 if (statemask & IPT_CONNTRACK_STATE_SNAT) {
357 printf("%sSNAT", sep);
358 sep = ",";
359 }
360 if (statemask & IPT_CONNTRACK_STATE_DNAT) {
361 printf("%sDNAT", sep);
362 sep = ",";
363 }
364 printf(" ");
365}
366
367static void
368print_status(unsigned int statusmask)
369{
370 const char *sep = "";
371
372 if (statusmask & IPS_EXPECTED) {
373 printf("%sEXPECTED", sep);
374 sep = ",";
375 }
376 if (statusmask & IPS_SEEN_REPLY) {
377 printf("%sSEEN_REPLY", sep);
378 sep = ",";
379 }
380 if (statusmask & IPS_ASSURED) {
381 printf("%sASSURED", sep);
382 sep = ",";
383 }
384 if (statusmask == 0) {
385 printf("%sNONE", sep);
386 sep = ",";
387 }
388 printf(" ");
389}
390
391static void
392print_addr(struct in_addr *addr, struct in_addr *mask, int inv, int numeric)
393{
394 char buf[BUFSIZ];
395
396 if (inv)
397 fputc('!', stdout);
398
399 if (mask->s_addr == 0L && !numeric)
400 printf("%s ", "anywhere");
401 else {
402 if (numeric)
403 sprintf(buf, "%s", addr_to_dotted(addr));
404 else
405 sprintf(buf, "%s", addr_to_anyname(addr));
406 strcat(buf, mask_to_dotted(mask));
407 printf("%s ", buf);
408 }
409}
410
411/* Saves the matchinfo in parsable form to stdout. */
412static void
413matchinfo_print(const struct ipt_ip *ip, const struct ipt_entry_match *match, int numeric, const char *optpfx)
414{
415 struct ipt_conntrack_info *sinfo = (struct ipt_conntrack_info *)match->data;
416
417 if(sinfo->flags & IPT_CONNTRACK_STATE) {
418 printf("%sctstate ", optpfx);
419 if (sinfo->invflags & IPT_CONNTRACK_STATE)
420 fputc('!', stdout);
421 print_state(sinfo->statemask);
422 }
423
424 if(sinfo->flags & IPT_CONNTRACK_ORIGSRC) {
425 printf("%sctorigsrc ", optpfx);
426
427 print_addr(
428 (struct in_addr *)&sinfo->tuple[IP_CT_DIR_ORIGINAL].src.ip,
429 &sinfo->sipmsk[IP_CT_DIR_ORIGINAL],
430 sinfo->invflags & IPT_CONNTRACK_ORIGSRC,
431 numeric);
432 }
433
434 if(sinfo->flags & IPT_CONNTRACK_ORIGDST) {
435 printf("%sctorigdst ", optpfx);
436
437 print_addr(
438 (struct in_addr *)&sinfo->tuple[IP_CT_DIR_ORIGINAL].dst.ip,
439 &sinfo->dipmsk[IP_CT_DIR_ORIGINAL],
440 sinfo->invflags & IPT_CONNTRACK_ORIGDST,
441 numeric);
442 }
443
444 if(sinfo->flags & IPT_CONNTRACK_REPLSRC) {
445 printf("%sctorigsrc ", optpfx);
446
447 print_addr(
448 (struct in_addr *)&sinfo->tuple[IP_CT_DIR_REPLY].src.ip,
449 &sinfo->sipmsk[IP_CT_DIR_REPLY],
450 sinfo->invflags & IPT_CONNTRACK_REPLSRC,
451 numeric);
452 }
453
454 if(sinfo->flags & IPT_CONNTRACK_REPLDST) {
455 printf("%sctorigdst ", optpfx);
456
457 print_addr(
458 (struct in_addr *)&sinfo->tuple[IP_CT_DIR_REPLY].dst.ip,
459 &sinfo->dipmsk[IP_CT_DIR_REPLY],
460 sinfo->invflags & IPT_CONNTRACK_REPLDST,
461 numeric);
462 }
463
464 if(sinfo->flags & IPT_CONNTRACK_STATUS) {
465 printf("%sctstatus ", optpfx);
466 if (sinfo->invflags & IPT_CONNTRACK_STATE)
467 fputc('!', stdout);
468 print_status(sinfo->statusmask);
469 }
470
471 if(sinfo->flags & IPT_CONNTRACK_EXPIRES) {
472 printf("%sctexpire ", optpfx);
473 if (sinfo->invflags & IPT_CONNTRACK_EXPIRES)
474 fputc('!', stdout);
475
476 if (sinfo->expires_max == sinfo->expires_min)
477 printf("%lu ", sinfo->expires_min);
478 else
479 printf("%lu:%lu ", sinfo->expires_min, sinfo->expires_max);
480 }
481}
482
483/* Prints out the matchinfo. */
484static void
485print(const struct ipt_ip *ip,
486 const struct ipt_entry_match *match,
487 int numeric)
488{
489 matchinfo_print(ip, match, numeric, "");
490}
491
492/* Saves the matchinfo in parsable form to stdout. */
493static void save(const struct ipt_ip *ip, const struct ipt_entry_match *match)
494{
495 matchinfo_print(ip, match, 0, "--");
496}
497
498static
499struct iptables_match conntrack
500= { NULL,
501 "conntrack",
502 NETFILTER_VERSION,
503 IPT_ALIGN(sizeof(struct ipt_conntrack_info)),
504 IPT_ALIGN(sizeof(struct ipt_conntrack_info)),
505 &help,
506 &init,
507 &parse,
508 &final_check,
509 &print,
510 &save,
511 opts
512};
513
514void _init(void)
515{
516 register_match(&conntrack);
517}