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