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 
23 static const char *iface = "lo";
24 static uint16_t port;
25 static const char *chain = "SYNPROXY";
26 
parse_packet(const char * host,const uint8_t * data)27 static 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 
probe_host(const char * host)91 static 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 
154 err4:
155 	close(fd);
156 err3:
157 	pcap_freecode(&fp);
158 err2:
159 	pcap_close(ph);
160 err1:
161 	return;
162 }
163 
164 enum {
165 	OPT_HELP	= 'h',
166 	OPT_IFACE	= 'i',
167 	OPT_PORT	= 'p',
168 	OPT_CHAIN	= 'c',
169 };
170 
171 static 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 
print_help(const char * name)179 static 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 
main(int argc,char ** argv)191 int 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