1/*
2 * Copyright (c) 2013 Patrick McHardy <kaber@trash.net>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 2 as
6 * published by the Free Software Foundation.
7 */
8
9#include <stdlib.h>
10#include <stdbool.h>
11#include <unistd.h>
12#include <string.h>
13#include <errno.h>
14#include <getopt.h>
15#include <sys/types.h>
16#include <sys/socket.h>
17#include <netinet/in.h>
18#include <arpa/inet.h>
19#include <pcap/pcap.h>
20#include <netinet/ip.h>
21#include <netinet/tcp.h>
22
23static const char *iface = "lo";
24static uint16_t port;
25static const char *chain = "SYNPROXY";
26
27static int parse_packet(const char *host, const uint8_t *data)
28{
29	const struct iphdr *iph = (void *)data + 14;
30	const struct tcphdr *th = (void *)iph + iph->ihl * 4;
31	int length;
32	uint8_t *ptr;
33
34	if (!th->syn || !th->ack)
35		return 0;
36
37	printf("-A %s -d %s -p tcp --dport %u "
38	       "-m state --state UNTRACKED,INVALID "
39	       "-j SYNPROXY ", chain, host, port);
40
41	/* ECE && !CWR */
42	if (th->res2 == 0x1)
43		printf("--ecn ");
44
45	length = th->doff * 4 - sizeof(*th);
46	ptr = (uint8_t *)(th + 1);
47	while (length > 0) {
48		int opcode = *ptr++;
49		int opsize;
50
51		switch (opcode) {
52		case TCPOPT_EOL:
53			return 1;
54		case TCPOPT_NOP:
55			length--;
56			continue;
57		default:
58			opsize = *ptr++;
59			if (opsize < 2)
60				return 1;
61			if (opsize > length)
62				return 1;
63
64			switch (opcode) {
65			case TCPOPT_MAXSEG:
66				if (opsize == TCPOLEN_MAXSEG)
67					printf("--mss %u ", ntohs(*(uint16_t *)ptr));
68				break;
69			case TCPOPT_WINDOW:
70				if (opsize == TCPOLEN_WINDOW)
71					printf("--wscale %u ", *ptr);
72				break;
73			case TCPOPT_TIMESTAMP:
74				if (opsize == TCPOLEN_TIMESTAMP)
75					printf("--timestamp ");
76				break;
77			case TCPOPT_SACK_PERMITTED:
78				if (opsize == TCPOLEN_SACK_PERMITTED)
79					printf("--sack-perm ");
80				break;
81			}
82
83			ptr += opsize - 2;
84			length -= opsize;
85		}
86	}
87	printf("\n");
88	return 1;
89}
90
91static void probe_host(const char *host)
92{
93	struct sockaddr_in sin;
94	char pcap_errbuf[PCAP_ERRBUF_SIZE];
95	struct pcap_pkthdr pkthdr;
96	const uint8_t *data;
97	struct bpf_program fp;
98	pcap_t *ph;
99	int fd;
100
101	ph = pcap_create(iface, pcap_errbuf);
102	if (ph == NULL) {
103		perror("pcap_create");
104		goto err1;
105	}
106
107	if (pcap_setnonblock(ph, 1, pcap_errbuf) == -1) {
108		perror("pcap_setnonblock");
109		goto err2;
110	}
111
112	if (pcap_setfilter(ph, &fp) == -1) {
113		pcap_perror(ph, "pcap_setfilter");
114		goto err2;
115	}
116
117	if (pcap_activate(ph) != 0) {
118		pcap_perror(ph, "pcap_activate");
119		goto err2;
120	}
121
122	if (pcap_compile(ph, &fp, "src host 127.0.0.1 and tcp and src port 80",
123			 1, PCAP_NETMASK_UNKNOWN) == -1) {
124		pcap_perror(ph, "pcap_compile");
125		goto err2;
126	}
127
128	fd = socket(AF_INET, SOCK_STREAM, 0);
129	if (fd < 0) {
130		perror("socket");
131		goto err3;
132	}
133
134	memset(&sin, 0, sizeof(sin));
135	sin.sin_family		= AF_INET;
136	sin.sin_port		= htons(port);
137	sin.sin_addr.s_addr	= inet_addr(host);
138
139	if (connect(fd, (struct sockaddr *)&sin, sizeof(sin)) < 0) {
140		perror("connect");
141		goto err4;
142	}
143
144	for (;;) {
145		data = pcap_next(ph, &pkthdr);
146		if (data == NULL)
147			break;
148		if (parse_packet(host, data))
149			break;
150	}
151
152	close(fd);
153
154err4:
155	close(fd);
156err3:
157	pcap_freecode(&fp);
158err2:
159	pcap_close(ph);
160err1:
161	return;
162}
163
164enum {
165	OPT_HELP	= 'h',
166	OPT_IFACE	= 'i',
167	OPT_PORT	= 'p',
168	OPT_CHAIN	= 'c',
169};
170
171static const struct option options[] = {
172	{ .name = "help",  .has_arg = false, .val = OPT_HELP },
173	{ .name = "iface", .has_arg = true,  .val = OPT_IFACE },
174	{ .name = "port" , .has_arg = true,  .val = OPT_PORT },
175	{ .name = "chain", .has_arg = true,  .val = OPT_CHAIN },
176	{ }
177};
178
179static void print_help(const char *name)
180{
181	printf("%s [ options ] address...\n"
182	       "\n"
183	       "Options:\n"
184	       " -i/--iface        Outbound interface\n"
185	       " -p/--port         Port number to probe\n"
186	       " -c/--chain        Chain name to use for rules\n"
187	       " -h/--help         Show this help\n",
188	       name);
189}
190
191int main(int argc, char **argv)
192{
193	int optidx = 0, c;
194
195	for (;;) {
196		c = getopt_long(argc, argv, "hi:p:c:", options, &optidx);
197		if (c == -1)
198			break;
199
200		switch (c) {
201		case OPT_IFACE:
202			iface = optarg;
203			break;
204		case OPT_PORT:
205			port = atoi(optarg);
206			break;
207		case OPT_CHAIN:
208			chain = optarg;
209			break;
210		case OPT_HELP:
211			print_help(argv[0]);
212			exit(0);
213		case '?':
214			print_help(argv[0]);
215			exit(1);
216		}
217	}
218
219	argc -= optind;
220	argv += optind;
221
222	while (argc > 0) {
223		probe_host(*argv);
224		argc--;
225		argv++;
226	}
227	return 0;
228}
229