blob: 48c2f1dc95d1313ffda459782783adeab91c56ea [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
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
58/* Initialize the match. */
59static void
60init(struct ipt_entry_match *m, unsigned int *nfcache)
61{
62 /* Can't cache this */
63 *nfcache |= NFC_UNKNOWN;
64}
65
66static int
67parse_state(const char *state, size_t strlen, struct ipt_conntrack_info *sinfo)
68{
69 if (strncasecmp(state, "INVALID", strlen) == 0)
70 sinfo->statemask |= IPT_CONNTRACK_STATE_INVALID;
71 else if (strncasecmp(state, "NEW", strlen) == 0)
72 sinfo->statemask |= IPT_CONNTRACK_STATE_BIT(IP_CT_NEW);
73 else if (strncasecmp(state, "ESTABLISHED", strlen) == 0)
74 sinfo->statemask |= IPT_CONNTRACK_STATE_BIT(IP_CT_ESTABLISHED);
75 else if (strncasecmp(state, "RELATED", strlen) == 0)
76 sinfo->statemask |= IPT_CONNTRACK_STATE_BIT(IP_CT_RELATED);
Harald Welte4dc734c2003-10-07 18:55:13 +000077 else if (strncasecmp(state, "UNTRACKED", strlen) == 0)
78 sinfo->statemask |= IPT_CONNTRACK_STATE_UNTRACKED;
Marc Boucher5054e852002-01-19 10:59:12 +000079 else if (strncasecmp(state, "SNAT", strlen) == 0)
80 sinfo->statemask |= IPT_CONNTRACK_STATE_SNAT;
81 else if (strncasecmp(state, "DNAT", strlen) == 0)
82 sinfo->statemask |= IPT_CONNTRACK_STATE_DNAT;
83 else
84 return 0;
85 return 1;
86}
87
88static void
89parse_states(const char *arg, struct ipt_conntrack_info *sinfo)
90{
91 const char *comma;
92
93 while ((comma = strchr(arg, ',')) != NULL) {
94 if (comma == arg || !parse_state(arg, comma-arg, sinfo))
95 exit_error(PARAMETER_PROBLEM, "Bad ctstate `%s'", arg);
96 arg = comma+1;
97 }
98
99 if (strlen(arg) == 0 || !parse_state(arg, strlen(arg), sinfo))
100 exit_error(PARAMETER_PROBLEM, "Bad ctstate `%s'", arg);
101}
102
103static int
104parse_status(const char *status, size_t strlen, struct ipt_conntrack_info *sinfo)
105{
106 if (strncasecmp(status, "NONE", strlen) == 0)
107 sinfo->statusmask |= 0;
108 else if (strncasecmp(status, "EXPECTED", strlen) == 0)
109 sinfo->statusmask |= IPS_EXPECTED;
110 else if (strncasecmp(status, "SEEN_REPLY", strlen) == 0)
111 sinfo->statusmask |= IPS_SEEN_REPLY;
112 else if (strncasecmp(status, "ASSURED", strlen) == 0)
113 sinfo->statusmask |= IPS_ASSURED;
Harald Weltea643c3e2003-08-25 11:08:52 +0000114#ifdef IPS_CONFIRMED
115 else if (strncasecmp(status, "CONFIRMED", strlen) == 0)
116 sinfo->stausmask |= IPS_CONFIRMED;
117#endif
Marc Boucher5054e852002-01-19 10:59:12 +0000118 else
119 return 0;
120 return 1;
121}
122
123static void
124parse_statuses(const char *arg, struct ipt_conntrack_info *sinfo)
125{
126 const char *comma;
127
128 while ((comma = strchr(arg, ',')) != NULL) {
129 if (comma == arg || !parse_status(arg, comma-arg, sinfo))
130 exit_error(PARAMETER_PROBLEM, "Bad ctstatus `%s'", arg);
131 arg = comma+1;
132 }
133
134 if (strlen(arg) == 0 || !parse_status(arg, strlen(arg), sinfo))
135 exit_error(PARAMETER_PROBLEM, "Bad ctstatus `%s'", arg);
136}
137
138
139static unsigned long
140parse_expire(const char *s)
141{
142 unsigned int len;
143
144 if (string_to_number(s, 0, 0xFFFFFFFF, &len) == -1)
145 exit_error(PARAMETER_PROBLEM, "expire value invalid: `%s'\n", s);
146 else
147 return len;
148}
149
150/* If a single value is provided, min and max are both set to the value */
151static void
152parse_expires(const char *s, struct ipt_conntrack_info *sinfo)
153{
154 char *buffer;
155 char *cp;
156
157 buffer = strdup(s);
158 if ((cp = strchr(buffer, ':')) == NULL)
159 sinfo->expires_min = sinfo->expires_max = parse_expire(buffer);
160 else {
161 *cp = '\0';
162 cp++;
163
164 sinfo->expires_min = buffer[0] ? parse_expire(buffer) : 0;
165 sinfo->expires_max = cp[0] ? parse_expire(cp) : 0xFFFFFFFF;
166 }
167 free(buffer);
168
169 if (sinfo->expires_min > sinfo->expires_max)
170 exit_error(PARAMETER_PROBLEM,
171 "expire min. range value `%lu' greater than max. "
172 "range value `%lu'", sinfo->expires_min, sinfo->expires_max);
173
174}
175
176/* Function which parses command options; returns true if it
177 ate an option */
178static int
179parse(int c, char **argv, int invert, unsigned int *flags,
180 const struct ipt_entry *entry,
181 unsigned int *nfcache,
182 struct ipt_entry_match **match)
183{
184 struct ipt_conntrack_info *sinfo = (struct ipt_conntrack_info *)(*match)->data;
185 char *protocol = NULL;
186 unsigned int naddrs = 0;
187 struct in_addr *addrs = NULL;
188
189
190 switch (c) {
191 case '1':
Harald Welteb77f1da2002-03-14 11:35:58 +0000192 check_inverse(optarg, &invert, &optind, 0);
Marc Boucher5054e852002-01-19 10:59:12 +0000193
194 parse_states(argv[optind-1], sinfo);
195 if (invert) {
196 sinfo->invflags |= IPT_CONNTRACK_STATE;
197 }
198 sinfo->flags |= IPT_CONNTRACK_STATE;
199 break;
200
201 case '2':
Harald Welte3c5bd602002-03-14 19:54:34 +0000202 check_inverse(optarg, &invert, &optind, 0);
Marc Boucher5054e852002-01-19 10:59:12 +0000203
204 if(invert)
205 sinfo->invflags |= IPT_CONNTRACK_PROTO;
206
207 /* Canonicalize into lower case */
208 for (protocol = argv[optind-1]; *protocol; protocol++)
209 *protocol = tolower(*protocol);
210
211 protocol = argv[optind-1];
212 sinfo->tuple[IP_CT_DIR_ORIGINAL].dst.protonum = parse_protocol(protocol);
213
214 if (sinfo->tuple[IP_CT_DIR_ORIGINAL].dst.protonum == 0
215 && (sinfo->invflags & IPT_INV_PROTO))
216 exit_error(PARAMETER_PROBLEM,
217 "rule would never match protocol");
218
219 sinfo->flags |= IPT_CONNTRACK_PROTO;
220 break;
221
222 case '3':
Harald Welteb77f1da2002-03-14 11:35:58 +0000223 check_inverse(optarg, &invert, &optind, 9);
Marc Boucher5054e852002-01-19 10:59:12 +0000224
225 if (invert)
226 sinfo->invflags |= IPT_CONNTRACK_ORIGSRC;
227
228 parse_hostnetworkmask(argv[optind-1], &addrs,
229 &sinfo->sipmsk[IP_CT_DIR_ORIGINAL],
230 &naddrs);
231 if(naddrs > 1)
232 exit_error(PARAMETER_PROBLEM,
233 "multiple IP addresses not allowed");
234
235 if(naddrs == 1) {
236 sinfo->tuple[IP_CT_DIR_ORIGINAL].src.ip = addrs[0].s_addr;
237 }
238
239 sinfo->flags |= IPT_CONNTRACK_ORIGSRC;
240 break;
241
242 case '4':
Harald Welteb77f1da2002-03-14 11:35:58 +0000243 check_inverse(optarg, &invert, &optind, 0);
Marc Boucher5054e852002-01-19 10:59:12 +0000244
245 if (invert)
246 sinfo->invflags |= IPT_CONNTRACK_ORIGDST;
247
248 parse_hostnetworkmask(argv[optind-1], &addrs,
249 &sinfo->dipmsk[IP_CT_DIR_ORIGINAL],
250 &naddrs);
251 if(naddrs > 1)
252 exit_error(PARAMETER_PROBLEM,
253 "multiple IP addresses not allowed");
254
255 if(naddrs == 1) {
256 sinfo->tuple[IP_CT_DIR_ORIGINAL].dst.ip = addrs[0].s_addr;
257 }
258
259 sinfo->flags |= IPT_CONNTRACK_ORIGDST;
260 break;
261
262 case '5':
Harald Welteb77f1da2002-03-14 11:35:58 +0000263 check_inverse(optarg, &invert, &optind, 0);
Marc Boucher5054e852002-01-19 10:59:12 +0000264
265 if (invert)
266 sinfo->invflags |= IPT_CONNTRACK_REPLSRC;
267
268 parse_hostnetworkmask(argv[optind-1], &addrs,
269 &sinfo->sipmsk[IP_CT_DIR_REPLY],
270 &naddrs);
271 if(naddrs > 1)
272 exit_error(PARAMETER_PROBLEM,
273 "multiple IP addresses not allowed");
274
275 if(naddrs == 1) {
276 sinfo->tuple[IP_CT_DIR_REPLY].src.ip = addrs[0].s_addr;
277 }
278
279 sinfo->flags |= IPT_CONNTRACK_REPLSRC;
280 break;
281
282 case '6':
Harald Welteb77f1da2002-03-14 11:35:58 +0000283 check_inverse(optarg, &invert, &optind, 0);
Marc Boucher5054e852002-01-19 10:59:12 +0000284
285 if (invert)
286 sinfo->invflags |= IPT_CONNTRACK_REPLDST;
287
288 parse_hostnetworkmask(argv[optind-1], &addrs,
289 &sinfo->dipmsk[IP_CT_DIR_REPLY],
290 &naddrs);
291 if(naddrs > 1)
292 exit_error(PARAMETER_PROBLEM,
293 "multiple IP addresses not allowed");
294
295 if(naddrs == 1) {
296 sinfo->tuple[IP_CT_DIR_REPLY].dst.ip = addrs[0].s_addr;
297 }
298
299 sinfo->flags |= IPT_CONNTRACK_REPLDST;
300 break;
301
302 case '7':
Harald Welteb77f1da2002-03-14 11:35:58 +0000303 check_inverse(optarg, &invert, &optind, 0);
Marc Boucher5054e852002-01-19 10:59:12 +0000304
305 parse_statuses(argv[optind-1], sinfo);
306 if (invert) {
307 sinfo->invflags |= IPT_CONNTRACK_STATUS;
308 }
309 sinfo->flags |= IPT_CONNTRACK_STATUS;
310 break;
311
312 case '8':
Harald Welteb77f1da2002-03-14 11:35:58 +0000313 check_inverse(optarg, &invert, &optind, 0);
Marc Boucher5054e852002-01-19 10:59:12 +0000314
315 parse_expires(argv[optind-1], sinfo);
316 if (invert) {
317 sinfo->invflags |= IPT_CONNTRACK_EXPIRES;
318 }
319 sinfo->flags |= IPT_CONNTRACK_EXPIRES;
320 break;
321
322 default:
323 return 0;
324 }
325
326 *flags = sinfo->flags;
327 return 1;
328}
329
330static void
331final_check(unsigned int flags)
332{
333 if (!flags)
334 exit_error(PARAMETER_PROBLEM, "You must specify one or more options");
335}
336
337static void
338print_state(unsigned int statemask)
339{
340 const char *sep = "";
341
342 if (statemask & IPT_CONNTRACK_STATE_INVALID) {
343 printf("%sINVALID", sep);
344 sep = ",";
345 }
346 if (statemask & IPT_CONNTRACK_STATE_BIT(IP_CT_NEW)) {
347 printf("%sNEW", sep);
348 sep = ",";
349 }
350 if (statemask & IPT_CONNTRACK_STATE_BIT(IP_CT_RELATED)) {
351 printf("%sRELATED", sep);
352 sep = ",";
353 }
354 if (statemask & IPT_CONNTRACK_STATE_BIT(IP_CT_ESTABLISHED)) {
355 printf("%sESTABLISHED", sep);
356 sep = ",";
357 }
Harald Welte4dc734c2003-10-07 18:55:13 +0000358 if (statemask & IPT_CONNTRACK_STATE_UNTRACKED) {
359 printf("%sUNTRACKED", sep);
360 sep = ",";
361 }
Marc Boucher5054e852002-01-19 10:59:12 +0000362 if (statemask & IPT_CONNTRACK_STATE_SNAT) {
363 printf("%sSNAT", sep);
364 sep = ",";
365 }
366 if (statemask & IPT_CONNTRACK_STATE_DNAT) {
367 printf("%sDNAT", sep);
368 sep = ",";
369 }
370 printf(" ");
371}
372
373static void
374print_status(unsigned int statusmask)
375{
376 const char *sep = "";
377
378 if (statusmask & IPS_EXPECTED) {
379 printf("%sEXPECTED", sep);
380 sep = ",";
381 }
382 if (statusmask & IPS_SEEN_REPLY) {
383 printf("%sSEEN_REPLY", sep);
384 sep = ",";
385 }
386 if (statusmask & IPS_ASSURED) {
387 printf("%sASSURED", sep);
388 sep = ",";
389 }
Harald Weltea643c3e2003-08-25 11:08:52 +0000390#ifdef IPS_CONFIRMED
391 if (statusmask & IPS_CONFIRMED) {
392 printf("%sCONFIRMED", sep);
393 sep =",";
394 }
395#endif
Marc Boucher5054e852002-01-19 10:59:12 +0000396 if (statusmask == 0) {
397 printf("%sNONE", sep);
398 sep = ",";
399 }
400 printf(" ");
401}
402
403static void
404print_addr(struct in_addr *addr, struct in_addr *mask, int inv, int numeric)
405{
406 char buf[BUFSIZ];
407
408 if (inv)
409 fputc('!', stdout);
410
411 if (mask->s_addr == 0L && !numeric)
412 printf("%s ", "anywhere");
413 else {
414 if (numeric)
415 sprintf(buf, "%s", addr_to_dotted(addr));
416 else
417 sprintf(buf, "%s", addr_to_anyname(addr));
418 strcat(buf, mask_to_dotted(mask));
419 printf("%s ", buf);
420 }
421}
422
423/* Saves the matchinfo in parsable form to stdout. */
424static void
425matchinfo_print(const struct ipt_ip *ip, const struct ipt_entry_match *match, int numeric, const char *optpfx)
426{
427 struct ipt_conntrack_info *sinfo = (struct ipt_conntrack_info *)match->data;
428
429 if(sinfo->flags & IPT_CONNTRACK_STATE) {
430 printf("%sctstate ", optpfx);
431 if (sinfo->invflags & IPT_CONNTRACK_STATE)
Michael Schwendtdfba3ac2002-12-05 20:20:29 +0000432 printf("! ");
Marc Boucher5054e852002-01-19 10:59:12 +0000433 print_state(sinfo->statemask);
434 }
435
436 if(sinfo->flags & IPT_CONNTRACK_ORIGSRC) {
437 printf("%sctorigsrc ", optpfx);
438
439 print_addr(
440 (struct in_addr *)&sinfo->tuple[IP_CT_DIR_ORIGINAL].src.ip,
441 &sinfo->sipmsk[IP_CT_DIR_ORIGINAL],
442 sinfo->invflags & IPT_CONNTRACK_ORIGSRC,
443 numeric);
444 }
445
446 if(sinfo->flags & IPT_CONNTRACK_ORIGDST) {
447 printf("%sctorigdst ", optpfx);
448
449 print_addr(
450 (struct in_addr *)&sinfo->tuple[IP_CT_DIR_ORIGINAL].dst.ip,
451 &sinfo->dipmsk[IP_CT_DIR_ORIGINAL],
452 sinfo->invflags & IPT_CONNTRACK_ORIGDST,
453 numeric);
454 }
455
456 if(sinfo->flags & IPT_CONNTRACK_REPLSRC) {
Lutz Preßlerd0ae04e2003-03-04 14:50:50 +0000457 printf("%sctreplsrc ", optpfx);
Marc Boucher5054e852002-01-19 10:59:12 +0000458
459 print_addr(
460 (struct in_addr *)&sinfo->tuple[IP_CT_DIR_REPLY].src.ip,
461 &sinfo->sipmsk[IP_CT_DIR_REPLY],
462 sinfo->invflags & IPT_CONNTRACK_REPLSRC,
463 numeric);
464 }
465
466 if(sinfo->flags & IPT_CONNTRACK_REPLDST) {
Lutz Preßlerd0ae04e2003-03-04 14:50:50 +0000467 printf("%sctrepldst ", optpfx);
Marc Boucher5054e852002-01-19 10:59:12 +0000468
469 print_addr(
470 (struct in_addr *)&sinfo->tuple[IP_CT_DIR_REPLY].dst.ip,
471 &sinfo->dipmsk[IP_CT_DIR_REPLY],
472 sinfo->invflags & IPT_CONNTRACK_REPLDST,
473 numeric);
474 }
475
476 if(sinfo->flags & IPT_CONNTRACK_STATUS) {
477 printf("%sctstatus ", optpfx);
478 if (sinfo->invflags & IPT_CONNTRACK_STATE)
Michael Schwendtdfba3ac2002-12-05 20:20:29 +0000479 printf("! ");
Marc Boucher5054e852002-01-19 10:59:12 +0000480 print_status(sinfo->statusmask);
481 }
482
483 if(sinfo->flags & IPT_CONNTRACK_EXPIRES) {
484 printf("%sctexpire ", optpfx);
485 if (sinfo->invflags & IPT_CONNTRACK_EXPIRES)
Michael Schwendtdfba3ac2002-12-05 20:20:29 +0000486 printf("! ");
Marc Boucher5054e852002-01-19 10:59:12 +0000487
488 if (sinfo->expires_max == sinfo->expires_min)
489 printf("%lu ", sinfo->expires_min);
490 else
491 printf("%lu:%lu ", sinfo->expires_min, sinfo->expires_max);
492 }
493}
494
495/* Prints out the matchinfo. */
496static void
497print(const struct ipt_ip *ip,
498 const struct ipt_entry_match *match,
499 int numeric)
500{
501 matchinfo_print(ip, match, numeric, "");
502}
503
504/* Saves the matchinfo in parsable form to stdout. */
505static void save(const struct ipt_ip *ip, const struct ipt_entry_match *match)
506{
Joszef Kadlecsikdb503f92004-05-05 10:10:33 +0000507 matchinfo_print(ip, match, 1, "--");
Marc Boucher5054e852002-01-19 10:59:12 +0000508}
509
510static
511struct iptables_match conntrack
512= { NULL,
513 "conntrack",
Harald Welte80fe35d2002-05-29 13:08:15 +0000514 IPTABLES_VERSION,
Marc Boucher5054e852002-01-19 10:59:12 +0000515 IPT_ALIGN(sizeof(struct ipt_conntrack_info)),
516 IPT_ALIGN(sizeof(struct ipt_conntrack_info)),
517 &help,
518 &init,
519 &parse,
520 &final_check,
521 &print,
522 &save,
523 opts
524};
525
526void _init(void)
527{
528 register_match(&conntrack);
529}