54ada43c309b81cf7f611b49c509409b6f0606d5
[darkstat] / decode.c
1 /* darkstat 3
2 * copyright (c) 2001-2009 Emil Mikulic.
3 *
4 * decode.c: packet decoding.
5 *
6 * Given a captured packet, decode it and fill out a pktsummary struct which
7 * will be sent to the accounting code in acct.c
8 *
9 * You may use, modify and redistribute this file under the terms of the
10 * GNU General Public License version 2. (see COPYING.GPL)
11 */
12
13 #include "darkstat.h"
14 #include "acct.h"
15 #include "cap.h"
16
17 #include <sys/ioctl.h>
18 #include <sys/socket.h>
19 #include <assert.h>
20 #include "err.h"
21 #include <pcap.h>
22 #include <string.h>
23 #include <unistd.h>
24 #include <arpa/inet.h> /* inet_ntoa() */
25
26 /* need struct ether_header */
27 #if defined(__NetBSD__) || defined(__OpenBSD__)
28 # include <sys/queue.h>
29 # include <net/if.h>
30 # include <net/if_arp.h>
31 # include <netinet/if_ether.h>
32 #else
33 # ifdef __sun
34 # include <sys/ethernet.h>
35 # define ETHER_HDR_LEN 14
36 # else
37 # ifdef _AIX
38 # include <netinet/if_ether.h>
39 # define ETHER_HDR_LEN 14
40 # else
41 # include <net/ethernet.h>
42 # endif
43 # endif
44 #endif
45 #ifndef ETHERTYPE_PPPOE
46 #define ETHERTYPE_PPPOE 0x8864
47 #endif
48
49 #include <net/if.h> /* struct ifreq */
50 #include <netinet/in_systm.h> /* n_long */
51 #include <netinet/ip.h> /* struct ip */
52 #define __FAVOR_BSD
53 #include <netinet/tcp.h> /* struct tcphdr */
54 #include <netinet/udp.h> /* struct udphdr */
55
56 extern int want_pppoe;
57
58 static void decode_ether(u_char *, const struct pcap_pkthdr *,
59 const u_char *);
60 static void decode_loop(u_char *, const struct pcap_pkthdr *,
61 const u_char *);
62 static void decode_ppp(u_char *, const struct pcap_pkthdr *,
63 const u_char *);
64 static void decode_pppoe(u_char *, const struct pcap_pkthdr *,
65 const u_char *);
66 static void decode_pppoe_real(const u_char *pdata, const uint32_t len,
67 pktsummary *sm);
68 static void decode_linux_sll(u_char *, const struct pcap_pkthdr *,
69 const u_char *);
70 static void decode_raw(u_char *, const struct pcap_pkthdr *,
71 const u_char *);
72 static void decode_ip(const u_char *pdata, const uint32_t len,
73 pktsummary *sm);
74
75 /* Link-type header information */
76 static const linkhdr_t linkhdrs[] = {
77 /* linktype hdrlen handler */
78 { DLT_EN10MB, ETHER_HDR_LEN, decode_ether },
79 { DLT_LOOP, NULL_HDR_LEN, decode_loop },
80 { DLT_NULL, NULL_HDR_LEN, decode_loop },
81 { DLT_PPP, PPP_HDR_LEN, decode_ppp },
82 #if defined(__NetBSD__)
83 { DLT_PPP_SERIAL, PPP_HDR_LEN, decode_ppp },
84 #endif
85 { DLT_FDDI, FDDI_HDR_LEN, NULL },
86 { DLT_PPP_ETHER, PPPOE_HDR_LEN, decode_pppoe },
87 #ifdef DLT_LINUX_SLL
88 { DLT_LINUX_SLL, SLL_HDR_LEN, decode_linux_sll },
89 #endif
90 { DLT_RAW, RAW_HDR_LEN, decode_raw },
91 { -1, -1, NULL }
92 };
93
94 /*
95 * Returns a pointer to the linkhdr_t record matching the given linktype, or
96 * NULL if no matching entry found.
97 */
98 const linkhdr_t *
99 getlinkhdr(int linktype)
100 {
101 int i;
102
103 for (i=0; linkhdrs[i].linktype != -1; i++)
104 if (linkhdrs[i].linktype == linktype)
105 return (&(linkhdrs[i]));
106 return (NULL);
107 }
108
109 /*
110 * Returns the minimum snaplen needed to decode everything up to the TCP/UDP
111 * packet headers. Argument lh is not allowed to be NULL.
112 */
113 int
114 getsnaplen(const linkhdr_t *lh)
115 {
116 assert(lh != NULL);
117 return (lh->hdrlen + IP_HDR_LEN + max(TCP_HDR_LEN, UDP_HDR_LEN));
118 }
119
120 /*
121 * Convert IP address to a numbers-and-dots notation in a static buffer
122 * provided by inet_ntoa().
123 */
124 char *
125 ip_to_str(const in_addr_t ip)
126 {
127 struct in_addr in;
128
129 in.s_addr = ip;
130 return (inet_ntoa(in));
131 }
132
133 /* Decoding functions. */
134 static void
135 decode_ether(u_char *user _unused_,
136 const struct pcap_pkthdr *pheader,
137 const u_char *pdata)
138 {
139 u_short type;
140 const struct ether_header *hdr = (const struct ether_header *)pdata;
141 pktsummary sm;
142 memset(&sm, 0, sizeof(sm));
143 sm.time = pheader->ts.tv_sec;
144
145 if (pheader->caplen < ETHER_HDR_LEN) {
146 verbosef("ether: packet too short (%u bytes)", pheader->caplen);
147 return;
148 }
149
150 #ifdef __sun
151 memcpy(sm.src_mac, hdr->ether_shost.ether_addr_octet, sizeof(sm.src_mac));
152 memcpy(sm.dst_mac, hdr->ether_dhost.ether_addr_octet, sizeof(sm.dst_mac));
153 #else
154 memcpy(sm.src_mac, hdr->ether_shost, sizeof(sm.src_mac));
155 memcpy(sm.dst_mac, hdr->ether_dhost, sizeof(sm.dst_mac));
156 #endif
157
158 type = ntohs( hdr->ether_type );
159 switch (type) {
160 case ETHERTYPE_IP:
161 if (!want_pppoe) {
162 decode_ip(pdata + ETHER_HDR_LEN,
163 pheader->caplen - ETHER_HDR_LEN, &sm);
164 acct_for(&sm);
165 } else
166 verbosef("ether: discarded IP packet, expecting PPPoE instead");
167 break;
168 case ETHERTYPE_ARP:
169 /* known protocol, don't complain about it. */
170 break;
171 case ETHERTYPE_PPPOE:
172 if (want_pppoe)
173 decode_pppoe_real(pdata + ETHER_HDR_LEN,
174 pheader->caplen - ETHER_HDR_LEN, &sm);
175 else
176 verbosef("ether: got PPPoE frame: maybe you want --pppoe");
177 break;
178 default:
179 verbosef("ether: unknown protocol (0x%04x)", type);
180 }
181 }
182
183 static void
184 decode_loop(u_char *user _unused_,
185 const struct pcap_pkthdr *pheader,
186 const u_char *pdata)
187 {
188 uint32_t family;
189 pktsummary sm;
190 memset(&sm, 0, sizeof(sm));
191
192 if (pheader->caplen < NULL_HDR_LEN) {
193 verbosef("loop: packet too short (%u bytes)", pheader->caplen);
194 return;
195 }
196 family = *(const uint32_t *)pdata;
197 #ifdef __OpenBSD__
198 family = ntohl(family);
199 #endif
200 if (family == AF_INET) {
201 /* OpenBSD tun or FreeBSD tun or FreeBSD lo */
202 decode_ip(pdata + NULL_HDR_LEN, pheader->caplen - NULL_HDR_LEN, &sm);
203 sm.time = pheader->ts.tv_sec;
204 acct_for(&sm);
205 }
206 else
207 verbosef("loop: unknown family (%x)", family);
208 }
209
210 static void
211 decode_ppp(u_char *user _unused_,
212 const struct pcap_pkthdr *pheader,
213 const u_char *pdata)
214 {
215 pktsummary sm;
216 memset(&sm, 0, sizeof(sm));
217
218 if (pheader->caplen < PPPOE_HDR_LEN) {
219 verbosef("ppp: packet too short (%u bytes)", pheader->caplen);
220 return;
221 }
222
223 if (pdata[2] == 0x00 && pdata[3] == 0x21) {
224 decode_ip(pdata + PPP_HDR_LEN, pheader->caplen - PPP_HDR_LEN, &sm);
225 sm.time = pheader->ts.tv_sec;
226 acct_for(&sm);
227 } else
228 verbosef("non-IP PPP packet; ignoring.");
229 }
230
231 static void
232 decode_pppoe(u_char *user _unused_,
233 const struct pcap_pkthdr *pheader,
234 const u_char *pdata)
235 {
236 pktsummary sm;
237 memset(&sm, 0, sizeof(sm));
238 sm.time = pheader->ts.tv_sec;
239 decode_pppoe_real(pdata, pheader->caplen, &sm);
240 }
241
242 static void
243 decode_pppoe_real(const u_char *pdata, const uint32_t len,
244 pktsummary *sm)
245 {
246 if (len < PPPOE_HDR_LEN) {
247 verbosef("pppoe: packet too short (%u bytes)", len);
248 return;
249 }
250
251 if (pdata[1] != 0x00) {
252 verbosef("pppoe: code = 0x%02x, expecting 0; ignoring.", pdata[1]);
253 return;
254 }
255
256 if ((pdata[6] == 0xc0) && (pdata[7] == 0x21)) return; /* LCP */
257 if ((pdata[6] == 0xc0) && (pdata[7] == 0x25)) return; /* LQR */
258
259 if ((pdata[6] == 0x00) && (pdata[7] == 0x21)) {
260 decode_ip(pdata + PPPOE_HDR_LEN, len - PPPOE_HDR_LEN, sm);
261 acct_for(sm);
262 } else
263 verbosef("pppoe: non-IP PPPoE packet (0x%02x%02x); ignoring.",
264 pdata[6], pdata[7]);
265 }
266
267 /* very similar to decode_ether ... */
268 static void
269 decode_linux_sll(u_char *user _unused_,
270 const struct pcap_pkthdr *pheader,
271 const u_char *pdata)
272 {
273 const struct sll_header {
274 uint16_t packet_type;
275 uint16_t device_type;
276 uint16_t addr_length;
277 #define SLL_MAX_ADDRLEN 8
278 uint8_t addr[SLL_MAX_ADDRLEN];
279 uint16_t ether_type;
280 } *hdr = (const struct sll_header *)pdata;
281 u_short type;
282 pktsummary sm;
283 memset(&sm, 0, sizeof(sm));
284
285 if (pheader->caplen < SLL_HDR_LEN) {
286 verbosef("linux_sll: packet too short (%u bytes)", pheader->caplen);
287 return;
288 }
289
290 type = ntohs( hdr->ether_type );
291 switch (type) {
292 case ETHERTYPE_IP:
293 decode_ip(pdata + SLL_HDR_LEN, pheader->caplen - SLL_HDR_LEN, &sm);
294 sm.time = pheader->ts.tv_sec;
295 acct_for(&sm);
296 break;
297 case ETHERTYPE_ARP:
298 /* known protocol, don't complain about it. */
299 break;
300 default:
301 verbosef("linux_sll: unknown protocol (%04x)", type);
302 }
303 }
304
305 static void
306 decode_raw(u_char *user _unused_,
307 const struct pcap_pkthdr *pheader,
308 const u_char *pdata)
309 {
310 pktsummary sm;
311 memset(&sm, 0, sizeof(sm));
312
313 decode_ip(pdata, pheader->caplen, &sm);
314 sm.time = pheader->ts.tv_sec;
315 acct_for(&sm);
316 }
317
318 static void
319 decode_ip(const u_char *pdata, const uint32_t len, pktsummary *sm)
320 {
321 const struct ip *hdr = (const struct ip *)pdata;
322
323 if (len < IP_HDR_LEN) {
324 verbosef("ip: packet too short (%u bytes)", len);
325 return;
326 }
327 if (hdr->ip_v != 4) {
328 verbosef("ip: version %d (expecting 4)", hdr->ip_v);
329 return;
330 }
331
332 sm->len = ntohs(hdr->ip_len);
333 sm->proto = hdr->ip_p;
334 sm->src_ip = hdr->ip_src.s_addr;
335 sm->dest_ip = hdr->ip_dst.s_addr;
336
337 switch (sm->proto) {
338 case IPPROTO_TCP: {
339 const struct tcphdr *thdr =
340 (const struct tcphdr *)(pdata + IP_HDR_LEN);
341 if (len < IP_HDR_LEN + TCP_HDR_LEN) {
342 verbosef("tcp: packet too short (%u bytes)", len);
343 return;
344 }
345 sm->src_port = ntohs(thdr->th_sport);
346 sm->dest_port = ntohs(thdr->th_dport);
347 sm->tcp_flags = thdr->th_flags &
348 (TH_FIN|TH_SYN|TH_RST|TH_PUSH|TH_ACK|TH_URG);
349 break;
350 }
351
352 case IPPROTO_UDP: {
353 const struct udphdr *uhdr =
354 (const struct udphdr *)(pdata + IP_HDR_LEN);
355 if (len < IP_HDR_LEN + UDP_HDR_LEN) {
356 verbosef("udp: packet too short (%u bytes)", len);
357 return;
358 }
359 sm->src_port = ntohs(uhdr->uh_sport);
360 sm->dest_port = ntohs(uhdr->uh_dport);
361 break;
362 }
363
364 case IPPROTO_ICMP:
365 /* known protocol, don't complain about it */
366 break;
367
368 default:
369 verbosef("ip: unknown protocol %d", sm->proto);
370 }
371 }
372
373 /* vim:set ts=3 sw=3 tw=78 expandtab: */