Home | History | Annotate | Download | only in extensions
      1 /* Shared library add-on to iptables to add TCP support. */
      2 #include <stdio.h>
      3 #include <netdb.h>
      4 #include <string.h>
      5 #include <stdlib.h>
      6 #include <getopt.h>
      7 #include <iptables.h>
      8 #include <linux/netfilter_ipv4/ip_tables.h>
      9 #include <netinet/in.h>
     10 
     11 /* Function which prints out usage message. */
     12 static void
     13 help(void)
     14 {
     15 	printf(
     16 "TCP v%s options:\n"
     17 " --tcp-flags [!] mask comp	match when TCP flags & mask == comp\n"
     18 "				(Flags: SYN ACK FIN RST URG PSH ALL NONE)\n"
     19 "[!] --syn			match when only SYN flag set\n"
     20 "				(equivalent to --tcp-flags SYN,RST,ACK SYN)\n"
     21 " --source-port [!] port[:port]\n"
     22 " --sport ...\n"
     23 "				match source port(s)\n"
     24 " --destination-port [!] port[:port]\n"
     25 " --dport ...\n"
     26 "				match destination port(s)\n"
     27 " --tcp-option [!] number       match if TCP option set\n\n",
     28 IPTABLES_VERSION);
     29 }
     30 
     31 static struct option opts[] = {
     32 	{ "source-port", 1, 0, '1' },
     33 	{ "sport", 1, 0, '1' }, /* synonym */
     34 	{ "destination-port", 1, 0, '2' },
     35 	{ "dport", 1, 0, '2' }, /* synonym */
     36 	{ "syn", 0, 0, '3' },
     37 	{ "tcp-flags", 1, 0, '4' },
     38 	{ "tcp-option", 1, 0, '5' },
     39 	{0}
     40 };
     41 
     42 static void
     43 parse_tcp_ports(const char *portstring, u_int16_t *ports)
     44 {
     45 	char *buffer;
     46 	char *cp;
     47 
     48 	buffer = strdup(portstring);
     49 	if ((cp = strchr(buffer, ':')) == NULL)
     50 		ports[0] = ports[1] = parse_port(buffer, "tcp");
     51 	else {
     52 		*cp = '\0';
     53 		cp++;
     54 
     55 		ports[0] = buffer[0] ? parse_port(buffer, "tcp") : 0;
     56 		ports[1] = cp[0] ? parse_port(cp, "tcp") : 0xFFFF;
     57 
     58 		if (ports[0] > ports[1])
     59 			exit_error(PARAMETER_PROBLEM,
     60 				   "invalid portrange (min > max)");
     61 	}
     62 	free(buffer);
     63 }
     64 
     65 struct tcp_flag_names {
     66 	const char *name;
     67 	unsigned int flag;
     68 };
     69 
     70 static struct tcp_flag_names tcp_flag_names[]
     71 = { { "FIN", 0x01 },
     72     { "SYN", 0x02 },
     73     { "RST", 0x04 },
     74     { "PSH", 0x08 },
     75     { "ACK", 0x10 },
     76     { "URG", 0x20 },
     77     { "ALL", 0x3F },
     78     { "NONE", 0 },
     79 };
     80 
     81 static unsigned int
     82 parse_tcp_flag(const char *flags)
     83 {
     84 	unsigned int ret = 0;
     85 	char *ptr;
     86 	char *buffer;
     87 
     88 	buffer = strdup(flags);
     89 
     90 	for (ptr = strtok(buffer, ","); ptr; ptr = strtok(NULL, ",")) {
     91 		unsigned int i;
     92 		for (i = 0;
     93 		     i < sizeof(tcp_flag_names)/sizeof(struct tcp_flag_names);
     94 		     i++) {
     95 			if (strcasecmp(tcp_flag_names[i].name, ptr) == 0) {
     96 				ret |= tcp_flag_names[i].flag;
     97 				break;
     98 			}
     99 		}
    100 		if (i == sizeof(tcp_flag_names)/sizeof(struct tcp_flag_names))
    101 			exit_error(PARAMETER_PROBLEM,
    102 				   "Unknown TCP flag `%s'", ptr);
    103 		}
    104 
    105 	free(buffer);
    106 	return ret;
    107 }
    108 
    109 static void
    110 parse_tcp_flags(struct ipt_tcp *tcpinfo,
    111 		const char *mask,
    112 		const char *cmp,
    113 		int invert)
    114 {
    115 	tcpinfo->flg_mask = parse_tcp_flag(mask);
    116 	tcpinfo->flg_cmp = parse_tcp_flag(cmp);
    117 
    118 	if (invert)
    119 		tcpinfo->invflags |= IPT_TCP_INV_FLAGS;
    120 }
    121 
    122 static void
    123 parse_tcp_option(const char *option, u_int8_t *result)
    124 {
    125 	unsigned int ret;
    126 
    127 	if (string_to_number(option, 1, 255, &ret) == -1)
    128 		exit_error(PARAMETER_PROBLEM, "Bad TCP option `%s'", option);
    129 
    130 	*result = (u_int8_t)ret;
    131 }
    132 
    133 /* Initialize the match. */
    134 static void
    135 init(struct ipt_entry_match *m, unsigned int *nfcache)
    136 {
    137 	struct ipt_tcp *tcpinfo = (struct ipt_tcp *)m->data;
    138 
    139 	tcpinfo->spts[1] = tcpinfo->dpts[1] = 0xFFFF;
    140 }
    141 
    142 #define TCP_SRC_PORTS 0x01
    143 #define TCP_DST_PORTS 0x02
    144 #define TCP_FLAGS 0x04
    145 #define TCP_OPTION	0x08
    146 
    147 /* Function which parses command options; returns true if it
    148    ate an option. */
    149 static int
    150 parse(int c, char **argv, int invert, unsigned int *flags,
    151       const struct ipt_entry *entry,
    152       unsigned int *nfcache,
    153       struct ipt_entry_match **match)
    154 {
    155 	struct ipt_tcp *tcpinfo = (struct ipt_tcp *)(*match)->data;
    156 
    157 	switch (c) {
    158 	case '1':
    159 		if (*flags & TCP_SRC_PORTS)
    160 			exit_error(PARAMETER_PROBLEM,
    161 				   "Only one `--source-port' allowed");
    162 		check_inverse(optarg, &invert, &optind, 0);
    163 		parse_tcp_ports(argv[optind-1], tcpinfo->spts);
    164 		if (invert)
    165 			tcpinfo->invflags |= IPT_TCP_INV_SRCPT;
    166 		*flags |= TCP_SRC_PORTS;
    167 		break;
    168 
    169 	case '2':
    170 		if (*flags & TCP_DST_PORTS)
    171 			exit_error(PARAMETER_PROBLEM,
    172 				   "Only one `--destination-port' allowed");
    173 		check_inverse(optarg, &invert, &optind, 0);
    174 		parse_tcp_ports(argv[optind-1], tcpinfo->dpts);
    175 		if (invert)
    176 			tcpinfo->invflags |= IPT_TCP_INV_DSTPT;
    177 		*flags |= TCP_DST_PORTS;
    178 		break;
    179 
    180 	case '3':
    181 		if (*flags & TCP_FLAGS)
    182 			exit_error(PARAMETER_PROBLEM,
    183 				   "Only one of `--syn' or `--tcp-flags' "
    184 				   " allowed");
    185 		parse_tcp_flags(tcpinfo, "SYN,RST,ACK,FIN", "SYN", invert);
    186 		*flags |= TCP_FLAGS;
    187 		break;
    188 
    189 	case '4':
    190 		if (*flags & TCP_FLAGS)
    191 			exit_error(PARAMETER_PROBLEM,
    192 				   "Only one of `--syn' or `--tcp-flags' "
    193 				   " allowed");
    194 		check_inverse(optarg, &invert, &optind, 0);
    195 
    196 		if (!argv[optind]
    197 		    || argv[optind][0] == '-' || argv[optind][0] == '!')
    198 			exit_error(PARAMETER_PROBLEM,
    199 				   "--tcp-flags requires two args.");
    200 
    201 		parse_tcp_flags(tcpinfo, argv[optind-1], argv[optind],
    202 				invert);
    203 		optind++;
    204 		*flags |= TCP_FLAGS;
    205 		break;
    206 
    207 	case '5':
    208 		if (*flags & TCP_OPTION)
    209 			exit_error(PARAMETER_PROBLEM,
    210 				   "Only one `--tcp-option' allowed");
    211 		check_inverse(optarg, &invert, &optind, 0);
    212 		parse_tcp_option(argv[optind-1], &tcpinfo->option);
    213 		if (invert)
    214 			tcpinfo->invflags |= IPT_TCP_INV_OPTION;
    215 		*flags |= TCP_OPTION;
    216 		break;
    217 
    218 	default:
    219 		return 0;
    220 	}
    221 
    222 	return 1;
    223 }
    224 
    225 /* Final check; we don't care. */
    226 static void
    227 final_check(unsigned int flags)
    228 {
    229 }
    230 
    231 static char *
    232 port_to_service(int port)
    233 {
    234 	struct servent *service;
    235 
    236 	if ((service = getservbyport(htons(port), "tcp")))
    237 		return service->s_name;
    238 
    239 	return NULL;
    240 }
    241 
    242 static void
    243 print_port(u_int16_t port, int numeric)
    244 {
    245 	char *service;
    246 
    247 	if (numeric || (service = port_to_service(port)) == NULL)
    248 		printf("%u", port);
    249 	else
    250 		printf("%s", service);
    251 }
    252 
    253 static void
    254 print_ports(const char *name, u_int16_t min, u_int16_t max,
    255 	    int invert, int numeric)
    256 {
    257 	const char *inv = invert ? "!" : "";
    258 
    259 	if (min != 0 || max != 0xFFFF || invert) {
    260 		printf("%s", name);
    261 		if (min == max) {
    262 			printf(":%s", inv);
    263 			print_port(min, numeric);
    264 		} else {
    265 			printf("s:%s", inv);
    266 			print_port(min, numeric);
    267 			printf(":");
    268 			print_port(max, numeric);
    269 		}
    270 		printf(" ");
    271 	}
    272 }
    273 
    274 static void
    275 print_option(u_int8_t option, int invert, int numeric)
    276 {
    277 	if (option || invert)
    278 		printf("option=%s%u ", invert ? "!" : "", option);
    279 }
    280 
    281 static void
    282 print_tcpf(u_int8_t flags)
    283 {
    284 	int have_flag = 0;
    285 
    286 	while (flags) {
    287 		unsigned int i;
    288 
    289 		for (i = 0; (flags & tcp_flag_names[i].flag) == 0; i++);
    290 
    291 		if (have_flag)
    292 			printf(",");
    293 		printf("%s", tcp_flag_names[i].name);
    294 		have_flag = 1;
    295 
    296 		flags &= ~tcp_flag_names[i].flag;
    297 	}
    298 
    299 	if (!have_flag)
    300 		printf("NONE");
    301 }
    302 
    303 static void
    304 print_flags(u_int8_t mask, u_int8_t cmp, int invert, int numeric)
    305 {
    306 	if (mask || invert) {
    307 		printf("flags:%s", invert ? "!" : "");
    308 		if (numeric)
    309 			printf("0x%02X/0x%02X ", mask, cmp);
    310 		else {
    311 			print_tcpf(mask);
    312 			printf("/");
    313 			print_tcpf(cmp);
    314 			printf(" ");
    315 		}
    316 	}
    317 }
    318 
    319 /* Prints out the union ipt_matchinfo. */
    320 static void
    321 print(const struct ipt_ip *ip,
    322       const struct ipt_entry_match *match, int numeric)
    323 {
    324 	const struct ipt_tcp *tcp = (struct ipt_tcp *)match->data;
    325 
    326 	printf("tcp ");
    327 	print_ports("spt", tcp->spts[0], tcp->spts[1],
    328 		    tcp->invflags & IPT_TCP_INV_SRCPT,
    329 		    numeric);
    330 	print_ports("dpt", tcp->dpts[0], tcp->dpts[1],
    331 		    tcp->invflags & IPT_TCP_INV_DSTPT,
    332 		    numeric);
    333 	print_option(tcp->option,
    334 		     tcp->invflags & IPT_TCP_INV_OPTION,
    335 		     numeric);
    336 	print_flags(tcp->flg_mask, tcp->flg_cmp,
    337 		    tcp->invflags & IPT_TCP_INV_FLAGS,
    338 		    numeric);
    339 	if (tcp->invflags & ~IPT_TCP_INV_MASK)
    340 		printf("Unknown invflags: 0x%X ",
    341 		       tcp->invflags & ~IPT_TCP_INV_MASK);
    342 }
    343 
    344 /* Saves the union ipt_matchinfo in parsable form to stdout. */
    345 static void save(const struct ipt_ip *ip, const struct ipt_entry_match *match)
    346 {
    347 	const struct ipt_tcp *tcpinfo = (struct ipt_tcp *)match->data;
    348 
    349 	if (tcpinfo->spts[0] != 0
    350 	    || tcpinfo->spts[1] != 0xFFFF) {
    351 		if (tcpinfo->invflags & IPT_TCP_INV_SRCPT)
    352 			printf("! ");
    353 		if (tcpinfo->spts[0]
    354 		    != tcpinfo->spts[1])
    355 			printf("--sport %u:%u ",
    356 			       tcpinfo->spts[0],
    357 			       tcpinfo->spts[1]);
    358 		else
    359 			printf("--sport %u ",
    360 			       tcpinfo->spts[0]);
    361 	}
    362 
    363 	if (tcpinfo->dpts[0] != 0
    364 	    || tcpinfo->dpts[1] != 0xFFFF) {
    365 		if (tcpinfo->invflags & IPT_TCP_INV_DSTPT)
    366 			printf("! ");
    367 		if (tcpinfo->dpts[0]
    368 		    != tcpinfo->dpts[1])
    369 			printf("--dport %u:%u ",
    370 			       tcpinfo->dpts[0],
    371 			       tcpinfo->dpts[1]);
    372 		else
    373 			printf("--dport %u ",
    374 			       tcpinfo->dpts[0]);
    375 	}
    376 
    377 	if (tcpinfo->option
    378 	    || (tcpinfo->invflags & IPT_TCP_INV_OPTION)) {
    379 		if (tcpinfo->invflags & IPT_TCP_INV_OPTION)
    380 			printf("! ");
    381 		printf("--tcp-option %u ", tcpinfo->option);
    382 	}
    383 
    384 	if (tcpinfo->flg_mask
    385 	    || (tcpinfo->invflags & IPT_TCP_INV_FLAGS)) {
    386 		if (tcpinfo->invflags & IPT_TCP_INV_FLAGS)
    387 			printf("! ");
    388 		printf("--tcp-flags ");
    389 		if (tcpinfo->flg_mask != 0xFF) {
    390 			print_tcpf(tcpinfo->flg_mask);
    391 		}
    392 		printf(" ");
    393 		print_tcpf(tcpinfo->flg_cmp);
    394 		printf(" ");
    395 	}
    396 }
    397 
    398 static struct iptables_match tcp = {
    399 	.next		= NULL,
    400 	.name		= "tcp",
    401 	.version	= IPTABLES_VERSION,
    402 	.size		= IPT_ALIGN(sizeof(struct ipt_tcp)),
    403 	.userspacesize	= IPT_ALIGN(sizeof(struct ipt_tcp)),
    404 	.help		= &help,
    405 	.init		= &init,
    406 	.parse		= &parse,
    407 	.final_check	= &final_check,
    408 	.print		= &print,
    409 	.save		= &save,
    410 	.extra_opts	= opts
    411 };
    412 
    413 void
    414 ipt_tcp_init(void)
    415 {
    416 	register_match(&tcp);
    417 }
    418