1 | /* |
---|
2 | Portions are Copyright 2002 Tim Carstens. All rights reserved. |
---|
3 | Redistribution and use, with or without modification, are permitted provided |
---|
4 | that the following conditions are met: |
---|
5 | |
---|
6 | - Redistribution must retain the above copyright notice and this list of |
---|
7 | conditions. |
---|
8 | |
---|
9 | - The name of Tim Carstens may not be used to endorse or promote products |
---|
10 | derived from this document without specific prior written permission. |
---|
11 | */ |
---|
12 | |
---|
13 | #include <stdio.h> |
---|
14 | #include <stdlib.h> |
---|
15 | #include <string.h> |
---|
16 | #include <sys/types.h> |
---|
17 | #include <inttypes.h> |
---|
18 | #include <sys/stat.h> |
---|
19 | #include <fcntl.h> |
---|
20 | #include <errno.h> |
---|
21 | #include <netinet/in.h> |
---|
22 | #include <pcap.h> |
---|
23 | |
---|
24 | #define DEBUG 0 |
---|
25 | |
---|
26 | /* ethernet headers are always exactly 14 bytes */ |
---|
27 | /* XXX: ^^^ really? */ |
---|
28 | #define SIZE_ETHERNET 14 |
---|
29 | |
---|
30 | /* Ethernet addresses are 6 bytes */ |
---|
31 | #define ETHER_ADDR_LEN 6 |
---|
32 | |
---|
33 | /* Ethernet header */ |
---|
34 | /* |
---|
35 | struct sniff_ethernet |
---|
36 | { |
---|
37 | u_char ether_dhost[ETHER_ADDR_LEN]; / * Destination host address * / |
---|
38 | u_char ether_shost[ETHER_ADDR_LEN]; / * Source host address * / |
---|
39 | u_short ether_type; / * IP? ARP? RARP? etc * / |
---|
40 | }; |
---|
41 | */ |
---|
42 | |
---|
43 | /* IP header */ |
---|
44 | struct sniff_ip |
---|
45 | { |
---|
46 | u_char ip_vhl; /* version << 4 | header length >> 2 */ |
---|
47 | u_char ip_tos; /* type of service */ |
---|
48 | u_short ip_len; /* total length */ |
---|
49 | u_short ip_id; /* identification */ |
---|
50 | u_short ip_off; /* fragment offset field */ |
---|
51 | #define IP_RF 0x8000 /* reserved fragment flag */ |
---|
52 | #define IP_DF 0x4000 /* dont fragment flag */ |
---|
53 | #define IP_MF 0x2000 /* more fragments flag */ |
---|
54 | #define IP_OFFMASK 0x1fff /* mask for fragmenting bits */ |
---|
55 | u_char ip_ttl; /* time to live */ |
---|
56 | u_char ip_p; /* protocol */ |
---|
57 | u_short ip_sum; /* checksum */ |
---|
58 | struct in_addr ip_src,ip_dst; /* source and dest address */ |
---|
59 | }; |
---|
60 | #define IP_HL(ip) (((ip)->ip_vhl) & 0x0f) |
---|
61 | #define IP_V(ip) (((ip)->ip_vhl) >> 4) |
---|
62 | |
---|
63 | /* TCP header */ |
---|
64 | typedef u_int tcp_seq; |
---|
65 | |
---|
66 | struct sniff_tcp |
---|
67 | { |
---|
68 | u_short th_sport; /* source port */ |
---|
69 | u_short th_dport; /* destination port */ |
---|
70 | tcp_seq th_seq; /* sequence number */ |
---|
71 | tcp_seq th_ack; /* acknowledgement number */ |
---|
72 | u_char th_offx2; /* data offset, rsvd */ |
---|
73 | #define TH_OFF(th) (((th)->th_offx2 & 0xf0) >> 4) |
---|
74 | u_char th_flags; |
---|
75 | #define TH_FIN 0x01 |
---|
76 | #define TH_SYN 0x02 |
---|
77 | #define TH_RST 0x04 |
---|
78 | #define TH_PUSH 0x08 |
---|
79 | #define TH_ACK 0x10 |
---|
80 | #define TH_URG 0x20 |
---|
81 | #define TH_ECE 0x40 |
---|
82 | #define TH_CWR 0x80 |
---|
83 | #define TH_FLAGS (TH_FIN|TH_SYN|TH_RST|TH_ACK|TH_URG|TH_ECE|TH_CWR) |
---|
84 | u_short th_win; /* window */ |
---|
85 | u_short th_sum; /* checksum */ |
---|
86 | u_short th_urp; /* urgent pointer */ |
---|
87 | }; |
---|
88 | |
---|
89 | typedef struct { |
---|
90 | uint8_t kind; |
---|
91 | uint8_t size; |
---|
92 | } tcp_option_t; |
---|
93 | |
---|
94 | |
---|
95 | |
---|
96 | struct in_addr my_ip_raw; |
---|
97 | struct in_addr target_ip_raw; |
---|
98 | uint16_t target_port; |
---|
99 | |
---|
100 | FILE* output; |
---|
101 | |
---|
102 | struct useful_info |
---|
103 | { |
---|
104 | uint32_t tcpseq; |
---|
105 | uint32_t tcpack; |
---|
106 | uint32_t tsval; |
---|
107 | uint16_t local_port; |
---|
108 | uint16_t payload_len; |
---|
109 | uint8_t sent; |
---|
110 | }; |
---|
111 | |
---|
112 | |
---|
113 | char* output_template = "{\"local_port\":%u,\"sent\":%u,\"payload_len\":%u,\"tcpseq\":%u,\"tcpack\":%u,\"observed\":%lu%06lu000,\"tsval\":%u}\n"; |
---|
114 | uint8_t payloads_only = 1; |
---|
115 | |
---|
116 | |
---|
117 | pcap_t* create_listener(const char* dev, int snaplen, int promisc, int to_ms, char* errbuf) |
---|
118 | { |
---|
119 | pcap_t* ret_val; |
---|
120 | int status; |
---|
121 | int tstcount, i, besttst; |
---|
122 | int* tstypes; |
---|
123 | |
---|
124 | ret_val = pcap_create(dev, errbuf); |
---|
125 | if (ret_val == NULL) |
---|
126 | return (NULL); |
---|
127 | |
---|
128 | status = pcap_set_snaplen(ret_val, snaplen); |
---|
129 | if (status < 0) |
---|
130 | goto fail; |
---|
131 | |
---|
132 | status = pcap_set_promisc(ret_val, promisc); |
---|
133 | if (status < 0) |
---|
134 | goto fail; |
---|
135 | |
---|
136 | status = pcap_set_timeout(ret_val, to_ms); |
---|
137 | if (status < 0) |
---|
138 | goto fail; |
---|
139 | |
---|
140 | |
---|
141 | /* Try to select the best timestamp source for packet capture */ |
---|
142 | tstcount = pcap_list_tstamp_types(ret_val, &tstypes); |
---|
143 | if(tstcount > 0) |
---|
144 | { |
---|
145 | #if DEBUG |
---|
146 | fprintf(stderr, "INFO: Available Packet Timers: "); |
---|
147 | #endif |
---|
148 | besttst = -1; |
---|
149 | for(i=0; i < tstcount; i++) |
---|
150 | { |
---|
151 | #if DEBUG |
---|
152 | fprintf(stderr, " %s", pcap_tstamp_type_val_to_name(tstypes[i])); |
---|
153 | #endif |
---|
154 | switch (tstypes[i]) |
---|
155 | { |
---|
156 | case PCAP_TSTAMP_HOST: |
---|
157 | break; |
---|
158 | case PCAP_TSTAMP_HOST_LOWPREC: |
---|
159 | break; |
---|
160 | case PCAP_TSTAMP_HOST_HIPREC: |
---|
161 | { |
---|
162 | if(besttst != PCAP_TSTAMP_ADAPTER_UNSYNCED |
---|
163 | && besttst != PCAP_TSTAMP_ADAPTER) |
---|
164 | besttst = PCAP_TSTAMP_HOST_HIPREC; |
---|
165 | break; |
---|
166 | } |
---|
167 | case PCAP_TSTAMP_ADAPTER: |
---|
168 | { |
---|
169 | if(besttst != PCAP_TSTAMP_ADAPTER_UNSYNCED) |
---|
170 | besttst = PCAP_TSTAMP_ADAPTER; |
---|
171 | break; |
---|
172 | } |
---|
173 | case PCAP_TSTAMP_ADAPTER_UNSYNCED: |
---|
174 | /*besttst = PCAP_TSTAMP_ADAPTER_UNSYNCED;*/ |
---|
175 | break; |
---|
176 | default: |
---|
177 | #if DEBUG |
---|
178 | fprintf(stderr, " unknown_type_%d", tstypes[i]); |
---|
179 | #endif |
---|
180 | break; |
---|
181 | } |
---|
182 | } |
---|
183 | pcap_free_tstamp_types(tstypes); |
---|
184 | #if DEBUG |
---|
185 | fprintf(stderr, "\n"); |
---|
186 | #endif |
---|
187 | /*besttst = PCAP_TSTAMP_HOST;*/ |
---|
188 | if(besttst != -1) |
---|
189 | { |
---|
190 | #if DEBUG |
---|
191 | fprintf(stderr, "INFO: Attempting to set the timestamp source to: %s\n", |
---|
192 | pcap_tstamp_type_val_to_name(besttst)); |
---|
193 | #endif |
---|
194 | if(pcap_set_tstamp_type(ret_val, besttst) != 0) |
---|
195 | fprintf(stderr, "WARN: Failed to set preferred timestamp source.\n"); |
---|
196 | } |
---|
197 | } |
---|
198 | |
---|
199 | /* Attempt to set nanosecond timestamp precision */ |
---|
200 | if(pcap_set_tstamp_precision(ret_val, PCAP_TSTAMP_PRECISION_NANO) != 0) |
---|
201 | fprintf(stderr, "INFO: Failed to set packet capture nanosecond precision.\n"); |
---|
202 | else |
---|
203 | output_template = "{\"local_port\":%u,\"sent\":%u,\"payload_len\":%u,\"tcpseq\":%u,\"tcpack\":%u,\"observed\":%lu%09lu,\"tsval\":%u}\n"; |
---|
204 | |
---|
205 | |
---|
206 | status = pcap_activate(ret_val); |
---|
207 | if (status < 0) |
---|
208 | goto fail; |
---|
209 | |
---|
210 | return ret_val; |
---|
211 | |
---|
212 | fail: |
---|
213 | pcap_close(ret_val); |
---|
214 | return NULL; |
---|
215 | } |
---|
216 | |
---|
217 | |
---|
218 | static int extract_packet_fields(const struct pcap_pkthdr *header, |
---|
219 | const u_char* packet, |
---|
220 | struct useful_info* fields) |
---|
221 | { |
---|
222 | const struct sniff_ip* ip; /* The IP header */ |
---|
223 | const struct sniff_tcp* tcp; /* The TCP header */ |
---|
224 | u_int iphdr_size; |
---|
225 | u_int tcphdr_size; |
---|
226 | uint16_t ip_len; |
---|
227 | uint16_t tsval; |
---|
228 | uint8_t* opt; |
---|
229 | tcp_option_t* _opt; |
---|
230 | |
---|
231 | /*printf("snaplen: %u\n", header->caplen);*/ |
---|
232 | |
---|
233 | ip = (struct sniff_ip*)(packet + SIZE_ETHERNET); |
---|
234 | iphdr_size = IP_HL(ip)*4; |
---|
235 | if (iphdr_size < 20) |
---|
236 | return 0; |
---|
237 | ip_len = ntohs(ip->ip_len); |
---|
238 | |
---|
239 | tcp = (struct sniff_tcp*)(packet + SIZE_ETHERNET + iphdr_size); |
---|
240 | tcphdr_size = TH_OFF(tcp)*4; |
---|
241 | if (tcphdr_size < 20) |
---|
242 | return 0; |
---|
243 | |
---|
244 | /*fprintf(stderr, "%d\n", (ip_len - iphdr_size - tcphdr_size));*/ |
---|
245 | if ((ip_len - iphdr_size - tcphdr_size < 0) |
---|
246 | || (payloads_only && (ip_len - iphdr_size - tcphdr_size == 0))) |
---|
247 | { return 0; } |
---|
248 | |
---|
249 | fields->tcpseq = ntohl(tcp->th_seq); |
---|
250 | fields->tcpack = ntohl(tcp->th_ack); |
---|
251 | fields->payload_len = ip_len - iphdr_size - tcphdr_size; |
---|
252 | if(ip->ip_src.s_addr == target_ip_raw.s_addr && ntohs(tcp->th_sport) == target_port) |
---|
253 | { |
---|
254 | fields->sent = 0; |
---|
255 | fields->local_port = ntohs(tcp->th_dport); |
---|
256 | } |
---|
257 | else |
---|
258 | { |
---|
259 | fields->sent = 1; |
---|
260 | fields->local_port = ntohs(tcp->th_sport); |
---|
261 | } |
---|
262 | /*printf("src: %lX / dst: %lX\n", ip->ip_src, ip->ip_dst);*/ |
---|
263 | |
---|
264 | fields->tsval = 0; |
---|
265 | if(tcphdr_size > 20) |
---|
266 | { |
---|
267 | opt = (uint8_t*)(packet + SIZE_ETHERNET + iphdr_size + 20); |
---|
268 | while((*opt != 0) && (opt - packet) < header->caplen) |
---|
269 | { |
---|
270 | _opt = (tcp_option_t*)opt; |
---|
271 | if(_opt->kind == 1) /* NOP */ |
---|
272 | { |
---|
273 | ++opt; // NOP is one byte; |
---|
274 | continue; |
---|
275 | } |
---|
276 | |
---|
277 | if(_opt->kind == 8) /* timestamp */ |
---|
278 | { |
---|
279 | fields->tsval = ntohl(*(uint32_t*)(opt+2)); |
---|
280 | break; |
---|
281 | } |
---|
282 | |
---|
283 | opt += _opt->size; |
---|
284 | } |
---|
285 | } |
---|
286 | |
---|
287 | return 1; |
---|
288 | } |
---|
289 | |
---|
290 | |
---|
291 | void process_packet(u_char *args, const struct pcap_pkthdr *header, const u_char *packet) |
---|
292 | { |
---|
293 | struct useful_info fields; |
---|
294 | |
---|
295 | if(extract_packet_fields(header, packet, &fields)) |
---|
296 | { |
---|
297 | fprintf(output, output_template, fields.local_port, fields.sent, |
---|
298 | fields.payload_len, fields.tcpseq, fields.tcpack, |
---|
299 | header->ts.tv_sec, header->ts.tv_usec, fields.tsval); |
---|
300 | fflush(output); |
---|
301 | } |
---|
302 | } |
---|
303 | |
---|
304 | |
---|
305 | |
---|
306 | int main(int argc, char** argv) |
---|
307 | { |
---|
308 | char* dev; /* The device to sniff on */ |
---|
309 | char* my_ip; |
---|
310 | char* target_ip; |
---|
311 | pcap_t* handle; /* Session handle */ |
---|
312 | char bpf[255]; /* The filter expression (length of 168 should be enough)*/ |
---|
313 | char errbuf[PCAP_ERRBUF_SIZE]; /* Error string */ |
---|
314 | struct bpf_program fp; /* The compiled filter */ |
---|
315 | bpf_u_int32 mask; /* Our netmask */ |
---|
316 | bpf_u_int32 net; /* Our IP */ |
---|
317 | struct pcap_pkthdr header; /* The header that pcap gives us */ |
---|
318 | const u_char *packet; /* The actual packet */ |
---|
319 | |
---|
320 | if(argc < 6) |
---|
321 | { |
---|
322 | fprintf(stderr, "USAGE:\n %s {interface} {my_ip} {target_ip} {target_port} {output_file} [{payloads_only?}]\n", argv[0]); |
---|
323 | return 1; |
---|
324 | } |
---|
325 | |
---|
326 | dev = argv[1]; |
---|
327 | my_ip = argv[2]; |
---|
328 | target_ip = argv[3]; |
---|
329 | target_port = atoi(argv[4]); |
---|
330 | if(argc == 7 && argv[6][0] == '0') |
---|
331 | payloads_only = 0; |
---|
332 | |
---|
333 | if(!(output = fopen(argv[5], "w+"))) |
---|
334 | { |
---|
335 | fprintf(stderr, "ERROR: could not open output file due to: %s\n", strerror(errno)); |
---|
336 | return 2; |
---|
337 | } |
---|
338 | |
---|
339 | snprintf(bpf, 255, "(src host %s and dst host %s and tcp and src port %u) or (dst host %s and src host %s and tcp and dst port %u)", |
---|
340 | target_ip, my_ip, target_port, target_ip, my_ip, target_port); |
---|
341 | |
---|
342 | |
---|
343 | if(!inet_aton(my_ip, &my_ip_raw)) |
---|
344 | { |
---|
345 | fprintf(stderr, "Couldn't parse my_ip.\n"); |
---|
346 | return 1; |
---|
347 | } |
---|
348 | |
---|
349 | if(!inet_aton(target_ip, &target_ip_raw)) |
---|
350 | { |
---|
351 | fprintf(stderr, "Couldn't parse target_ip.\n"); |
---|
352 | return 1; |
---|
353 | } |
---|
354 | |
---|
355 | memset(errbuf, 0, PCAP_ERRBUF_SIZE); |
---|
356 | |
---|
357 | /* Find the properties for the device */ |
---|
358 | if (pcap_lookupnet(dev, &net, &mask, errbuf) == -1) |
---|
359 | { |
---|
360 | fprintf(stderr, "Couldn't get netmask for device %s: %s\n", dev, errbuf); |
---|
361 | net = 0; |
---|
362 | mask = 0; |
---|
363 | } |
---|
364 | |
---|
365 | /* Open the session in promiscuous mode */ |
---|
366 | /* XXX: does to_ms timeout (param 4) matter? */ |
---|
367 | handle = create_listener(dev, BUFSIZ, 0, 1000, errbuf); |
---|
368 | if (handle == NULL) |
---|
369 | { |
---|
370 | fprintf(stderr, "Couldn't open device %s: %s\n", dev, errbuf); |
---|
371 | return 2; |
---|
372 | } |
---|
373 | |
---|
374 | /* Compile and apply the filter */ |
---|
375 | if (pcap_compile(handle, &fp, bpf, 0, net) == -1) |
---|
376 | { |
---|
377 | fprintf(stderr, "Couldn't parse filter %s: %s\n", bpf, pcap_geterr(handle)); |
---|
378 | return 2; |
---|
379 | } |
---|
380 | |
---|
381 | if (pcap_setfilter(handle, &fp) == -1) |
---|
382 | { |
---|
383 | fprintf(stderr, "Couldn't install filter %s: %s\n", bpf, pcap_geterr(handle)); |
---|
384 | return 2; |
---|
385 | } |
---|
386 | |
---|
387 | /* XXX: report errors */ |
---|
388 | pcap_loop(handle, 0, process_packet, NULL); |
---|
389 | |
---|
390 | /* And close the session */ |
---|
391 | pcap_close(handle); |
---|
392 | |
---|
393 | return 0; |
---|
394 | } |
---|