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