blob: 6b3d39b263ccffc03b722a5a3392928e051bd512 [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. */
Jan Engelhardt59d16402007-10-04 16:28:39 +000021static void conntrack_help(void)
Marc Boucher5054e852002-01-19 10:59:12 +000022{
23 printf(
24"conntrack match v%s options:\n"
Harald Welte4dc734c2003-10-07 18:55:13 +000025" [!] --ctstate [INVALID|ESTABLISHED|NEW|RELATED|UNTRACKED|SNAT|DNAT][,...]\n"
Marc Boucher5054e852002-01-19 10:59:12 +000026" State(s) to match\n"
27" [!] --ctproto proto Protocol to match; by number or name, eg. `tcp'\n"
28" --ctorigsrc [!] address[/mask]\n"
29" Original source specification\n"
30" --ctorigdst [!] address[/mask]\n"
31" Original destination specification\n"
32" --ctreplsrc [!] address[/mask]\n"
33" Reply source specification\n"
34" --ctrepldst [!] address[/mask]\n"
35" Reply destination specification\n"
Harald Weltea643c3e2003-08-25 11:08:52 +000036" [!] --ctstatus [NONE|EXPECTED|SEEN_REPLY|ASSURED|CONFIRMED][,...]\n"
Marc Boucher5054e852002-01-19 10:59:12 +000037" Status(es) to match\n"
38" [!] --ctexpire time[:time] Match remaining lifetime in seconds against\n"
39" value or range of values (inclusive)\n"
Harald Welte80fe35d2002-05-29 13:08:15 +000040"\n", IPTABLES_VERSION);
Marc Boucher5054e852002-01-19 10:59:12 +000041}
42
Jan Engelhardt59d16402007-10-04 16:28:39 +000043static const struct option conntrack_opts[] = {
Patrick McHardy500f4832007-09-08 15:59:04 +000044 { "ctstate", 1, NULL, '1' },
45 { "ctproto", 1, NULL, '2' },
46 { "ctorigsrc", 1, NULL, '3' },
47 { "ctorigdst", 1, NULL, '4' },
48 { "ctreplsrc", 1, NULL, '5' },
49 { "ctrepldst", 1, NULL, '6' },
50 { "ctstatus", 1, NULL, '7' },
51 { "ctexpire", 1, NULL, '8' },
52 { }
Marc Boucher5054e852002-01-19 10:59:12 +000053};
54
Marc Boucher5054e852002-01-19 10:59:12 +000055static int
56parse_state(const char *state, size_t strlen, struct ipt_conntrack_info *sinfo)
57{
58 if (strncasecmp(state, "INVALID", strlen) == 0)
59 sinfo->statemask |= IPT_CONNTRACK_STATE_INVALID;
60 else if (strncasecmp(state, "NEW", strlen) == 0)
61 sinfo->statemask |= IPT_CONNTRACK_STATE_BIT(IP_CT_NEW);
62 else if (strncasecmp(state, "ESTABLISHED", strlen) == 0)
63 sinfo->statemask |= IPT_CONNTRACK_STATE_BIT(IP_CT_ESTABLISHED);
64 else if (strncasecmp(state, "RELATED", strlen) == 0)
65 sinfo->statemask |= IPT_CONNTRACK_STATE_BIT(IP_CT_RELATED);
Harald Welte4dc734c2003-10-07 18:55:13 +000066 else if (strncasecmp(state, "UNTRACKED", strlen) == 0)
67 sinfo->statemask |= IPT_CONNTRACK_STATE_UNTRACKED;
Marc Boucher5054e852002-01-19 10:59:12 +000068 else if (strncasecmp(state, "SNAT", strlen) == 0)
69 sinfo->statemask |= IPT_CONNTRACK_STATE_SNAT;
70 else if (strncasecmp(state, "DNAT", strlen) == 0)
71 sinfo->statemask |= IPT_CONNTRACK_STATE_DNAT;
72 else
73 return 0;
74 return 1;
75}
76
77static void
78parse_states(const char *arg, struct ipt_conntrack_info *sinfo)
79{
80 const char *comma;
81
82 while ((comma = strchr(arg, ',')) != NULL) {
83 if (comma == arg || !parse_state(arg, comma-arg, sinfo))
84 exit_error(PARAMETER_PROBLEM, "Bad ctstate `%s'", arg);
85 arg = comma+1;
86 }
87
88 if (strlen(arg) == 0 || !parse_state(arg, strlen(arg), sinfo))
89 exit_error(PARAMETER_PROBLEM, "Bad ctstate `%s'", arg);
90}
91
92static int
93parse_status(const char *status, size_t strlen, struct ipt_conntrack_info *sinfo)
94{
95 if (strncasecmp(status, "NONE", strlen) == 0)
96 sinfo->statusmask |= 0;
97 else if (strncasecmp(status, "EXPECTED", strlen) == 0)
98 sinfo->statusmask |= IPS_EXPECTED;
99 else if (strncasecmp(status, "SEEN_REPLY", strlen) == 0)
100 sinfo->statusmask |= IPS_SEEN_REPLY;
101 else if (strncasecmp(status, "ASSURED", strlen) == 0)
102 sinfo->statusmask |= IPS_ASSURED;
Harald Weltea643c3e2003-08-25 11:08:52 +0000103#ifdef IPS_CONFIRMED
104 else if (strncasecmp(status, "CONFIRMED", strlen) == 0)
105 sinfo->stausmask |= IPS_CONFIRMED;
106#endif
Marc Boucher5054e852002-01-19 10:59:12 +0000107 else
108 return 0;
109 return 1;
110}
111
112static void
113parse_statuses(const char *arg, struct ipt_conntrack_info *sinfo)
114{
115 const char *comma;
116
117 while ((comma = strchr(arg, ',')) != NULL) {
118 if (comma == arg || !parse_status(arg, comma-arg, sinfo))
119 exit_error(PARAMETER_PROBLEM, "Bad ctstatus `%s'", arg);
120 arg = comma+1;
121 }
122
123 if (strlen(arg) == 0 || !parse_status(arg, strlen(arg), sinfo))
124 exit_error(PARAMETER_PROBLEM, "Bad ctstatus `%s'", arg);
125}
126
Marc Boucher5054e852002-01-19 10:59:12 +0000127static unsigned long
128parse_expire(const char *s)
129{
130 unsigned int len;
131
Martin Josefsson1da399c2004-05-26 15:50:57 +0000132 if (string_to_number(s, 0, 0, &len) == -1)
Marc Boucher5054e852002-01-19 10:59:12 +0000133 exit_error(PARAMETER_PROBLEM, "expire value invalid: `%s'\n", s);
134 else
135 return len;
136}
137
138/* If a single value is provided, min and max are both set to the value */
139static void
140parse_expires(const char *s, struct ipt_conntrack_info *sinfo)
141{
142 char *buffer;
143 char *cp;
144
145 buffer = strdup(s);
146 if ((cp = strchr(buffer, ':')) == NULL)
147 sinfo->expires_min = sinfo->expires_max = parse_expire(buffer);
148 else {
149 *cp = '\0';
150 cp++;
151
152 sinfo->expires_min = buffer[0] ? parse_expire(buffer) : 0;
Martin Josefsson1da399c2004-05-26 15:50:57 +0000153 sinfo->expires_max = cp[0] ? parse_expire(cp) : -1;
Marc Boucher5054e852002-01-19 10:59:12 +0000154 }
155 free(buffer);
156
157 if (sinfo->expires_min > sinfo->expires_max)
158 exit_error(PARAMETER_PROBLEM,
159 "expire min. range value `%lu' greater than max. "
160 "range value `%lu'", sinfo->expires_min, sinfo->expires_max);
Marc Boucher5054e852002-01-19 10:59:12 +0000161}
162
163/* Function which parses command options; returns true if it
164 ate an option */
Jan Engelhardt59d16402007-10-04 16:28:39 +0000165static int conntrack_parse(int c, char **argv, int invert, unsigned int *flags,
166 const void *entry, struct xt_entry_match **match)
Marc Boucher5054e852002-01-19 10:59:12 +0000167{
168 struct ipt_conntrack_info *sinfo = (struct ipt_conntrack_info *)(*match)->data;
169 char *protocol = NULL;
170 unsigned int naddrs = 0;
171 struct in_addr *addrs = NULL;
172
173
174 switch (c) {
175 case '1':
Harald Welteb77f1da2002-03-14 11:35:58 +0000176 check_inverse(optarg, &invert, &optind, 0);
Marc Boucher5054e852002-01-19 10:59:12 +0000177
178 parse_states(argv[optind-1], sinfo);
179 if (invert) {
180 sinfo->invflags |= IPT_CONNTRACK_STATE;
181 }
182 sinfo->flags |= IPT_CONNTRACK_STATE;
183 break;
184
185 case '2':
Harald Welte3c5bd602002-03-14 19:54:34 +0000186 check_inverse(optarg, &invert, &optind, 0);
Marc Boucher5054e852002-01-19 10:59:12 +0000187
188 if(invert)
189 sinfo->invflags |= IPT_CONNTRACK_PROTO;
190
191 /* Canonicalize into lower case */
192 for (protocol = argv[optind-1]; *protocol; protocol++)
193 *protocol = tolower(*protocol);
194
195 protocol = argv[optind-1];
196 sinfo->tuple[IP_CT_DIR_ORIGINAL].dst.protonum = parse_protocol(protocol);
197
198 if (sinfo->tuple[IP_CT_DIR_ORIGINAL].dst.protonum == 0
199 && (sinfo->invflags & IPT_INV_PROTO))
200 exit_error(PARAMETER_PROBLEM,
201 "rule would never match protocol");
202
203 sinfo->flags |= IPT_CONNTRACK_PROTO;
204 break;
205
206 case '3':
Harald Welteb77f1da2002-03-14 11:35:58 +0000207 check_inverse(optarg, &invert, &optind, 9);
Marc Boucher5054e852002-01-19 10:59:12 +0000208
209 if (invert)
210 sinfo->invflags |= IPT_CONNTRACK_ORIGSRC;
211
212 parse_hostnetworkmask(argv[optind-1], &addrs,
213 &sinfo->sipmsk[IP_CT_DIR_ORIGINAL],
214 &naddrs);
215 if(naddrs > 1)
216 exit_error(PARAMETER_PROBLEM,
217 "multiple IP addresses not allowed");
218
219 if(naddrs == 1) {
220 sinfo->tuple[IP_CT_DIR_ORIGINAL].src.ip = addrs[0].s_addr;
221 }
222
223 sinfo->flags |= IPT_CONNTRACK_ORIGSRC;
224 break;
225
226 case '4':
Harald Welteb77f1da2002-03-14 11:35:58 +0000227 check_inverse(optarg, &invert, &optind, 0);
Marc Boucher5054e852002-01-19 10:59:12 +0000228
229 if (invert)
230 sinfo->invflags |= IPT_CONNTRACK_ORIGDST;
231
232 parse_hostnetworkmask(argv[optind-1], &addrs,
233 &sinfo->dipmsk[IP_CT_DIR_ORIGINAL],
234 &naddrs);
235 if(naddrs > 1)
236 exit_error(PARAMETER_PROBLEM,
237 "multiple IP addresses not allowed");
238
239 if(naddrs == 1) {
240 sinfo->tuple[IP_CT_DIR_ORIGINAL].dst.ip = addrs[0].s_addr;
241 }
242
243 sinfo->flags |= IPT_CONNTRACK_ORIGDST;
244 break;
245
246 case '5':
Harald Welteb77f1da2002-03-14 11:35:58 +0000247 check_inverse(optarg, &invert, &optind, 0);
Marc Boucher5054e852002-01-19 10:59:12 +0000248
249 if (invert)
250 sinfo->invflags |= IPT_CONNTRACK_REPLSRC;
251
252 parse_hostnetworkmask(argv[optind-1], &addrs,
253 &sinfo->sipmsk[IP_CT_DIR_REPLY],
254 &naddrs);
255 if(naddrs > 1)
256 exit_error(PARAMETER_PROBLEM,
257 "multiple IP addresses not allowed");
258
259 if(naddrs == 1) {
260 sinfo->tuple[IP_CT_DIR_REPLY].src.ip = addrs[0].s_addr;
261 }
262
263 sinfo->flags |= IPT_CONNTRACK_REPLSRC;
264 break;
265
266 case '6':
Harald Welteb77f1da2002-03-14 11:35:58 +0000267 check_inverse(optarg, &invert, &optind, 0);
Marc Boucher5054e852002-01-19 10:59:12 +0000268
269 if (invert)
270 sinfo->invflags |= IPT_CONNTRACK_REPLDST;
271
272 parse_hostnetworkmask(argv[optind-1], &addrs,
273 &sinfo->dipmsk[IP_CT_DIR_REPLY],
274 &naddrs);
275 if(naddrs > 1)
276 exit_error(PARAMETER_PROBLEM,
277 "multiple IP addresses not allowed");
278
279 if(naddrs == 1) {
280 sinfo->tuple[IP_CT_DIR_REPLY].dst.ip = addrs[0].s_addr;
281 }
282
283 sinfo->flags |= IPT_CONNTRACK_REPLDST;
284 break;
285
286 case '7':
Harald Welteb77f1da2002-03-14 11:35:58 +0000287 check_inverse(optarg, &invert, &optind, 0);
Marc Boucher5054e852002-01-19 10:59:12 +0000288
289 parse_statuses(argv[optind-1], sinfo);
290 if (invert) {
291 sinfo->invflags |= IPT_CONNTRACK_STATUS;
292 }
293 sinfo->flags |= IPT_CONNTRACK_STATUS;
294 break;
295
296 case '8':
Harald Welteb77f1da2002-03-14 11:35:58 +0000297 check_inverse(optarg, &invert, &optind, 0);
Marc Boucher5054e852002-01-19 10:59:12 +0000298
299 parse_expires(argv[optind-1], sinfo);
300 if (invert) {
301 sinfo->invflags |= IPT_CONNTRACK_EXPIRES;
302 }
303 sinfo->flags |= IPT_CONNTRACK_EXPIRES;
304 break;
305
306 default:
307 return 0;
308 }
309
310 *flags = sinfo->flags;
311 return 1;
312}
313
Jan Engelhardt59d16402007-10-04 16:28:39 +0000314static void conntrack_check(unsigned int flags)
Marc Boucher5054e852002-01-19 10:59:12 +0000315{
316 if (!flags)
317 exit_error(PARAMETER_PROBLEM, "You must specify one or more options");
318}
319
320static void
321print_state(unsigned int statemask)
322{
323 const char *sep = "";
324
325 if (statemask & IPT_CONNTRACK_STATE_INVALID) {
326 printf("%sINVALID", sep);
327 sep = ",";
328 }
329 if (statemask & IPT_CONNTRACK_STATE_BIT(IP_CT_NEW)) {
330 printf("%sNEW", sep);
331 sep = ",";
332 }
333 if (statemask & IPT_CONNTRACK_STATE_BIT(IP_CT_RELATED)) {
334 printf("%sRELATED", sep);
335 sep = ",";
336 }
337 if (statemask & IPT_CONNTRACK_STATE_BIT(IP_CT_ESTABLISHED)) {
338 printf("%sESTABLISHED", sep);
339 sep = ",";
340 }
Harald Welte4dc734c2003-10-07 18:55:13 +0000341 if (statemask & IPT_CONNTRACK_STATE_UNTRACKED) {
342 printf("%sUNTRACKED", sep);
343 sep = ",";
344 }
Marc Boucher5054e852002-01-19 10:59:12 +0000345 if (statemask & IPT_CONNTRACK_STATE_SNAT) {
346 printf("%sSNAT", sep);
347 sep = ",";
348 }
349 if (statemask & IPT_CONNTRACK_STATE_DNAT) {
350 printf("%sDNAT", sep);
351 sep = ",";
352 }
353 printf(" ");
354}
355
356static void
357print_status(unsigned int statusmask)
358{
359 const char *sep = "";
360
361 if (statusmask & IPS_EXPECTED) {
362 printf("%sEXPECTED", sep);
363 sep = ",";
364 }
365 if (statusmask & IPS_SEEN_REPLY) {
366 printf("%sSEEN_REPLY", sep);
367 sep = ",";
368 }
369 if (statusmask & IPS_ASSURED) {
370 printf("%sASSURED", sep);
371 sep = ",";
372 }
Harald Weltea643c3e2003-08-25 11:08:52 +0000373#ifdef IPS_CONFIRMED
374 if (statusmask & IPS_CONFIRMED) {
375 printf("%sCONFIRMED", sep);
376 sep =",";
377 }
378#endif
Marc Boucher5054e852002-01-19 10:59:12 +0000379 if (statusmask == 0) {
380 printf("%sNONE", sep);
381 sep = ",";
382 }
383 printf(" ");
384}
385
386static void
387print_addr(struct in_addr *addr, struct in_addr *mask, int inv, int numeric)
388{
389 char buf[BUFSIZ];
390
Tom Eastep55548fd2005-09-19 15:14:04 +0000391 if (inv)
392 printf("! ");
Marc Boucher5054e852002-01-19 10:59:12 +0000393
394 if (mask->s_addr == 0L && !numeric)
395 printf("%s ", "anywhere");
396 else {
397 if (numeric)
398 sprintf(buf, "%s", addr_to_dotted(addr));
399 else
400 sprintf(buf, "%s", addr_to_anyname(addr));
401 strcat(buf, mask_to_dotted(mask));
402 printf("%s ", buf);
403 }
404}
405
406/* Saves the matchinfo in parsable form to stdout. */
407static void
Yasuyuki KOZAKAIc0a9ab92007-07-24 06:02:05 +0000408matchinfo_print(const void *ip, const struct xt_entry_match *match, int numeric, const char *optpfx)
Marc Boucher5054e852002-01-19 10:59:12 +0000409{
410 struct ipt_conntrack_info *sinfo = (struct ipt_conntrack_info *)match->data;
411
412 if(sinfo->flags & IPT_CONNTRACK_STATE) {
413 printf("%sctstate ", optpfx);
414 if (sinfo->invflags & IPT_CONNTRACK_STATE)
Michael Schwendtdfba3ac2002-12-05 20:20:29 +0000415 printf("! ");
Marc Boucher5054e852002-01-19 10:59:12 +0000416 print_state(sinfo->statemask);
417 }
418
Phil Oester5a4892b2005-11-17 13:34:51 +0000419 if(sinfo->flags & IPT_CONNTRACK_PROTO) {
420 printf("%sctproto ", optpfx);
421 if (sinfo->invflags & IPT_CONNTRACK_PROTO)
422 printf("! ");
423 printf("%u ", sinfo->tuple[IP_CT_DIR_ORIGINAL].dst.protonum);
424 }
425
Marc Boucher5054e852002-01-19 10:59:12 +0000426 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);
Phil Oester811b0402004-08-23 18:41:44 +0000468 if (sinfo->invflags & IPT_CONNTRACK_STATUS)
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. */
Jan Engelhardt59d16402007-10-04 16:28:39 +0000486static void conntrack_print(const void *ip, const struct xt_entry_match *match,
487 int numeric)
Marc Boucher5054e852002-01-19 10:59:12 +0000488{
489 matchinfo_print(ip, match, numeric, "");
490}
491
492/* Saves the matchinfo in parsable form to stdout. */
Jan Engelhardt59d16402007-10-04 16:28:39 +0000493static void conntrack_save(const void *ip, const struct xt_entry_match *match)
Marc Boucher5054e852002-01-19 10:59:12 +0000494{
Joszef Kadlecsikdb503f92004-05-05 10:10:33 +0000495 matchinfo_print(ip, match, 1, "--");
Marc Boucher5054e852002-01-19 10:59:12 +0000496}
497
Jan Engelhardt59d16402007-10-04 16:28:39 +0000498static struct iptables_match conntrack_match = {
Pablo Neira8caee8b2004-12-28 13:11:59 +0000499 .name = "conntrack",
500 .version = IPTABLES_VERSION,
501 .size = IPT_ALIGN(sizeof(struct ipt_conntrack_info)),
502 .userspacesize = IPT_ALIGN(sizeof(struct ipt_conntrack_info)),
Jan Engelhardt59d16402007-10-04 16:28:39 +0000503 .help = conntrack_help,
504 .parse = conntrack_parse,
505 .final_check = conntrack_check,
506 .print = conntrack_print,
507 .save = conntrack_save,
508 .extra_opts = conntrack_opts,
Marc Boucher5054e852002-01-19 10:59:12 +0000509};
510
511void _init(void)
512{
Jan Engelhardt59d16402007-10-04 16:28:39 +0000513 register_match(&conntrack_match);
Marc Boucher5054e852002-01-19 10:59:12 +0000514}