blob: 15d42649b83375ce0b200bd729386539a7dc376f [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>
Patrick McHardy40d54752007-04-18 07:00:36 +000012#include <linux/netfilter/nf_conntrack_common.h>
Martin Josefsson1da399c2004-05-26 15:50:57 +000013/* For 64bit kernel / 32bit userspace */
14#include "../include/linux/netfilter_ipv4/ipt_conntrack.h"
Marc Boucher5054e852002-01-19 10:59:12 +000015
Harald Welte4dc734c2003-10-07 18:55:13 +000016#ifndef IPT_CONNTRACK_STATE_UNTRACKED
17#define IPT_CONNTRACK_STATE_UNTRACKED (1 << (IP_CT_NUMBER + 3))
18#endif
19
Marc Boucher5054e852002-01-19 10:59:12 +000020/* Function which prints out usage message. */
21static void
22help(void)
23{
24 printf(
25"conntrack match v%s options:\n"
Harald Welte4dc734c2003-10-07 18:55:13 +000026" [!] --ctstate [INVALID|ESTABLISHED|NEW|RELATED|UNTRACKED|SNAT|DNAT][,...]\n"
Marc Boucher5054e852002-01-19 10:59:12 +000027" State(s) to match\n"
28" [!] --ctproto proto Protocol to match; by number or name, eg. `tcp'\n"
29" --ctorigsrc [!] address[/mask]\n"
30" Original source specification\n"
31" --ctorigdst [!] address[/mask]\n"
32" Original destination specification\n"
33" --ctreplsrc [!] address[/mask]\n"
34" Reply source specification\n"
35" --ctrepldst [!] address[/mask]\n"
36" Reply destination specification\n"
Harald Weltea643c3e2003-08-25 11:08:52 +000037" [!] --ctstatus [NONE|EXPECTED|SEEN_REPLY|ASSURED|CONFIRMED][,...]\n"
Marc Boucher5054e852002-01-19 10:59:12 +000038" Status(es) to match\n"
39" [!] --ctexpire time[:time] Match remaining lifetime in seconds against\n"
40" value or range of values (inclusive)\n"
Harald Welte80fe35d2002-05-29 13:08:15 +000041"\n", IPTABLES_VERSION);
Marc Boucher5054e852002-01-19 10:59:12 +000042}
43
44
45
46static struct option opts[] = {
47 { "ctstate", 1, 0, '1' },
48 { "ctproto", 1, 0, '2' },
49 { "ctorigsrc", 1, 0, '3' },
50 { "ctorigdst", 1, 0, '4' },
51 { "ctreplsrc", 1, 0, '5' },
52 { "ctrepldst", 1, 0, '6' },
53 { "ctstatus", 1, 0, '7' },
54 { "ctexpire", 1, 0, '8' },
55 {0}
56};
57
Marc Boucher5054e852002-01-19 10:59:12 +000058static int
59parse_state(const char *state, size_t strlen, struct ipt_conntrack_info *sinfo)
60{
61 if (strncasecmp(state, "INVALID", strlen) == 0)
62 sinfo->statemask |= IPT_CONNTRACK_STATE_INVALID;
63 else if (strncasecmp(state, "NEW", strlen) == 0)
64 sinfo->statemask |= IPT_CONNTRACK_STATE_BIT(IP_CT_NEW);
65 else if (strncasecmp(state, "ESTABLISHED", strlen) == 0)
66 sinfo->statemask |= IPT_CONNTRACK_STATE_BIT(IP_CT_ESTABLISHED);
67 else if (strncasecmp(state, "RELATED", strlen) == 0)
68 sinfo->statemask |= IPT_CONNTRACK_STATE_BIT(IP_CT_RELATED);
Harald Welte4dc734c2003-10-07 18:55:13 +000069 else if (strncasecmp(state, "UNTRACKED", strlen) == 0)
70 sinfo->statemask |= IPT_CONNTRACK_STATE_UNTRACKED;
Marc Boucher5054e852002-01-19 10:59:12 +000071 else if (strncasecmp(state, "SNAT", strlen) == 0)
72 sinfo->statemask |= IPT_CONNTRACK_STATE_SNAT;
73 else if (strncasecmp(state, "DNAT", strlen) == 0)
74 sinfo->statemask |= IPT_CONNTRACK_STATE_DNAT;
75 else
76 return 0;
77 return 1;
78}
79
80static void
81parse_states(const char *arg, struct ipt_conntrack_info *sinfo)
82{
83 const char *comma;
84
85 while ((comma = strchr(arg, ',')) != NULL) {
86 if (comma == arg || !parse_state(arg, comma-arg, sinfo))
87 exit_error(PARAMETER_PROBLEM, "Bad ctstate `%s'", arg);
88 arg = comma+1;
89 }
90
91 if (strlen(arg) == 0 || !parse_state(arg, strlen(arg), sinfo))
92 exit_error(PARAMETER_PROBLEM, "Bad ctstate `%s'", arg);
93}
94
95static int
96parse_status(const char *status, size_t strlen, struct ipt_conntrack_info *sinfo)
97{
98 if (strncasecmp(status, "NONE", strlen) == 0)
99 sinfo->statusmask |= 0;
100 else if (strncasecmp(status, "EXPECTED", strlen) == 0)
101 sinfo->statusmask |= IPS_EXPECTED;
102 else if (strncasecmp(status, "SEEN_REPLY", strlen) == 0)
103 sinfo->statusmask |= IPS_SEEN_REPLY;
104 else if (strncasecmp(status, "ASSURED", strlen) == 0)
105 sinfo->statusmask |= IPS_ASSURED;
Harald Weltea643c3e2003-08-25 11:08:52 +0000106#ifdef IPS_CONFIRMED
107 else if (strncasecmp(status, "CONFIRMED", strlen) == 0)
108 sinfo->stausmask |= IPS_CONFIRMED;
109#endif
Marc Boucher5054e852002-01-19 10:59:12 +0000110 else
111 return 0;
112 return 1;
113}
114
115static void
116parse_statuses(const char *arg, struct ipt_conntrack_info *sinfo)
117{
118 const char *comma;
119
120 while ((comma = strchr(arg, ',')) != NULL) {
121 if (comma == arg || !parse_status(arg, comma-arg, sinfo))
122 exit_error(PARAMETER_PROBLEM, "Bad ctstatus `%s'", arg);
123 arg = comma+1;
124 }
125
126 if (strlen(arg) == 0 || !parse_status(arg, strlen(arg), sinfo))
127 exit_error(PARAMETER_PROBLEM, "Bad ctstatus `%s'", arg);
128}
129
Marc Boucher5054e852002-01-19 10:59:12 +0000130static unsigned long
131parse_expire(const char *s)
132{
133 unsigned int len;
134
Martin Josefsson1da399c2004-05-26 15:50:57 +0000135 if (string_to_number(s, 0, 0, &len) == -1)
Marc Boucher5054e852002-01-19 10:59:12 +0000136 exit_error(PARAMETER_PROBLEM, "expire value invalid: `%s'\n", s);
137 else
138 return len;
139}
140
141/* If a single value is provided, min and max are both set to the value */
142static void
143parse_expires(const char *s, struct ipt_conntrack_info *sinfo)
144{
145 char *buffer;
146 char *cp;
147
148 buffer = strdup(s);
149 if ((cp = strchr(buffer, ':')) == NULL)
150 sinfo->expires_min = sinfo->expires_max = parse_expire(buffer);
151 else {
152 *cp = '\0';
153 cp++;
154
155 sinfo->expires_min = buffer[0] ? parse_expire(buffer) : 0;
Martin Josefsson1da399c2004-05-26 15:50:57 +0000156 sinfo->expires_max = cp[0] ? parse_expire(cp) : -1;
Marc Boucher5054e852002-01-19 10:59:12 +0000157 }
158 free(buffer);
159
160 if (sinfo->expires_min > sinfo->expires_max)
161 exit_error(PARAMETER_PROBLEM,
162 "expire min. range value `%lu' greater than max. "
163 "range value `%lu'", sinfo->expires_min, sinfo->expires_max);
Marc Boucher5054e852002-01-19 10:59:12 +0000164}
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,
Yasuyuki KOZAKAIc0a9ab92007-07-24 06:02:05 +0000170 const void *entry,
Marc Boucher5054e852002-01-19 10:59:12 +0000171 unsigned int *nfcache,
Yasuyuki KOZAKAI193df8e2007-07-24 05:57:28 +0000172 struct xt_entry_match **match)
Marc Boucher5054e852002-01-19 10:59:12 +0000173{
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':
Harald Welteb77f1da2002-03-14 11:35:58 +0000182 check_inverse(optarg, &invert, &optind, 0);
Marc Boucher5054e852002-01-19 10:59:12 +0000183
184 parse_states(argv[optind-1], sinfo);
185 if (invert) {
186 sinfo->invflags |= IPT_CONNTRACK_STATE;
187 }
188 sinfo->flags |= IPT_CONNTRACK_STATE;
189 break;
190
191 case '2':
Harald Welte3c5bd602002-03-14 19:54:34 +0000192 check_inverse(optarg, &invert, &optind, 0);
Marc Boucher5054e852002-01-19 10:59:12 +0000193
194 if(invert)
195 sinfo->invflags |= IPT_CONNTRACK_PROTO;
196
197 /* Canonicalize into lower case */
198 for (protocol = argv[optind-1]; *protocol; protocol++)
199 *protocol = tolower(*protocol);
200
201 protocol = argv[optind-1];
202 sinfo->tuple[IP_CT_DIR_ORIGINAL].dst.protonum = parse_protocol(protocol);
203
204 if (sinfo->tuple[IP_CT_DIR_ORIGINAL].dst.protonum == 0
205 && (sinfo->invflags & IPT_INV_PROTO))
206 exit_error(PARAMETER_PROBLEM,
207 "rule would never match protocol");
208
209 sinfo->flags |= IPT_CONNTRACK_PROTO;
210 break;
211
212 case '3':
Harald Welteb77f1da2002-03-14 11:35:58 +0000213 check_inverse(optarg, &invert, &optind, 9);
Marc Boucher5054e852002-01-19 10:59:12 +0000214
215 if (invert)
216 sinfo->invflags |= IPT_CONNTRACK_ORIGSRC;
217
218 parse_hostnetworkmask(argv[optind-1], &addrs,
219 &sinfo->sipmsk[IP_CT_DIR_ORIGINAL],
220 &naddrs);
221 if(naddrs > 1)
222 exit_error(PARAMETER_PROBLEM,
223 "multiple IP addresses not allowed");
224
225 if(naddrs == 1) {
226 sinfo->tuple[IP_CT_DIR_ORIGINAL].src.ip = addrs[0].s_addr;
227 }
228
229 sinfo->flags |= IPT_CONNTRACK_ORIGSRC;
230 break;
231
232 case '4':
Harald Welteb77f1da2002-03-14 11:35:58 +0000233 check_inverse(optarg, &invert, &optind, 0);
Marc Boucher5054e852002-01-19 10:59:12 +0000234
235 if (invert)
236 sinfo->invflags |= IPT_CONNTRACK_ORIGDST;
237
238 parse_hostnetworkmask(argv[optind-1], &addrs,
239 &sinfo->dipmsk[IP_CT_DIR_ORIGINAL],
240 &naddrs);
241 if(naddrs > 1)
242 exit_error(PARAMETER_PROBLEM,
243 "multiple IP addresses not allowed");
244
245 if(naddrs == 1) {
246 sinfo->tuple[IP_CT_DIR_ORIGINAL].dst.ip = addrs[0].s_addr;
247 }
248
249 sinfo->flags |= IPT_CONNTRACK_ORIGDST;
250 break;
251
252 case '5':
Harald Welteb77f1da2002-03-14 11:35:58 +0000253 check_inverse(optarg, &invert, &optind, 0);
Marc Boucher5054e852002-01-19 10:59:12 +0000254
255 if (invert)
256 sinfo->invflags |= IPT_CONNTRACK_REPLSRC;
257
258 parse_hostnetworkmask(argv[optind-1], &addrs,
259 &sinfo->sipmsk[IP_CT_DIR_REPLY],
260 &naddrs);
261 if(naddrs > 1)
262 exit_error(PARAMETER_PROBLEM,
263 "multiple IP addresses not allowed");
264
265 if(naddrs == 1) {
266 sinfo->tuple[IP_CT_DIR_REPLY].src.ip = addrs[0].s_addr;
267 }
268
269 sinfo->flags |= IPT_CONNTRACK_REPLSRC;
270 break;
271
272 case '6':
Harald Welteb77f1da2002-03-14 11:35:58 +0000273 check_inverse(optarg, &invert, &optind, 0);
Marc Boucher5054e852002-01-19 10:59:12 +0000274
275 if (invert)
276 sinfo->invflags |= IPT_CONNTRACK_REPLDST;
277
278 parse_hostnetworkmask(argv[optind-1], &addrs,
279 &sinfo->dipmsk[IP_CT_DIR_REPLY],
280 &naddrs);
281 if(naddrs > 1)
282 exit_error(PARAMETER_PROBLEM,
283 "multiple IP addresses not allowed");
284
285 if(naddrs == 1) {
286 sinfo->tuple[IP_CT_DIR_REPLY].dst.ip = addrs[0].s_addr;
287 }
288
289 sinfo->flags |= IPT_CONNTRACK_REPLDST;
290 break;
291
292 case '7':
Harald Welteb77f1da2002-03-14 11:35:58 +0000293 check_inverse(optarg, &invert, &optind, 0);
Marc Boucher5054e852002-01-19 10:59:12 +0000294
295 parse_statuses(argv[optind-1], sinfo);
296 if (invert) {
297 sinfo->invflags |= IPT_CONNTRACK_STATUS;
298 }
299 sinfo->flags |= IPT_CONNTRACK_STATUS;
300 break;
301
302 case '8':
Harald Welteb77f1da2002-03-14 11:35:58 +0000303 check_inverse(optarg, &invert, &optind, 0);
Marc Boucher5054e852002-01-19 10:59:12 +0000304
305 parse_expires(argv[optind-1], sinfo);
306 if (invert) {
307 sinfo->invflags |= IPT_CONNTRACK_EXPIRES;
308 }
309 sinfo->flags |= IPT_CONNTRACK_EXPIRES;
310 break;
311
312 default:
313 return 0;
314 }
315
316 *flags = sinfo->flags;
317 return 1;
318}
319
320static void
321final_check(unsigned int flags)
322{
323 if (!flags)
324 exit_error(PARAMETER_PROBLEM, "You must specify one or more options");
325}
326
327static void
328print_state(unsigned int statemask)
329{
330 const char *sep = "";
331
332 if (statemask & IPT_CONNTRACK_STATE_INVALID) {
333 printf("%sINVALID", sep);
334 sep = ",";
335 }
336 if (statemask & IPT_CONNTRACK_STATE_BIT(IP_CT_NEW)) {
337 printf("%sNEW", sep);
338 sep = ",";
339 }
340 if (statemask & IPT_CONNTRACK_STATE_BIT(IP_CT_RELATED)) {
341 printf("%sRELATED", sep);
342 sep = ",";
343 }
344 if (statemask & IPT_CONNTRACK_STATE_BIT(IP_CT_ESTABLISHED)) {
345 printf("%sESTABLISHED", sep);
346 sep = ",";
347 }
Harald Welte4dc734c2003-10-07 18:55:13 +0000348 if (statemask & IPT_CONNTRACK_STATE_UNTRACKED) {
349 printf("%sUNTRACKED", sep);
350 sep = ",";
351 }
Marc Boucher5054e852002-01-19 10:59:12 +0000352 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
Tom Eastep55548fd2005-09-19 15:14:04 +0000398 if (inv)
399 printf("! ");
Marc Boucher5054e852002-01-19 10:59:12 +0000400
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
Yasuyuki KOZAKAIc0a9ab92007-07-24 06:02:05 +0000415matchinfo_print(const void *ip, const struct xt_entry_match *match, int numeric, const char *optpfx)
Marc Boucher5054e852002-01-19 10:59:12 +0000416{
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
Phil Oester5a4892b2005-11-17 13:34:51 +0000426 if(sinfo->flags & IPT_CONNTRACK_PROTO) {
427 printf("%sctproto ", optpfx);
428 if (sinfo->invflags & IPT_CONNTRACK_PROTO)
429 printf("! ");
430 printf("%u ", sinfo->tuple[IP_CT_DIR_ORIGINAL].dst.protonum);
431 }
432
Marc Boucher5054e852002-01-19 10:59:12 +0000433 if(sinfo->flags & IPT_CONNTRACK_ORIGSRC) {
434 printf("%sctorigsrc ", optpfx);
435
436 print_addr(
437 (struct in_addr *)&sinfo->tuple[IP_CT_DIR_ORIGINAL].src.ip,
438 &sinfo->sipmsk[IP_CT_DIR_ORIGINAL],
439 sinfo->invflags & IPT_CONNTRACK_ORIGSRC,
440 numeric);
441 }
442
443 if(sinfo->flags & IPT_CONNTRACK_ORIGDST) {
444 printf("%sctorigdst ", optpfx);
445
446 print_addr(
447 (struct in_addr *)&sinfo->tuple[IP_CT_DIR_ORIGINAL].dst.ip,
448 &sinfo->dipmsk[IP_CT_DIR_ORIGINAL],
449 sinfo->invflags & IPT_CONNTRACK_ORIGDST,
450 numeric);
451 }
452
453 if(sinfo->flags & IPT_CONNTRACK_REPLSRC) {
Lutz Preßlerd0ae04e2003-03-04 14:50:50 +0000454 printf("%sctreplsrc ", optpfx);
Marc Boucher5054e852002-01-19 10:59:12 +0000455
456 print_addr(
457 (struct in_addr *)&sinfo->tuple[IP_CT_DIR_REPLY].src.ip,
458 &sinfo->sipmsk[IP_CT_DIR_REPLY],
459 sinfo->invflags & IPT_CONNTRACK_REPLSRC,
460 numeric);
461 }
462
463 if(sinfo->flags & IPT_CONNTRACK_REPLDST) {
Lutz Preßlerd0ae04e2003-03-04 14:50:50 +0000464 printf("%sctrepldst ", optpfx);
Marc Boucher5054e852002-01-19 10:59:12 +0000465
466 print_addr(
467 (struct in_addr *)&sinfo->tuple[IP_CT_DIR_REPLY].dst.ip,
468 &sinfo->dipmsk[IP_CT_DIR_REPLY],
469 sinfo->invflags & IPT_CONNTRACK_REPLDST,
470 numeric);
471 }
472
473 if(sinfo->flags & IPT_CONNTRACK_STATUS) {
474 printf("%sctstatus ", optpfx);
Phil Oester811b0402004-08-23 18:41:44 +0000475 if (sinfo->invflags & IPT_CONNTRACK_STATUS)
Michael Schwendtdfba3ac2002-12-05 20:20:29 +0000476 printf("! ");
Marc Boucher5054e852002-01-19 10:59:12 +0000477 print_status(sinfo->statusmask);
478 }
479
480 if(sinfo->flags & IPT_CONNTRACK_EXPIRES) {
481 printf("%sctexpire ", optpfx);
482 if (sinfo->invflags & IPT_CONNTRACK_EXPIRES)
Michael Schwendtdfba3ac2002-12-05 20:20:29 +0000483 printf("! ");
Marc Boucher5054e852002-01-19 10:59:12 +0000484
485 if (sinfo->expires_max == sinfo->expires_min)
486 printf("%lu ", sinfo->expires_min);
487 else
488 printf("%lu:%lu ", sinfo->expires_min, sinfo->expires_max);
489 }
490}
491
492/* Prints out the matchinfo. */
493static void
Yasuyuki KOZAKAIc0a9ab92007-07-24 06:02:05 +0000494print(const void *ip,
Yasuyuki KOZAKAI193df8e2007-07-24 05:57:28 +0000495 const struct xt_entry_match *match,
Marc Boucher5054e852002-01-19 10:59:12 +0000496 int numeric)
497{
498 matchinfo_print(ip, match, numeric, "");
499}
500
501/* Saves the matchinfo in parsable form to stdout. */
Yasuyuki KOZAKAIc0a9ab92007-07-24 06:02:05 +0000502static void save(const void *ip, const struct xt_entry_match *match)
Marc Boucher5054e852002-01-19 10:59:12 +0000503{
Joszef Kadlecsikdb503f92004-05-05 10:10:33 +0000504 matchinfo_print(ip, match, 1, "--");
Marc Boucher5054e852002-01-19 10:59:12 +0000505}
506
Pablo Neira8caee8b2004-12-28 13:11:59 +0000507static struct iptables_match conntrack = {
508 .next = NULL,
509 .name = "conntrack",
510 .version = IPTABLES_VERSION,
511 .size = IPT_ALIGN(sizeof(struct ipt_conntrack_info)),
512 .userspacesize = IPT_ALIGN(sizeof(struct ipt_conntrack_info)),
513 .help = &help,
Pablo Neira8caee8b2004-12-28 13:11:59 +0000514 .parse = &parse,
515 .final_check = &final_check,
516 .print = &print,
517 .save = &save,
518 .extra_opts = opts
Marc Boucher5054e852002-01-19 10:59:12 +0000519};
520
521void _init(void)
522{
523 register_match(&conntrack);
524}