blob: ccb78ea1f8a3e6b03f1476970d91fd61268c2b08 [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"
Harald Weltea643c3e2003-08-25 11:08:52 +000033" [!] --ctstatus [NONE|EXPECTED|SEEN_REPLY|ASSURED|CONFIRMED][,...]\n"
Marc Boucher5054e852002-01-19 10:59:12 +000034" Status(es) to match\n"
35" [!] --ctexpire time[:time] Match remaining lifetime in seconds against\n"
36" value or range of values (inclusive)\n"
Harald Welte80fe35d2002-05-29 13:08:15 +000037"\n", IPTABLES_VERSION);
Marc Boucher5054e852002-01-19 10:59:12 +000038}
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;
Harald Weltea643c3e2003-08-25 11:08:52 +0000108#ifdef IPS_CONFIRMED
109 else if (strncasecmp(status, "CONFIRMED", strlen) == 0)
110 sinfo->stausmask |= IPS_CONFIRMED;
111#endif
Marc Boucher5054e852002-01-19 10:59:12 +0000112 else
113 return 0;
114 return 1;
115}
116
117static void
118parse_statuses(const char *arg, struct ipt_conntrack_info *sinfo)
119{
120 const char *comma;
121
122 while ((comma = strchr(arg, ',')) != NULL) {
123 if (comma == arg || !parse_status(arg, comma-arg, sinfo))
124 exit_error(PARAMETER_PROBLEM, "Bad ctstatus `%s'", arg);
125 arg = comma+1;
126 }
127
128 if (strlen(arg) == 0 || !parse_status(arg, strlen(arg), sinfo))
129 exit_error(PARAMETER_PROBLEM, "Bad ctstatus `%s'", arg);
130}
131
132
133static unsigned long
134parse_expire(const char *s)
135{
136 unsigned int len;
137
138 if (string_to_number(s, 0, 0xFFFFFFFF, &len) == -1)
139 exit_error(PARAMETER_PROBLEM, "expire value invalid: `%s'\n", s);
140 else
141 return len;
142}
143
144/* If a single value is provided, min and max are both set to the value */
145static void
146parse_expires(const char *s, struct ipt_conntrack_info *sinfo)
147{
148 char *buffer;
149 char *cp;
150
151 buffer = strdup(s);
152 if ((cp = strchr(buffer, ':')) == NULL)
153 sinfo->expires_min = sinfo->expires_max = parse_expire(buffer);
154 else {
155 *cp = '\0';
156 cp++;
157
158 sinfo->expires_min = buffer[0] ? parse_expire(buffer) : 0;
159 sinfo->expires_max = cp[0] ? parse_expire(cp) : 0xFFFFFFFF;
160 }
161 free(buffer);
162
163 if (sinfo->expires_min > sinfo->expires_max)
164 exit_error(PARAMETER_PROBLEM,
165 "expire min. range value `%lu' greater than max. "
166 "range value `%lu'", sinfo->expires_min, sinfo->expires_max);
167
168}
169
170/* Function which parses command options; returns true if it
171 ate an option */
172static int
173parse(int c, char **argv, int invert, unsigned int *flags,
174 const struct ipt_entry *entry,
175 unsigned int *nfcache,
176 struct ipt_entry_match **match)
177{
178 struct ipt_conntrack_info *sinfo = (struct ipt_conntrack_info *)(*match)->data;
179 char *protocol = NULL;
180 unsigned int naddrs = 0;
181 struct in_addr *addrs = NULL;
182
183
184 switch (c) {
185 case '1':
Harald Welteb77f1da2002-03-14 11:35:58 +0000186 check_inverse(optarg, &invert, &optind, 0);
Marc Boucher5054e852002-01-19 10:59:12 +0000187
188 parse_states(argv[optind-1], sinfo);
189 if (invert) {
190 sinfo->invflags |= IPT_CONNTRACK_STATE;
191 }
192 sinfo->flags |= IPT_CONNTRACK_STATE;
193 break;
194
195 case '2':
Harald Welte3c5bd602002-03-14 19:54:34 +0000196 check_inverse(optarg, &invert, &optind, 0);
Marc Boucher5054e852002-01-19 10:59:12 +0000197
198 if(invert)
199 sinfo->invflags |= IPT_CONNTRACK_PROTO;
200
201 /* Canonicalize into lower case */
202 for (protocol = argv[optind-1]; *protocol; protocol++)
203 *protocol = tolower(*protocol);
204
205 protocol = argv[optind-1];
206 sinfo->tuple[IP_CT_DIR_ORIGINAL].dst.protonum = parse_protocol(protocol);
207
208 if (sinfo->tuple[IP_CT_DIR_ORIGINAL].dst.protonum == 0
209 && (sinfo->invflags & IPT_INV_PROTO))
210 exit_error(PARAMETER_PROBLEM,
211 "rule would never match protocol");
212
213 sinfo->flags |= IPT_CONNTRACK_PROTO;
214 break;
215
216 case '3':
Harald Welteb77f1da2002-03-14 11:35:58 +0000217 check_inverse(optarg, &invert, &optind, 9);
Marc Boucher5054e852002-01-19 10:59:12 +0000218
219 if (invert)
220 sinfo->invflags |= IPT_CONNTRACK_ORIGSRC;
221
222 parse_hostnetworkmask(argv[optind-1], &addrs,
223 &sinfo->sipmsk[IP_CT_DIR_ORIGINAL],
224 &naddrs);
225 if(naddrs > 1)
226 exit_error(PARAMETER_PROBLEM,
227 "multiple IP addresses not allowed");
228
229 if(naddrs == 1) {
230 sinfo->tuple[IP_CT_DIR_ORIGINAL].src.ip = addrs[0].s_addr;
231 }
232
233 sinfo->flags |= IPT_CONNTRACK_ORIGSRC;
234 break;
235
236 case '4':
Harald Welteb77f1da2002-03-14 11:35:58 +0000237 check_inverse(optarg, &invert, &optind, 0);
Marc Boucher5054e852002-01-19 10:59:12 +0000238
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':
Harald Welteb77f1da2002-03-14 11:35:58 +0000257 check_inverse(optarg, &invert, &optind, 0);
Marc Boucher5054e852002-01-19 10:59:12 +0000258
259 if (invert)
260 sinfo->invflags |= IPT_CONNTRACK_REPLSRC;
261
262 parse_hostnetworkmask(argv[optind-1], &addrs,
263 &sinfo->sipmsk[IP_CT_DIR_REPLY],
264 &naddrs);
265 if(naddrs > 1)
266 exit_error(PARAMETER_PROBLEM,
267 "multiple IP addresses not allowed");
268
269 if(naddrs == 1) {
270 sinfo->tuple[IP_CT_DIR_REPLY].src.ip = addrs[0].s_addr;
271 }
272
273 sinfo->flags |= IPT_CONNTRACK_REPLSRC;
274 break;
275
276 case '6':
Harald Welteb77f1da2002-03-14 11:35:58 +0000277 check_inverse(optarg, &invert, &optind, 0);
Marc Boucher5054e852002-01-19 10:59:12 +0000278
279 if (invert)
280 sinfo->invflags |= IPT_CONNTRACK_REPLDST;
281
282 parse_hostnetworkmask(argv[optind-1], &addrs,
283 &sinfo->dipmsk[IP_CT_DIR_REPLY],
284 &naddrs);
285 if(naddrs > 1)
286 exit_error(PARAMETER_PROBLEM,
287 "multiple IP addresses not allowed");
288
289 if(naddrs == 1) {
290 sinfo->tuple[IP_CT_DIR_REPLY].dst.ip = addrs[0].s_addr;
291 }
292
293 sinfo->flags |= IPT_CONNTRACK_REPLDST;
294 break;
295
296 case '7':
Harald Welteb77f1da2002-03-14 11:35:58 +0000297 check_inverse(optarg, &invert, &optind, 0);
Marc Boucher5054e852002-01-19 10:59:12 +0000298
299 parse_statuses(argv[optind-1], sinfo);
300 if (invert) {
301 sinfo->invflags |= IPT_CONNTRACK_STATUS;
302 }
303 sinfo->flags |= IPT_CONNTRACK_STATUS;
304 break;
305
306 case '8':
Harald Welteb77f1da2002-03-14 11:35:58 +0000307 check_inverse(optarg, &invert, &optind, 0);
Marc Boucher5054e852002-01-19 10:59:12 +0000308
309 parse_expires(argv[optind-1], sinfo);
310 if (invert) {
311 sinfo->invflags |= IPT_CONNTRACK_EXPIRES;
312 }
313 sinfo->flags |= IPT_CONNTRACK_EXPIRES;
314 break;
315
316 default:
317 return 0;
318 }
319
320 *flags = sinfo->flags;
321 return 1;
322}
323
324static void
325final_check(unsigned int flags)
326{
327 if (!flags)
328 exit_error(PARAMETER_PROBLEM, "You must specify one or more options");
329}
330
331static void
332print_state(unsigned int statemask)
333{
334 const char *sep = "";
335
336 if (statemask & IPT_CONNTRACK_STATE_INVALID) {
337 printf("%sINVALID", sep);
338 sep = ",";
339 }
340 if (statemask & IPT_CONNTRACK_STATE_BIT(IP_CT_NEW)) {
341 printf("%sNEW", sep);
342 sep = ",";
343 }
344 if (statemask & IPT_CONNTRACK_STATE_BIT(IP_CT_RELATED)) {
345 printf("%sRELATED", sep);
346 sep = ",";
347 }
348 if (statemask & IPT_CONNTRACK_STATE_BIT(IP_CT_ESTABLISHED)) {
349 printf("%sESTABLISHED", sep);
350 sep = ",";
351 }
352 if (statemask & IPT_CONNTRACK_STATE_SNAT) {
353 printf("%sSNAT", sep);
354 sep = ",";
355 }
356 if (statemask & IPT_CONNTRACK_STATE_DNAT) {
357 printf("%sDNAT", sep);
358 sep = ",";
359 }
360 printf(" ");
361}
362
363static void
364print_status(unsigned int statusmask)
365{
366 const char *sep = "";
367
368 if (statusmask & IPS_EXPECTED) {
369 printf("%sEXPECTED", sep);
370 sep = ",";
371 }
372 if (statusmask & IPS_SEEN_REPLY) {
373 printf("%sSEEN_REPLY", sep);
374 sep = ",";
375 }
376 if (statusmask & IPS_ASSURED) {
377 printf("%sASSURED", sep);
378 sep = ",";
379 }
Harald Weltea643c3e2003-08-25 11:08:52 +0000380#ifdef IPS_CONFIRMED
381 if (statusmask & IPS_CONFIRMED) {
382 printf("%sCONFIRMED", sep);
383 sep =",";
384 }
385#endif
Marc Boucher5054e852002-01-19 10:59:12 +0000386 if (statusmask == 0) {
387 printf("%sNONE", sep);
388 sep = ",";
389 }
390 printf(" ");
391}
392
393static void
394print_addr(struct in_addr *addr, struct in_addr *mask, int inv, int numeric)
395{
396 char buf[BUFSIZ];
397
398 if (inv)
399 fputc('!', stdout);
400
401 if (mask->s_addr == 0L && !numeric)
402 printf("%s ", "anywhere");
403 else {
404 if (numeric)
405 sprintf(buf, "%s", addr_to_dotted(addr));
406 else
407 sprintf(buf, "%s", addr_to_anyname(addr));
408 strcat(buf, mask_to_dotted(mask));
409 printf("%s ", buf);
410 }
411}
412
413/* Saves the matchinfo in parsable form to stdout. */
414static void
415matchinfo_print(const struct ipt_ip *ip, const struct ipt_entry_match *match, int numeric, const char *optpfx)
416{
417 struct ipt_conntrack_info *sinfo = (struct ipt_conntrack_info *)match->data;
418
419 if(sinfo->flags & IPT_CONNTRACK_STATE) {
420 printf("%sctstate ", optpfx);
421 if (sinfo->invflags & IPT_CONNTRACK_STATE)
Michael Schwendtdfba3ac2002-12-05 20:20:29 +0000422 printf("! ");
Marc Boucher5054e852002-01-19 10:59:12 +0000423 print_state(sinfo->statemask);
424 }
425
426 if(sinfo->flags & IPT_CONNTRACK_ORIGSRC) {
427 printf("%sctorigsrc ", optpfx);
428
429 print_addr(
430 (struct in_addr *)&sinfo->tuple[IP_CT_DIR_ORIGINAL].src.ip,
431 &sinfo->sipmsk[IP_CT_DIR_ORIGINAL],
432 sinfo->invflags & IPT_CONNTRACK_ORIGSRC,
433 numeric);
434 }
435
436 if(sinfo->flags & IPT_CONNTRACK_ORIGDST) {
437 printf("%sctorigdst ", optpfx);
438
439 print_addr(
440 (struct in_addr *)&sinfo->tuple[IP_CT_DIR_ORIGINAL].dst.ip,
441 &sinfo->dipmsk[IP_CT_DIR_ORIGINAL],
442 sinfo->invflags & IPT_CONNTRACK_ORIGDST,
443 numeric);
444 }
445
446 if(sinfo->flags & IPT_CONNTRACK_REPLSRC) {
Lutz Preßlerd0ae04e2003-03-04 14:50:50 +0000447 printf("%sctreplsrc ", optpfx);
Marc Boucher5054e852002-01-19 10:59:12 +0000448
449 print_addr(
450 (struct in_addr *)&sinfo->tuple[IP_CT_DIR_REPLY].src.ip,
451 &sinfo->sipmsk[IP_CT_DIR_REPLY],
452 sinfo->invflags & IPT_CONNTRACK_REPLSRC,
453 numeric);
454 }
455
456 if(sinfo->flags & IPT_CONNTRACK_REPLDST) {
Lutz Preßlerd0ae04e2003-03-04 14:50:50 +0000457 printf("%sctrepldst ", optpfx);
Marc Boucher5054e852002-01-19 10:59:12 +0000458
459 print_addr(
460 (struct in_addr *)&sinfo->tuple[IP_CT_DIR_REPLY].dst.ip,
461 &sinfo->dipmsk[IP_CT_DIR_REPLY],
462 sinfo->invflags & IPT_CONNTRACK_REPLDST,
463 numeric);
464 }
465
466 if(sinfo->flags & IPT_CONNTRACK_STATUS) {
467 printf("%sctstatus ", optpfx);
468 if (sinfo->invflags & IPT_CONNTRACK_STATE)
Michael Schwendtdfba3ac2002-12-05 20:20:29 +0000469 printf("! ");
Marc Boucher5054e852002-01-19 10:59:12 +0000470 print_status(sinfo->statusmask);
471 }
472
473 if(sinfo->flags & IPT_CONNTRACK_EXPIRES) {
474 printf("%sctexpire ", optpfx);
475 if (sinfo->invflags & IPT_CONNTRACK_EXPIRES)
Michael Schwendtdfba3ac2002-12-05 20:20:29 +0000476 printf("! ");
Marc Boucher5054e852002-01-19 10:59:12 +0000477
478 if (sinfo->expires_max == sinfo->expires_min)
479 printf("%lu ", sinfo->expires_min);
480 else
481 printf("%lu:%lu ", sinfo->expires_min, sinfo->expires_max);
482 }
483}
484
485/* Prints out the matchinfo. */
486static void
487print(const struct ipt_ip *ip,
488 const struct ipt_entry_match *match,
489 int numeric)
490{
491 matchinfo_print(ip, match, numeric, "");
492}
493
494/* Saves the matchinfo in parsable form to stdout. */
495static void save(const struct ipt_ip *ip, const struct ipt_entry_match *match)
496{
497 matchinfo_print(ip, match, 0, "--");
498}
499
500static
501struct iptables_match conntrack
502= { NULL,
503 "conntrack",
Harald Welte80fe35d2002-05-29 13:08:15 +0000504 IPTABLES_VERSION,
Marc Boucher5054e852002-01-19 10:59:12 +0000505 IPT_ALIGN(sizeof(struct ipt_conntrack_info)),
506 IPT_ALIGN(sizeof(struct ipt_conntrack_info)),
507 &help,
508 &init,
509 &parse,
510 &final_check,
511 &print,
512 &save,
513 opts
514};
515
516void _init(void)
517{
518 register_match(&conntrack);
519}