blob: 3f322d0125e67fd281c6809c4b438af4c3b584bd [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"
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;
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':
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 }
348 if (statemask & IPT_CONNTRACK_STATE_SNAT) {
349 printf("%sSNAT", sep);
350 sep = ",";
351 }
352 if (statemask & IPT_CONNTRACK_STATE_DNAT) {
353 printf("%sDNAT", sep);
354 sep = ",";
355 }
356 printf(" ");
357}
358
359static void
360print_status(unsigned int statusmask)
361{
362 const char *sep = "";
363
364 if (statusmask & IPS_EXPECTED) {
365 printf("%sEXPECTED", sep);
366 sep = ",";
367 }
368 if (statusmask & IPS_SEEN_REPLY) {
369 printf("%sSEEN_REPLY", sep);
370 sep = ",";
371 }
372 if (statusmask & IPS_ASSURED) {
373 printf("%sASSURED", sep);
374 sep = ",";
375 }
376 if (statusmask == 0) {
377 printf("%sNONE", sep);
378 sep = ",";
379 }
380 printf(" ");
381}
382
383static void
384print_addr(struct in_addr *addr, struct in_addr *mask, int inv, int numeric)
385{
386 char buf[BUFSIZ];
387
388 if (inv)
389 fputc('!', stdout);
390
391 if (mask->s_addr == 0L && !numeric)
392 printf("%s ", "anywhere");
393 else {
394 if (numeric)
395 sprintf(buf, "%s", addr_to_dotted(addr));
396 else
397 sprintf(buf, "%s", addr_to_anyname(addr));
398 strcat(buf, mask_to_dotted(mask));
399 printf("%s ", buf);
400 }
401}
402
403/* Saves the matchinfo in parsable form to stdout. */
404static void
405matchinfo_print(const struct ipt_ip *ip, const struct ipt_entry_match *match, int numeric, const char *optpfx)
406{
407 struct ipt_conntrack_info *sinfo = (struct ipt_conntrack_info *)match->data;
408
409 if(sinfo->flags & IPT_CONNTRACK_STATE) {
410 printf("%sctstate ", optpfx);
411 if (sinfo->invflags & IPT_CONNTRACK_STATE)
Michael Schwendtdfba3ac2002-12-05 20:20:29 +0000412 printf("! ");
Marc Boucher5054e852002-01-19 10:59:12 +0000413 print_state(sinfo->statemask);
414 }
415
416 if(sinfo->flags & IPT_CONNTRACK_ORIGSRC) {
417 printf("%sctorigsrc ", optpfx);
418
419 print_addr(
420 (struct in_addr *)&sinfo->tuple[IP_CT_DIR_ORIGINAL].src.ip,
421 &sinfo->sipmsk[IP_CT_DIR_ORIGINAL],
422 sinfo->invflags & IPT_CONNTRACK_ORIGSRC,
423 numeric);
424 }
425
426 if(sinfo->flags & IPT_CONNTRACK_ORIGDST) {
427 printf("%sctorigdst ", optpfx);
428
429 print_addr(
430 (struct in_addr *)&sinfo->tuple[IP_CT_DIR_ORIGINAL].dst.ip,
431 &sinfo->dipmsk[IP_CT_DIR_ORIGINAL],
432 sinfo->invflags & IPT_CONNTRACK_ORIGDST,
433 numeric);
434 }
435
436 if(sinfo->flags & IPT_CONNTRACK_REPLSRC) {
Lutz Preßlerd0ae04e2003-03-04 14:50:50 +0000437 printf("%sctreplsrc ", optpfx);
Marc Boucher5054e852002-01-19 10:59:12 +0000438
439 print_addr(
440 (struct in_addr *)&sinfo->tuple[IP_CT_DIR_REPLY].src.ip,
441 &sinfo->sipmsk[IP_CT_DIR_REPLY],
442 sinfo->invflags & IPT_CONNTRACK_REPLSRC,
443 numeric);
444 }
445
446 if(sinfo->flags & IPT_CONNTRACK_REPLDST) {
Lutz Preßlerd0ae04e2003-03-04 14:50:50 +0000447 printf("%sctrepldst ", optpfx);
Marc Boucher5054e852002-01-19 10:59:12 +0000448
449 print_addr(
450 (struct in_addr *)&sinfo->tuple[IP_CT_DIR_REPLY].dst.ip,
451 &sinfo->dipmsk[IP_CT_DIR_REPLY],
452 sinfo->invflags & IPT_CONNTRACK_REPLDST,
453 numeric);
454 }
455
456 if(sinfo->flags & IPT_CONNTRACK_STATUS) {
457 printf("%sctstatus ", optpfx);
458 if (sinfo->invflags & IPT_CONNTRACK_STATE)
Michael Schwendtdfba3ac2002-12-05 20:20:29 +0000459 printf("! ");
Marc Boucher5054e852002-01-19 10:59:12 +0000460 print_status(sinfo->statusmask);
461 }
462
463 if(sinfo->flags & IPT_CONNTRACK_EXPIRES) {
464 printf("%sctexpire ", optpfx);
465 if (sinfo->invflags & IPT_CONNTRACK_EXPIRES)
Michael Schwendtdfba3ac2002-12-05 20:20:29 +0000466 printf("! ");
Marc Boucher5054e852002-01-19 10:59:12 +0000467
468 if (sinfo->expires_max == sinfo->expires_min)
469 printf("%lu ", sinfo->expires_min);
470 else
471 printf("%lu:%lu ", sinfo->expires_min, sinfo->expires_max);
472 }
473}
474
475/* Prints out the matchinfo. */
476static void
477print(const struct ipt_ip *ip,
478 const struct ipt_entry_match *match,
479 int numeric)
480{
481 matchinfo_print(ip, match, numeric, "");
482}
483
484/* Saves the matchinfo in parsable form to stdout. */
485static void save(const struct ipt_ip *ip, const struct ipt_entry_match *match)
486{
487 matchinfo_print(ip, match, 0, "--");
488}
489
490static
491struct iptables_match conntrack
492= { NULL,
493 "conntrack",
Harald Welte80fe35d2002-05-29 13:08:15 +0000494 IPTABLES_VERSION,
Marc Boucher5054e852002-01-19 10:59:12 +0000495 IPT_ALIGN(sizeof(struct ipt_conntrack_info)),
496 IPT_ALIGN(sizeof(struct ipt_conntrack_info)),
497 &help,
498 &init,
499 &parse,
500 &final_check,
501 &print,
502 &save,
503 opts
504};
505
506void _init(void)
507{
508 register_match(&conntrack);
509}