5521684842cbf29aae60155dca492af90c9df62d
[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                 fputc('!', stdout);
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_ORIGSRC) {
446                 printf("%sctorigsrc ", optpfx);
447
448                 print_addr(
449                     (struct in_addr *)&sinfo->tuple[IP_CT_DIR_ORIGINAL].src.ip,
450                     &sinfo->sipmsk[IP_CT_DIR_ORIGINAL],
451                     sinfo->invflags & IPT_CONNTRACK_ORIGSRC,
452                     numeric);
453         }
454
455         if(sinfo->flags & IPT_CONNTRACK_ORIGDST) {
456                 printf("%sctorigdst ", optpfx);
457
458                 print_addr(
459                     (struct in_addr *)&sinfo->tuple[IP_CT_DIR_ORIGINAL].dst.ip,
460                     &sinfo->dipmsk[IP_CT_DIR_ORIGINAL],
461                     sinfo->invflags & IPT_CONNTRACK_ORIGDST,
462                     numeric);
463         }
464
465         if(sinfo->flags & IPT_CONNTRACK_REPLSRC) {
466                 printf("%sctreplsrc ", optpfx);
467
468                 print_addr(
469                     (struct in_addr *)&sinfo->tuple[IP_CT_DIR_REPLY].src.ip,
470                     &sinfo->sipmsk[IP_CT_DIR_REPLY],
471                     sinfo->invflags & IPT_CONNTRACK_REPLSRC,
472                     numeric);
473         }
474
475         if(sinfo->flags & IPT_CONNTRACK_REPLDST) {
476                 printf("%sctrepldst ", optpfx);
477
478                 print_addr(
479                     (struct in_addr *)&sinfo->tuple[IP_CT_DIR_REPLY].dst.ip,
480                     &sinfo->dipmsk[IP_CT_DIR_REPLY],
481                     sinfo->invflags & IPT_CONNTRACK_REPLDST,
482                     numeric);
483         }
484
485         if(sinfo->flags & IPT_CONNTRACK_STATUS) {
486                 printf("%sctstatus ", optpfx);
487                 if (sinfo->invflags & IPT_CONNTRACK_STATUS)
488                         printf("! ");
489                 print_status(sinfo->statusmask);
490         }
491
492         if(sinfo->flags & IPT_CONNTRACK_EXPIRES) {
493                 printf("%sctexpire ", optpfx);
494                 if (sinfo->invflags & IPT_CONNTRACK_EXPIRES)
495                         printf("! ");
496
497 #ifdef KERNEL_64_USERSPACE_32
498                 if (sinfo->expires_max == sinfo->expires_min)
499                         printf("%llu ", sinfo->expires_min);
500                 else
501                         printf("%llu:%llu ", sinfo->expires_min, sinfo->expires_max);
502 #else
503                 if (sinfo->expires_max == sinfo->expires_min)
504                         printf("%lu ", sinfo->expires_min);
505                 else
506                         printf("%lu:%lu ", sinfo->expires_min, sinfo->expires_max);
507 #endif
508         }
509 }
510
511 /* Prints out the matchinfo. */
512 static void
513 print(const struct ipt_ip *ip,
514       const struct ipt_entry_match *match,
515       int numeric)
516 {
517         matchinfo_print(ip, match, numeric, "");
518 }
519
520 /* Saves the matchinfo in parsable form to stdout. */
521 static void save(const struct ipt_ip *ip, const struct ipt_entry_match *match)
522 {
523         matchinfo_print(ip, match, 1, "--");
524 }
525
526 static struct iptables_match conntrack = { 
527         .next           = NULL,
528         .name           = "conntrack",
529         .version        = IPTABLES_VERSION,
530         .size           = IPT_ALIGN(sizeof(struct ipt_conntrack_info)),
531         .userspacesize  = IPT_ALIGN(sizeof(struct ipt_conntrack_info)),
532         .help           = &help,
533         .parse          = &parse,
534         .final_check    = &final_check,
535         .print          = &print,
536         .save           = &save,
537         .extra_opts     = opts
538 };
539
540 void _init(void)
541 {
542         register_match(&conntrack);
543 }