/* darkstat 3 * copyright (c) 2001-2007 Emil Mikulic. * * decode.c: packet decoding. * * Given a captured packet, decode it and fill out a pktsummary struct which * will be sent to the accounting code in acct.c * * You may use, modify and redistribute this file under the terms of the * GNU General Public License version 2. (see COPYING.GPL) */ #include "darkstat.h" #include "acct.h" #include "cap.h" #include #include #include #include "err.h" #include #include #include #include /* inet_ntoa() */ /* need struct ether_header */ #if defined(__NetBSD__) || defined(__OpenBSD__) # include # include # include # include #else # ifdef __sun # include # define ETHER_HDR_LEN 14 # else # ifdef _AIX # include # define ETHER_HDR_LEN 14 # else # include # endif # endif #endif #include /* struct ifreq */ #include /* n_long */ #include /* struct ip */ #define __FAVOR_BSD #include /* struct tcphdr */ #include /* struct udphdr */ static void decode_ether(u_char *, const struct pcap_pkthdr *, const u_char *); static void decode_loop(u_char *, const struct pcap_pkthdr *, const u_char *); static void decode_ppp(u_char *, const struct pcap_pkthdr *, const u_char *); static void decode_pppoe(u_char *, const struct pcap_pkthdr *, const u_char *); static void decode_linux_sll(u_char *, const struct pcap_pkthdr *, const u_char *); static void decode_ip(const u_char *pdata, const uint32_t len, pktsummary *sm); /* Link-type header information */ static const linkhdr_t linkhdrs[] = { /* linktype hdrlen handler */ { DLT_EN10MB, ETHER_HDR_LEN, decode_ether }, { DLT_LOOP, NULL_HDR_LEN, decode_loop }, { DLT_NULL, NULL_HDR_LEN, decode_loop }, { DLT_PPP, PPP_HDR_LEN, decode_ppp }, #if defined(__NetBSD__) { DLT_PPP_SERIAL, PPP_HDR_LEN, decode_ppp }, #endif { DLT_FDDI, FDDI_HDR_LEN, NULL }, { DLT_PPP_ETHER, PPPOE_HDR_LEN, decode_pppoe }, #ifdef DLT_LINUX_SLL { DLT_LINUX_SLL, SLL_HDR_LEN, decode_linux_sll }, #endif { -1, -1, NULL } }; /* * Returns a pointer to the linkhdr_t record matching the given linktype, or * NULL if no matching entry found. */ const linkhdr_t * getlinkhdr(int linktype) { int i; for (i=0; linkhdrs[i].linktype != -1; i++) if (linkhdrs[i].linktype == linktype) return (&(linkhdrs[i])); return (NULL); } /* * Returns the minimum caplen needed to decode everything up to the TCP/UDP * packet headers. Argument lh is not allowed to be NULL. */ int getcaplen(const linkhdr_t *lh) { assert(lh != NULL); return (lh->hdrlen + IP_HDR_LEN + max(TCP_HDR_LEN, UDP_HDR_LEN)); } /* * Convert IP address to a numbers-and-dots notation in a static buffer * provided by inet_ntoa(). */ char * ip_to_str(const in_addr_t ip) { struct in_addr in; in.s_addr = ip; return (inet_ntoa(in)); } /* Decoding functions. */ static void decode_ether(u_char *user _unused_, const struct pcap_pkthdr *pheader, const u_char *pdata) { u_short type; const struct ether_header *hdr = (const struct ether_header *)pdata; pktsummary sm; memset(&sm, 0, sizeof(sm)); if (pheader->caplen < ETHER_HDR_LEN) { verbosef("ether: packet too short (%u bytes)", pheader->caplen); return; } #ifdef __sun memcpy(sm.src_mac, hdr->ether_shost.ether_addr_octet, sizeof(sm.src_mac)); memcpy(sm.dst_mac, hdr->ether_dhost.ether_addr_octet, sizeof(sm.dst_mac)); #else memcpy(sm.src_mac, hdr->ether_shost, sizeof(sm.src_mac)); memcpy(sm.dst_mac, hdr->ether_dhost, sizeof(sm.dst_mac)); #endif type = ntohs( hdr->ether_type ); switch (type) { case ETHERTYPE_IP: decode_ip(pdata + ETHER_HDR_LEN, pheader->caplen - ETHER_HDR_LEN, &sm); sm.time = pheader->ts.tv_sec; acct_for(&sm); break; case ETHERTYPE_ARP: /* known protocol, don't complain about it. */ break; default: verbosef("ether: unknown protocol (%04x)", type); } } static void decode_loop(u_char *user _unused_, const struct pcap_pkthdr *pheader, const u_char *pdata) { uint32_t family; pktsummary sm; memset(&sm, 0, sizeof(sm)); if (pheader->caplen < NULL_HDR_LEN) { verbosef("loop: packet too short (%u bytes)", pheader->caplen); return; } family = *(const uint32_t *)pdata; #ifdef __OpenBSD__ family = ntohl(family); #endif if (family == AF_INET) { /* OpenBSD tun or FreeBSD tun or FreeBSD lo */ decode_ip(pdata + NULL_HDR_LEN, pheader->caplen - NULL_HDR_LEN, &sm); sm.time = pheader->ts.tv_sec; acct_for(&sm); } else verbosef("loop: unknown family (%x)", family); } static void decode_ppp(u_char *user _unused_, const struct pcap_pkthdr *pheader, const u_char *pdata) { pktsummary sm; memset(&sm, 0, sizeof(sm)); if (pdata[2] == 0x00 && pdata[3] == 0x21) { decode_ip(pdata + PPP_HDR_LEN, pheader->caplen - PPP_HDR_LEN, &sm); sm.time = pheader->ts.tv_sec; acct_for(&sm); } else verbosef("non-IP PPP packet; ignoring."); } static void decode_pppoe(u_char *user _unused_, const struct pcap_pkthdr *pheader, const u_char *pdata) { pktsummary sm; memset(&sm, 0, sizeof(sm)); if (pheader->caplen < PPPOE_HDR_LEN) { verbosef("loop: packet too short (%u bytes)", pheader->caplen); return; } /* * First, check if this is a session PPPoE packet * by checking the CODE part of the header * (2nd byte in header) */ if (pdata[1] == 0x00) { /* * Next, check if PPP packet type is 0x0021, * which is PPP_IP */ if (pdata[6] == 0x00 && pdata[7] == 0x21) { decode_ip(pdata + PPPOE_HDR_LEN, pheader->caplen - PPPOE_HDR_LEN, &sm); sm.time = pheader->ts.tv_sec; acct_for(&sm); } else verbosef("non-IP PPPoE packet; ignoring."); } else verbosef("non-session PPPoE packet; ignoring."); } /* very similar to decode_ether ... */ static void decode_linux_sll(u_char *user _unused_, const struct pcap_pkthdr *pheader, const u_char *pdata) { const struct sll_header { uint16_t packet_type; uint16_t device_type; uint16_t addr_length; #define SLL_MAX_ADDRLEN 8 uint8_t addr[SLL_MAX_ADDRLEN]; uint16_t ether_type; } *hdr = (const struct sll_header *)pdata; u_short type; pktsummary sm; memset(&sm, 0, sizeof(sm)); if (pheader->caplen < SLL_HDR_LEN) { verbosef("linux_sll: packet too short (%u bytes)", pheader->caplen); return; } type = ntohs( hdr->ether_type ); switch (type) { case ETHERTYPE_IP: decode_ip(pdata + SLL_HDR_LEN, pheader->caplen - SLL_HDR_LEN, &sm); sm.time = pheader->ts.tv_sec; acct_for(&sm); break; case ETHERTYPE_ARP: /* known protocol, don't complain about it. */ break; default: verbosef("linux_sll: unknown protocol (%04x)", type); } } static void decode_ip(const u_char *pdata, const uint32_t len, pktsummary *sm) { const struct ip *hdr = (const struct ip *)pdata; if (len < IP_HDR_LEN) { verbosef("ip: packet too short (%u bytes)", len); return; } if (hdr->ip_v != 4) { verbosef("ip: version %d (expecting 4)", hdr->ip_v); return; } sm->len = ntohs(hdr->ip_len); sm->proto = hdr->ip_p; sm->src_ip = hdr->ip_src.s_addr; sm->dest_ip = hdr->ip_dst.s_addr; switch (sm->proto) { case IPPROTO_TCP: { const struct tcphdr *thdr = (const struct tcphdr *)(pdata + IP_HDR_LEN); if (len < IP_HDR_LEN + TCP_HDR_LEN) { verbosef("tcp: packet too short (%u bytes)", len); return; } sm->src_port = ntohs(thdr->th_sport); sm->dest_port = ntohs(thdr->th_dport); sm->tcp_flags = thdr->th_flags & (TH_FIN|TH_SYN|TH_RST|TH_PUSH|TH_ACK|TH_URG); break; } case IPPROTO_UDP: { const struct udphdr *uhdr = (const struct udphdr *)(pdata + IP_HDR_LEN); if (len < IP_HDR_LEN + UDP_HDR_LEN) { verbosef("udp: packet too short (%u bytes)", len); return; } sm->src_port = ntohs(uhdr->uh_sport); sm->dest_port = ntohs(uhdr->uh_dport); break; } case IPPROTO_ICMP: /* known protocol, don't complain about it */ break; default: verbosef("ip: unknown protocol %d", sm->proto); } } /* vim:set ts=3 sw=3 tw=78 expandtab: */