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