Merge to iptables-1.3.5
[iptables.git] / extensions / libipt_conntrack.c
1 /* 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 /* For 64bit kernel / 32bit userspace */
15 #include "../include/linux/netfilter_ipv4/ipt_conntrack.h"
16
17 #ifndef IPT_CONNTRACK_STATE_UNTRACKED
18 #define IPT_CONNTRACK_STATE_UNTRACKED (1 << (IP_CT_NUMBER + 3))
19 #endif
20
21 /* Function which prints out usage message. */
22 static void
23 help(void)
24 {
25         printf(
26 "conntrack match v%s options:\n"
27 " [!] --ctstate [INVALID|ESTABLISHED|NEW|RELATED|UNTRACKED|SNAT|DNAT][,...]\n"
28 "                               State(s) to match\n"
29 " [!] --ctproto proto           Protocol to match; by number or name, eg. `tcp'\n"
30 "     --ctorigsrc  [!] address[/mask]\n"
31 "                               Original source specification\n"
32 "     --ctorigdst  [!] address[/mask]\n"
33 "                               Original destination specification\n"
34 "     --ctreplsrc  [!] address[/mask]\n"
35 "                               Reply source specification\n"
36 "     --ctrepldst  [!] address[/mask]\n"
37 "                               Reply destination specification\n"
38 " [!] --ctstatus [NONE|EXPECTED|SEEN_REPLY|ASSURED|CONFIRMED][,...]\n"
39 "                               Status(es) to match\n"
40 " [!] --ctexpire time[:time]    Match remaining lifetime in seconds against\n"
41 "                               value or range of values (inclusive)\n"
42 "\n", IPTABLES_VERSION);
43 }
44
45
46
47 static struct option opts[] = {
48         { "ctstate", 1, 0, '1' },
49         { "ctproto", 1, 0, '2' },
50         { "ctorigsrc", 1, 0, '3' },
51         { "ctorigdst", 1, 0, '4' },
52         { "ctreplsrc", 1, 0, '5' },
53         { "ctrepldst", 1, 0, '6' },
54         { "ctstatus", 1, 0, '7' },
55         { "ctexpire", 1, 0, '8' },
56         {0}
57 };
58
59 static int
60 parse_state(const char *state, size_t strlen, struct ipt_conntrack_info *sinfo)
61 {
62         if (strncasecmp(state, "INVALID", strlen) == 0)
63                 sinfo->statemask |= IPT_CONNTRACK_STATE_INVALID;
64         else if (strncasecmp(state, "NEW", strlen) == 0)
65                 sinfo->statemask |= IPT_CONNTRACK_STATE_BIT(IP_CT_NEW);
66         else if (strncasecmp(state, "ESTABLISHED", strlen) == 0)
67                 sinfo->statemask |= IPT_CONNTRACK_STATE_BIT(IP_CT_ESTABLISHED);
68         else if (strncasecmp(state, "RELATED", strlen) == 0)
69                 sinfo->statemask |= IPT_CONNTRACK_STATE_BIT(IP_CT_RELATED);
70         else if (strncasecmp(state, "UNTRACKED", strlen) == 0)
71                 sinfo->statemask |= IPT_CONNTRACK_STATE_UNTRACKED;
72         else if (strncasecmp(state, "SNAT", strlen) == 0)
73                 sinfo->statemask |= IPT_CONNTRACK_STATE_SNAT;
74         else if (strncasecmp(state, "DNAT", strlen) == 0)
75                 sinfo->statemask |= IPT_CONNTRACK_STATE_DNAT;
76         else
77                 return 0;
78         return 1;
79 }
80
81 static void
82 parse_states(const char *arg, struct ipt_conntrack_info *sinfo)
83 {
84         const char *comma;
85
86         while ((comma = strchr(arg, ',')) != NULL) {
87                 if (comma == arg || !parse_state(arg, comma-arg, sinfo))
88                         exit_error(PARAMETER_PROBLEM, "Bad ctstate `%s'", arg);
89                 arg = comma+1;
90         }
91
92         if (strlen(arg) == 0 || !parse_state(arg, strlen(arg), sinfo))
93                 exit_error(PARAMETER_PROBLEM, "Bad ctstate `%s'", arg);
94 }
95
96 static int
97 parse_status(const char *status, size_t strlen, struct ipt_conntrack_info *sinfo)
98 {
99         if (strncasecmp(status, "NONE", strlen) == 0)
100                 sinfo->statusmask |= 0;
101         else if (strncasecmp(status, "EXPECTED", strlen) == 0)
102                 sinfo->statusmask |= IPS_EXPECTED;
103         else if (strncasecmp(status, "SEEN_REPLY", strlen) == 0)
104                 sinfo->statusmask |= IPS_SEEN_REPLY;
105         else if (strncasecmp(status, "ASSURED", strlen) == 0)
106                 sinfo->statusmask |= IPS_ASSURED;
107 #ifdef IPS_CONFIRMED
108         else if (strncasecmp(status, "CONFIRMED", strlen) == 0)
109                 sinfo->stausmask |= IPS_CONFIRMED;
110 #endif
111         else
112                 return 0;
113         return 1;
114 }
115
116 static void
117 parse_statuses(const char *arg, struct ipt_conntrack_info *sinfo)
118 {
119         const char *comma;
120
121         while ((comma = strchr(arg, ',')) != NULL) {
122                 if (comma == arg || !parse_status(arg, comma-arg, sinfo))
123                         exit_error(PARAMETER_PROBLEM, "Bad ctstatus `%s'", arg);
124                 arg = comma+1;
125         }
126
127         if (strlen(arg) == 0 || !parse_status(arg, strlen(arg), sinfo))
128                 exit_error(PARAMETER_PROBLEM, "Bad ctstatus `%s'", arg);
129 }
130
131 #ifdef KERNEL_64_USERSPACE_32
132 static unsigned long long
133 parse_expire(const char *s)
134 {
135         unsigned long long len;
136         
137         if (string_to_number_ll(s, 0, 0, &len) == -1)
138                 exit_error(PARAMETER_PROBLEM, "expire value invalid: `%s'\n", s);
139         else
140                 return len;
141 }
142 #else
143 static unsigned long
144 parse_expire(const char *s)
145 {
146         unsigned int len;
147         
148         if (string_to_number(s, 0, 0, &len) == -1)
149                 exit_error(PARAMETER_PROBLEM, "expire value invalid: `%s'\n", s);
150         else
151                 return len;
152 }
153 #endif
154
155 /* If a single value is provided, min and max are both set to the value */
156 static void
157 parse_expires(const char *s, struct ipt_conntrack_info *sinfo)
158 {
159         char *buffer;
160         char *cp;
161
162         buffer = strdup(s);
163         if ((cp = strchr(buffer, ':')) == NULL)
164                 sinfo->expires_min = sinfo->expires_max = parse_expire(buffer);
165         else {
166                 *cp = '\0';
167                 cp++;
168
169                 sinfo->expires_min = buffer[0] ? parse_expire(buffer) : 0;
170                 sinfo->expires_max = cp[0] ? parse_expire(cp) : -1;
171         }
172         free(buffer);
173         
174         if (sinfo->expires_min > sinfo->expires_max)
175                 exit_error(PARAMETER_PROBLEM,
176 #ifdef KERNEL_64_USERSPACE_32
177                            "expire min. range value `%llu' greater than max. "
178                            "range value `%llu'", sinfo->expires_min, sinfo->expires_max);
179 #else
180                            "expire min. range value `%lu' greater than max. "
181                            "range value `%lu'", sinfo->expires_min, sinfo->expires_max);
182 #endif
183 }
184
185 /* Function which parses command options; returns true if it
186    ate an option */
187 static int
188 parse(int c, char **argv, int invert, unsigned int *flags,
189       const struct ipt_entry *entry,
190       unsigned int *nfcache,
191       struct ipt_entry_match **match)
192 {
193         struct ipt_conntrack_info *sinfo = (struct ipt_conntrack_info *)(*match)->data;
194         char *protocol = NULL;
195         unsigned int naddrs = 0;
196         struct in_addr *addrs = NULL;
197
198
199         switch (c) {
200         case '1':
201                 check_inverse(optarg, &invert, &optind, 0);
202
203                 parse_states(argv[optind-1], sinfo);
204                 if (invert) {
205                         sinfo->invflags |= IPT_CONNTRACK_STATE;
206                 }
207                 sinfo->flags |= IPT_CONNTRACK_STATE;
208                 break;
209
210         case '2':
211                 check_inverse(optarg, &invert, &optind, 0);
212
213                 if(invert)
214                         sinfo->invflags |= IPT_CONNTRACK_PROTO;
215
216                 /* Canonicalize into lower case */
217                 for (protocol = argv[optind-1]; *protocol; protocol++)
218                         *protocol = tolower(*protocol);
219
220                 protocol = argv[optind-1];
221                 sinfo->tuple[IP_CT_DIR_ORIGINAL].dst.protonum = parse_protocol(protocol);
222
223                 if (sinfo->tuple[IP_CT_DIR_ORIGINAL].dst.protonum == 0
224                     && (sinfo->invflags & IPT_INV_PROTO))
225                         exit_error(PARAMETER_PROBLEM,
226                                    "rule would never match protocol");
227
228                 sinfo->flags |= IPT_CONNTRACK_PROTO;
229                 break;
230
231         case '3':
232                 check_inverse(optarg, &invert, &optind, 9);
233
234                 if (invert)
235                         sinfo->invflags |= IPT_CONNTRACK_ORIGSRC;
236
237                 parse_hostnetworkmask(argv[optind-1], &addrs,
238                                         &sinfo->sipmsk[IP_CT_DIR_ORIGINAL],
239                                         &naddrs);
240                 if(naddrs > 1)
241                         exit_error(PARAMETER_PROBLEM,
242                                 "multiple IP addresses not allowed");
243
244                 if(naddrs == 1) {
245                         sinfo->tuple[IP_CT_DIR_ORIGINAL].src.ip = addrs[0].s_addr;
246                 }
247
248                 sinfo->flags |= IPT_CONNTRACK_ORIGSRC;
249                 break;
250
251         case '4':
252                 check_inverse(optarg, &invert, &optind, 0);
253
254                 if (invert)
255                         sinfo->invflags |= IPT_CONNTRACK_ORIGDST;
256
257                 parse_hostnetworkmask(argv[optind-1], &addrs,
258                                         &sinfo->dipmsk[IP_CT_DIR_ORIGINAL],
259                                         &naddrs);
260                 if(naddrs > 1)
261                         exit_error(PARAMETER_PROBLEM,
262                                 "multiple IP addresses not allowed");
263
264                 if(naddrs == 1) {
265                         sinfo->tuple[IP_CT_DIR_ORIGINAL].dst.ip = addrs[0].s_addr;
266                 }
267
268                 sinfo->flags |= IPT_CONNTRACK_ORIGDST;
269                 break;
270
271         case '5':
272                 check_inverse(optarg, &invert, &optind, 0);
273
274                 if (invert)
275                         sinfo->invflags |= IPT_CONNTRACK_REPLSRC;
276
277                 parse_hostnetworkmask(argv[optind-1], &addrs,
278                                         &sinfo->sipmsk[IP_CT_DIR_REPLY],
279                                         &naddrs);
280                 if(naddrs > 1)
281                         exit_error(PARAMETER_PROBLEM,
282                                 "multiple IP addresses not allowed");
283
284                 if(naddrs == 1) {
285                         sinfo->tuple[IP_CT_DIR_REPLY].src.ip = addrs[0].s_addr;
286                 }
287
288                 sinfo->flags |= IPT_CONNTRACK_REPLSRC;
289                 break;
290
291         case '6':
292                 check_inverse(optarg, &invert, &optind, 0);
293
294                 if (invert)
295                         sinfo->invflags |= IPT_CONNTRACK_REPLDST;
296
297                 parse_hostnetworkmask(argv[optind-1], &addrs,
298                                         &sinfo->dipmsk[IP_CT_DIR_REPLY],
299                                         &naddrs);
300                 if(naddrs > 1)
301                         exit_error(PARAMETER_PROBLEM,
302                                 "multiple IP addresses not allowed");
303
304                 if(naddrs == 1) {
305                         sinfo->tuple[IP_CT_DIR_REPLY].dst.ip = addrs[0].s_addr;
306                 }
307
308                 sinfo->flags |= IPT_CONNTRACK_REPLDST;
309                 break;
310
311         case '7':
312                 check_inverse(optarg, &invert, &optind, 0);
313
314                 parse_statuses(argv[optind-1], sinfo);
315                 if (invert) {
316                         sinfo->invflags |= IPT_CONNTRACK_STATUS;
317                 }
318                 sinfo->flags |= IPT_CONNTRACK_STATUS;
319                 break;
320
321         case '8':
322                 check_inverse(optarg, &invert, &optind, 0);
323
324                 parse_expires(argv[optind-1], sinfo);
325                 if (invert) {
326                         sinfo->invflags |= IPT_CONNTRACK_EXPIRES;
327                 }
328                 sinfo->flags |= IPT_CONNTRACK_EXPIRES;
329                 break;
330
331         default:
332                 return 0;
333         }
334
335         *flags = sinfo->flags;
336         return 1;
337 }
338
339 static void
340 final_check(unsigned int flags)
341 {
342         if (!flags)
343                 exit_error(PARAMETER_PROBLEM, "You must specify one or more options");
344 }
345
346 static void
347 print_state(unsigned int statemask)
348 {
349         const char *sep = "";
350
351         if (statemask & IPT_CONNTRACK_STATE_INVALID) {
352                 printf("%sINVALID", sep);
353                 sep = ",";
354         }
355         if (statemask & IPT_CONNTRACK_STATE_BIT(IP_CT_NEW)) {
356                 printf("%sNEW", sep);
357                 sep = ",";
358         }
359         if (statemask & IPT_CONNTRACK_STATE_BIT(IP_CT_RELATED)) {
360                 printf("%sRELATED", sep);
361                 sep = ",";
362         }
363         if (statemask & IPT_CONNTRACK_STATE_BIT(IP_CT_ESTABLISHED)) {
364                 printf("%sESTABLISHED", sep);
365                 sep = ",";
366         }
367         if (statemask & IPT_CONNTRACK_STATE_UNTRACKED) {
368                 printf("%sUNTRACKED", sep);
369                 sep = ",";
370         }
371         if (statemask & IPT_CONNTRACK_STATE_SNAT) {
372                 printf("%sSNAT", sep);
373                 sep = ",";
374         }
375         if (statemask & IPT_CONNTRACK_STATE_DNAT) {
376                 printf("%sDNAT", sep);
377                 sep = ",";
378         }
379         printf(" ");
380 }
381
382 static void
383 print_status(unsigned int statusmask)
384 {
385         const char *sep = "";
386
387         if (statusmask & IPS_EXPECTED) {
388                 printf("%sEXPECTED", sep);
389                 sep = ",";
390         }
391         if (statusmask & IPS_SEEN_REPLY) {
392                 printf("%sSEEN_REPLY", sep);
393                 sep = ",";
394         }
395         if (statusmask & IPS_ASSURED) {
396                 printf("%sASSURED", sep);
397                 sep = ",";
398         }
399 #ifdef IPS_CONFIRMED
400         if (statusmask & IPS_CONFIRMED) {
401                 printf("%sCONFIRMED", sep);
402                 sep =",";
403         }
404 #endif
405         if (statusmask == 0) {
406                 printf("%sNONE", sep);
407                 sep = ",";
408         }
409         printf(" ");
410 }
411
412 static void
413 print_addr(struct in_addr *addr, struct in_addr *mask, int inv, int numeric)
414 {
415         char buf[BUFSIZ];
416
417         if (inv) 
418                 printf("! ");
419
420         if (mask->s_addr == 0L && !numeric)
421                 printf("%s ", "anywhere");
422         else {
423                 if (numeric)
424                         sprintf(buf, "%s", addr_to_dotted(addr));
425                 else
426                         sprintf(buf, "%s", addr_to_anyname(addr));
427                 strcat(buf, mask_to_dotted(mask));
428                 printf("%s ", buf);
429         }
430 }
431
432 /* Saves the matchinfo in parsable form to stdout. */
433 static void
434 matchinfo_print(const struct ipt_ip *ip, const struct ipt_entry_match *match, int numeric, const char *optpfx)
435 {
436         struct ipt_conntrack_info *sinfo = (struct ipt_conntrack_info *)match->data;
437
438         if(sinfo->flags & IPT_CONNTRACK_STATE) {
439                 printf("%sctstate ", optpfx);
440                 if (sinfo->invflags & IPT_CONNTRACK_STATE)
441                         printf("! ");
442                 print_state(sinfo->statemask);
443         }
444
445         if(sinfo->flags & IPT_CONNTRACK_PROTO) {
446                 printf("%sctproto ", optpfx);
447                 if (sinfo->invflags & IPT_CONNTRACK_PROTO)
448                         printf("! ");
449                 printf("%u ", sinfo->tuple[IP_CT_DIR_ORIGINAL].dst.protonum);
450         }
451
452         if(sinfo->flags & IPT_CONNTRACK_ORIGSRC) {
453                 printf("%sctorigsrc ", optpfx);
454
455                 print_addr(
456                     (struct in_addr *)&sinfo->tuple[IP_CT_DIR_ORIGINAL].src.ip,
457                     &sinfo->sipmsk[IP_CT_DIR_ORIGINAL],
458                     sinfo->invflags & IPT_CONNTRACK_ORIGSRC,
459                     numeric);
460         }
461
462         if(sinfo->flags & IPT_CONNTRACK_ORIGDST) {
463                 printf("%sctorigdst ", optpfx);
464
465                 print_addr(
466                     (struct in_addr *)&sinfo->tuple[IP_CT_DIR_ORIGINAL].dst.ip,
467                     &sinfo->dipmsk[IP_CT_DIR_ORIGINAL],
468                     sinfo->invflags & IPT_CONNTRACK_ORIGDST,
469                     numeric);
470         }
471
472         if(sinfo->flags & IPT_CONNTRACK_REPLSRC) {
473                 printf("%sctreplsrc ", optpfx);
474
475                 print_addr(
476                     (struct in_addr *)&sinfo->tuple[IP_CT_DIR_REPLY].src.ip,
477                     &sinfo->sipmsk[IP_CT_DIR_REPLY],
478                     sinfo->invflags & IPT_CONNTRACK_REPLSRC,
479                     numeric);
480         }
481
482         if(sinfo->flags & IPT_CONNTRACK_REPLDST) {
483                 printf("%sctrepldst ", optpfx);
484
485                 print_addr(
486                     (struct in_addr *)&sinfo->tuple[IP_CT_DIR_REPLY].dst.ip,
487                     &sinfo->dipmsk[IP_CT_DIR_REPLY],
488                     sinfo->invflags & IPT_CONNTRACK_REPLDST,
489                     numeric);
490         }
491
492         if(sinfo->flags & IPT_CONNTRACK_STATUS) {
493                 printf("%sctstatus ", optpfx);
494                 if (sinfo->invflags & IPT_CONNTRACK_STATUS)
495                         printf("! ");
496                 print_status(sinfo->statusmask);
497         }
498
499         if(sinfo->flags & IPT_CONNTRACK_EXPIRES) {
500                 printf("%sctexpire ", optpfx);
501                 if (sinfo->invflags & IPT_CONNTRACK_EXPIRES)
502                         printf("! ");
503
504 #ifdef KERNEL_64_USERSPACE_32
505                 if (sinfo->expires_max == sinfo->expires_min)
506                         printf("%llu ", sinfo->expires_min);
507                 else
508                         printf("%llu:%llu ", sinfo->expires_min, sinfo->expires_max);
509 #else
510                 if (sinfo->expires_max == sinfo->expires_min)
511                         printf("%lu ", sinfo->expires_min);
512                 else
513                         printf("%lu:%lu ", sinfo->expires_min, sinfo->expires_max);
514 #endif
515         }
516 }
517
518 /* Prints out the matchinfo. */
519 static void
520 print(const struct ipt_ip *ip,
521       const struct ipt_entry_match *match,
522       int numeric)
523 {
524         matchinfo_print(ip, match, numeric, "");
525 }
526
527 /* Saves the matchinfo in parsable form to stdout. */
528 static void save(const struct ipt_ip *ip, const struct ipt_entry_match *match)
529 {
530         matchinfo_print(ip, match, 1, "--");
531 }
532
533 static struct iptables_match conntrack = { 
534         .next           = NULL,
535         .name           = "conntrack",
536         .version        = IPTABLES_VERSION,
537         .size           = IPT_ALIGN(sizeof(struct ipt_conntrack_info)),
538         .userspacesize  = IPT_ALIGN(sizeof(struct ipt_conntrack_info)),
539         .help           = &help,
540         .parse          = &parse,
541         .final_check    = &final_check,
542         .print          = &print,
543         .save           = &save,
544         .extra_opts     = opts
545 };
546
547 void _init(void)
548 {
549         register_match(&conntrack);
550 }