X-Git-Url: https://unix4lyfe.org/gitweb/darkstat/blobdiff_plain/6600552f3872de8309d863b6f6bf3b22f0588c9c..ca53027b6caadde18ec7b94d11853f2373bd658e:/decode.c diff --git a/decode.c b/decode.c index 0cd6cb3..e8e5f35 100644 --- a/decode.c +++ b/decode.c @@ -1,5 +1,5 @@ /* darkstat 3 - * copyright (c) 2001-2009 Emil Mikulic. + * copyright (c) 2001-2011 Emil Mikulic. * * decode.c: packet decoding. * @@ -10,52 +10,50 @@ * GNU General Public License version 2. (see COPYING.GPL) */ -#include "darkstat.h" -#include "acct.h" -#include "cap.h" +#include "cdefs.h" +#include "decode.h" +#include "err.h" +#include "opt.h" #include #include #include -#include "err.h" #include #include #include #include /* inet_ntoa() */ +#include /* struct ifreq */ /* need struct ether_header */ -#if defined(__NetBSD__) || defined(__OpenBSD__) -# include -# include -# include +#ifdef __NetBSD__ /* works for NetBSD 5.1.2 */ # include #else -# ifdef __sun -# include -# define ETHER_HDR_LEN 14 +# ifdef __OpenBSD__ +# include +# include +# include # else -# ifdef _AIX -# include +# ifdef __sun +# include # define ETHER_HDR_LEN 14 # else -# include +# ifdef _AIX +# include +# define ETHER_HDR_LEN 14 +# else +# include +# endif # endif # endif #endif + #ifndef ETHERTYPE_PPPOE -#define ETHERTYPE_PPPOE 0x8864 +# define ETHERTYPE_PPPOE 0x8864 #endif - #ifndef ETHERTYPE_IPV6 -# ifdef HAVE_NET_IF_ETHER_H -# include /* ETH_P_IPV6 for GNU/kfreebsd */ -# endif -# ifdef ETH_P_IPV6 -# define ETHERTYPE_IPV6 ETH_P_IPV6 -# endif +# define ETHERTYPE_IPV6 0x86DD #endif -#include /* struct ifreq */ #include /* n_long */ #include /* struct ip */ #include /* struct ip6_hdr */ @@ -63,33 +61,49 @@ #include /* struct tcphdr */ #include /* struct udphdr */ -extern int want_pppoe; - -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_pppoe_real(const u_char *pdata, const uint32_t len, - pktsummary *sm); -static void decode_linux_sll(u_char *, const struct pcap_pkthdr *, - const u_char *); -static void decode_raw(u_char *, const struct pcap_pkthdr *, - const u_char *); -static void decode_ip(const u_char *pdata, const uint32_t len, - pktsummary *sm); -static void decode_ipv6(const u_char *pdata, const uint32_t len, - pktsummary *sm); +#define PPP_HDR_LEN 4 +#define FDDI_HDR_LEN 21 +#define IP_HDR_LEN sizeof(struct ip) +#define IPV6_HDR_LEN sizeof(struct ip6_hdr) +#define TCP_HDR_LEN sizeof(struct tcphdr) +#define UDP_HDR_LEN sizeof(struct udphdr) +#define NULL_HDR_LEN 4 +#define SLL_HDR_LEN 16 +#define RAW_HDR_LEN 0 + +#ifndef IPV6_VERSION +# define IPV6_VERSION 0x60 +#endif + +#ifndef IPV6_VERSION_MASK +# define IPV6_VERSION_MASK 0xF0 +#endif + +static int decode_ether(DECODER_ARGS); +static int decode_loop(DECODER_ARGS); +static int decode_null(DECODER_ARGS); +static int decode_ppp(DECODER_ARGS); +static int decode_pppoe(DECODER_ARGS); +#ifdef DLT_LINUX_SLL +static int decode_linux_sll(DECODER_ARGS); +#endif +static int decode_raw(DECODER_ARGS); + +#define HELPER_ARGS const u_char *pdata, \ + const uint32_t len, \ + struct pktsummary *sm + +static int helper_pppoe(HELPER_ARGS); +static int helper_ip(HELPER_ARGS); +static int helper_ipv6(HELPER_ARGS); +static void helper_ip_deeper(HELPER_ARGS); /* protocols like TCP/UDP */ /* Link-type header information */ static const struct linkhdr 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_EN10MB, ETHER_HDR_LEN, decode_ether }, + { DLT_LOOP, NULL_HDR_LEN, decode_loop }, + { DLT_NULL, NULL_HDR_LEN, decode_null }, { DLT_PPP, PPP_HDR_LEN, decode_ppp }, #if defined(__NetBSD__) { DLT_PPP_SERIAL, PPP_HDR_LEN, decode_ppp }, @@ -100,208 +114,133 @@ static const struct linkhdr linkhdrs[] = { { DLT_LINUX_SLL, SLL_HDR_LEN, decode_linux_sll }, #endif { DLT_RAW, RAW_HDR_LEN, decode_raw }, - { -1, -1, NULL } + { -1, 0, NULL } }; -/* - * Returns a pointer to the linkhdr record matching the given linktype, or +/* Returns a pointer to the linkhdr record matching the given linktype, or * NULL if no matching entry found. */ -const struct linkhdr * -getlinkhdr(const int linktype) -{ +const struct linkhdr *getlinkhdr(const int linktype) { size_t i; for (i=0; linkhdrs[i].linktype != -1; i++) if (linkhdrs[i].linktype == linktype) return (&(linkhdrs[i])); - return (NULL); -} - -/* - * Returns the minimum snaplen needed to decode everything up to the TCP/UDP - * packet headers. The IPv6 header is normative. The argument lh is not - * allowed to be NULL. - */ -int -getsnaplen(const struct linkhdr *lh) -{ - assert(lh != NULL); - /* TODO MEA Investigate why the supplementary value 20 is needed on GNU/Linux. */ - return (20 + lh->hdrlen + IPV6_HDR_LEN + max(TCP_HDR_LEN, UDP_HDR_LEN)); + return NULL; } -/* - * Convert IP address to a presentation notation in a static buffer - * using inet_ntop(3). +/* Returns the minimum snaplen needed to decode everything up to and including + * the TCP/UDP packet headers. */ -char ipstr[INET6_ADDRSTRLEN]; /* TODO Reentrant? */ - -char * -ip_to_str(const struct addr46 *const ip) -{ - ipstr[0] = '\0'; - inet_ntop(ip->af, &ip->addr.ip6, ipstr, sizeof(ipstr)); - - return (ipstr); -} - -char * -ip_to_str_af(const void *const addr, sa_family_t af) -{ - ipstr[0] = '\0'; - inet_ntop(af, addr, ipstr, sizeof(ipstr)); - - return (ipstr); +int getsnaplen(const struct linkhdr *lh) { + return (int)(lh->hdrlen + IPV6_HDR_LEN + MAX(TCP_HDR_LEN, UDP_HDR_LEN)); } -/* Decoding functions. */ -static void -decode_ether(u_char *user _unused_, - const struct pcap_pkthdr *pheader, - const u_char *pdata) -{ +static int decode_ether(DECODER_ARGS) { u_short type; const struct ether_header *hdr = (const struct ether_header *)pdata; - pktsummary sm; - memset(&sm, 0, sizeof(sm)); - sm.time = pheader->ts.tv_sec; if (pheader->caplen < ETHER_HDR_LEN) { verbosef("ether: packet too short (%u bytes)", pheader->caplen); - return; + return 0; } - #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)); + 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)); + 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 ); + type = ntohs(hdr->ether_type); switch (type) { - case ETHERTYPE_IP: - case ETHERTYPE_IPV6: - if (!want_pppoe) { - decode_ip(pdata + ETHER_HDR_LEN, - pheader->caplen - ETHER_HDR_LEN, &sm); - acct_for(&sm); - } else + case ETHERTYPE_IP: + case ETHERTYPE_IPV6: + if (!opt_want_pppoe) + return helper_ip(pdata + ETHER_HDR_LEN, + pheader->caplen - ETHER_HDR_LEN, + sm); verbosef("ether: discarded IP packet, expecting PPPoE instead"); - break; - case ETHERTYPE_ARP: - /* known protocol, don't complain about it. */ - break; - case ETHERTYPE_PPPOE: - if (want_pppoe) - decode_pppoe_real(pdata + ETHER_HDR_LEN, - pheader->caplen - ETHER_HDR_LEN, &sm); - else + return 0; + case ETHERTYPE_PPPOE: + if (opt_want_pppoe) + return helper_pppoe(pdata + ETHER_HDR_LEN, + pheader->caplen - ETHER_HDR_LEN, + sm); verbosef("ether: got PPPoE frame: maybe you want --pppoe"); - break; - default: - verbosef("ether: unknown protocol (0x%04x)", type); + return 0; + case ETHERTYPE_ARP: + /* known protocol, don't complain about it. */ + return 0; + default: + verbosef("ether: unknown protocol (0x%04x)", type); + return 0; } } -static void -decode_loop(u_char *user _unused_, - const struct pcap_pkthdr *pheader, - const u_char *pdata) -{ +/* Very similar to decode_null, except on OpenBSD we need to think + * about family endianness. + */ +static int decode_loop(DECODER_ARGS) { 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; + return 0; } 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 if (family == AF_INET6) { - /* XXX: Check this! */ - 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); + if (family == AF_INET) + return helper_ip(pdata + NULL_HDR_LEN, + pheader->caplen - NULL_HDR_LEN, sm); + if (family == AF_INET6) + return helper_ipv6(pdata + NULL_HDR_LEN, + pheader->caplen - NULL_HDR_LEN, sm); + verbosef("loop: unknown family (0x%04x)", family); + return 0; } -static void -decode_ppp(u_char *user _unused_, - const struct pcap_pkthdr *pheader, - const u_char *pdata) -{ - pktsummary sm; - memset(&sm, 0, sizeof(sm)); +static int decode_null(DECODER_ARGS) { + uint32_t family; - if (pheader->caplen < PPPOE_HDR_LEN) { - verbosef("ppp: packet too short (%u bytes)", pheader->caplen); - return; + if (pheader->caplen < NULL_HDR_LEN) { + verbosef("null: packet too short (%u bytes)", pheader->caplen); + return 0; } - - 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)); - sm.time = pheader->ts.tv_sec; - decode_pppoe_real(pdata, pheader->caplen, &sm); + family = *(const uint32_t *)pdata; + if (family == AF_INET) + return helper_ip(pdata + NULL_HDR_LEN, + pheader->caplen - NULL_HDR_LEN, + sm); + if (family == AF_INET6) + return helper_ipv6(pdata + NULL_HDR_LEN, + pheader->caplen - NULL_HDR_LEN, + sm); + verbosef("null: unknown family (0x%04x)", family); + return 0; } -static void -decode_pppoe_real(const u_char *pdata, const uint32_t len, - pktsummary *sm) -{ - if (len < PPPOE_HDR_LEN) { - verbosef("pppoe: packet too short (%u bytes)", len); - return; - } - - if (pdata[1] != 0x00) { - verbosef("pppoe: code = 0x%02x, expecting 0; ignoring.", pdata[1]); - return; +static int decode_ppp(DECODER_ARGS) { + if (pheader->caplen < PPPOE_HDR_LEN) { + verbosef("ppp: packet too short (%u bytes)", pheader->caplen); + return 0; } + if (pdata[2] == 0x00 && pdata[3] == 0x21) + return helper_ip(pdata + PPP_HDR_LEN, + pheader->caplen - PPP_HDR_LEN, + sm); + verbosef("ppp: non-IP PPP packet; ignoring."); + return 0; +} - if ((pdata[6] == 0xc0) && (pdata[7] == 0x21)) return; /* LCP */ - if ((pdata[6] == 0xc0) && (pdata[7] == 0x25)) return; /* LQR */ - - if ((pdata[6] == 0x00) && (pdata[7] == 0x21)) { - decode_ip(pdata + PPPOE_HDR_LEN, len - PPPOE_HDR_LEN, sm); - acct_for(sm); - } else - verbosef("pppoe: non-IP PPPoE packet (0x%02x%02x); ignoring.", - pdata[6], pdata[7]); +static int decode_pppoe(DECODER_ARGS) { + return helper_pppoe(pdata, pheader->caplen, sm); } +#ifdef DLT_LINUX_SLL /* very similar to decode_ether ... */ -static void -decode_linux_sll(u_char *user _unused_, - const struct pcap_pkthdr *pheader, - const u_char *pdata) -{ +static int decode_linux_sll(DECODER_ARGS) { const struct sll_header { uint16_t packet_type; uint16_t device_type; @@ -311,159 +250,166 @@ decode_linux_sll(u_char *user _unused_, 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; + return 0; } - - type = ntohs( hdr->ether_type ); + type = ntohs(hdr->ether_type); switch (type) { case ETHERTYPE_IP: case ETHERTYPE_IPV6: - decode_ip(pdata + SLL_HDR_LEN, pheader->caplen - SLL_HDR_LEN, &sm); - sm.time = pheader->ts.tv_sec; - acct_for(&sm); - break; + return helper_ip(pdata + SLL_HDR_LEN, + pheader->caplen - SLL_HDR_LEN, + sm); case ETHERTYPE_ARP: /* known protocol, don't complain about it. */ - break; + return 0; default: - verbosef("linux_sll: unknown protocol (%04x)", type); + verbosef("linux_sll: unknown protocol (0x%04x)", type); + return 0; } } +#endif /* DLT_LINUX_SLL */ -static void -decode_raw(u_char *user _unused_, - const struct pcap_pkthdr *pheader, - const u_char *pdata) -{ - pktsummary sm; - memset(&sm, 0, sizeof(sm)); - - decode_ip(pdata, pheader->caplen, &sm); - sm.time = pheader->ts.tv_sec; - acct_for(&sm); +static int decode_raw(DECODER_ARGS) { + return helper_ip(pdata, pheader->caplen, sm); } -static void -decode_ip(const u_char *pdata, const uint32_t len, pktsummary *sm) -{ - const struct ip *hdr = (const struct ip *)pdata; +static int helper_pppoe(HELPER_ARGS) { + if (len < PPPOE_HDR_LEN) { + verbosef("pppoe: packet too short (%u bytes)", len); + return 0; + } - if (hdr->ip_v == 6) { - /* Redirect parsing of IPv6 packets. */ - decode_ipv6(pdata, len, sm); - return; + if (pdata[1] != 0x00) { + verbosef("pppoe: code = 0x%02x, expecting 0; ignoring.", pdata[1]); + return 0; } + + if ((pdata[6] == 0xc0) && (pdata[7] == 0x21)) return 0; /* LCP */ + if ((pdata[6] == 0xc0) && (pdata[7] == 0x25)) return 0; /* LQR */ + + if ((pdata[6] == 0x00) && (pdata[7] == 0x21)) + return helper_ip(pdata + PPPOE_HDR_LEN, len - PPPOE_HDR_LEN, sm); + + verbosef("pppoe: ignoring non-IP PPPoE packet (0x%02x%02x)", + pdata[6], pdata[7]); + return 0; +} + +static int helper_ip(HELPER_ARGS) { + const struct ip *hdr = (const struct ip *)pdata; + if (len < IP_HDR_LEN) { verbosef("ip: packet too short (%u bytes)", len); - return; + return 0; + } + if (hdr->ip_v == 6) { + return helper_ipv6(pdata, len, sm); } if (hdr->ip_v != 4) { verbosef("ip: version %d (expecting 4 or 6)", hdr->ip_v); - return; + return 0; } sm->len = ntohs(hdr->ip_len); - sm->af = AF_INET; sm->proto = hdr->ip_p; - memcpy(&sm->src_ip, &hdr->ip_src, sizeof(sm->src_ip)); - memcpy(&sm->dest_ip, &hdr->ip_dst, sizeof(sm->dest_ip)); - 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; - } + sm->src.family = IPv4; + sm->src.ip.v4 = hdr->ip_src.s_addr; - case IPPROTO_ICMP: - case IPPROTO_AH: - case IPPROTO_ESP: - case IPPROTO_OSPF: - /* known protocol, don't complain about it */ - break; + sm->dst.family = IPv4; + sm->dst.ip.v4 = hdr->ip_dst.s_addr; - default: - verbosef("ip: unknown protocol %d", sm->proto); - } + helper_ip_deeper(pdata + IP_HDR_LEN, len - IP_HDR_LEN, sm); + return 1; } -static void -decode_ipv6(const u_char *pdata, const uint32_t len, pktsummary *sm) -{ +static int helper_ipv6(HELPER_ARGS) { const struct ip6_hdr *hdr = (const struct ip6_hdr *)pdata; if (len < IPV6_HDR_LEN) { verbosef("ipv6: packet too short (%u bytes)", len); - return; + return 0; + } + if ((hdr->ip6_vfc & IPV6_VERSION_MASK) != IPV6_VERSION) { + verbosef("ipv6: bad version (%02x, expecting %02x)", + hdr->ip6_vfc & IPV6_VERSION_MASK, IPV6_VERSION); + return 0; } + /* IPv4 has "total length," but IPv6 has "payload length" which doesn't + * count the header bytes. + */ sm->len = ntohs(hdr->ip6_plen) + IPV6_HDR_LEN; - sm->af = AF_INET6; sm->proto = hdr->ip6_nxt; - memcpy(&sm->src_ip6, &hdr->ip6_src, sizeof(sm->src_ip6)); - memcpy(&sm->dest_ip6, &hdr->ip6_dst, sizeof(sm->dest_ip6)); + sm->src.family = IPv6; + memcpy(&sm->src.ip.v6, &hdr->ip6_src, sizeof(sm->src.ip.v6)); + sm->dst.family = IPv6; + memcpy(&sm->dst.ip.v6, &hdr->ip6_dst, sizeof(sm->dst.ip.v6)); + + /* Don't do proto accounting if this packet uses extension headers. */ + switch (sm->proto) { + case 0: /* Hop-by-Hop Options */ + case IPPROTO_NONE: + case IPPROTO_DSTOPTS: + case IPPROTO_ROUTING: + case IPPROTO_FRAGMENT: + case IPPROTO_AH: + case IPPROTO_ESP: + case 135: /* Mobility */ + sm->proto = IPPROTO_INVALID; + return 1; /* but we have addresses, so host accounting is ok */ + + default: + helper_ip_deeper(pdata + IPV6_HDR_LEN, len - IPV6_HDR_LEN, sm); + return 1; + } +} +static void helper_ip_deeper(HELPER_ARGS) { + /* At this stage we have IP addresses so we can do host accounting. + * If proto decode fails, we set IPPROTO_INVALID to skip proto accounting. + * We don't need to "return 0" like other helpers. + */ switch (sm->proto) { case IPPROTO_TCP: { - const struct tcphdr *thdr = - (const struct tcphdr *)(pdata + IPV6_HDR_LEN); - if (len < IPV6_HDR_LEN + TCP_HDR_LEN) { - verbosef("tcp6: packet too short (%u bytes)", len); + const struct tcphdr *thdr = (const struct tcphdr *)pdata; + if (len < TCP_HDR_LEN) { + verbosef("tcp: packet too short (%u bytes)", len); + sm->proto = IPPROTO_INVALID; /* don't do accounting! */ return; } sm->src_port = ntohs(thdr->th_sport); - sm->dest_port = ntohs(thdr->th_dport); + sm->dst_port = ntohs(thdr->th_dport); sm->tcp_flags = thdr->th_flags & (TH_FIN|TH_SYN|TH_RST|TH_PUSH|TH_ACK|TH_URG); - break; + return; } case IPPROTO_UDP: { - const struct udphdr *uhdr = - (const struct udphdr *)(pdata + IPV6_HDR_LEN); - if (len < IPV6_HDR_LEN + UDP_HDR_LEN) { - verbosef("udp6: packet too short (%u bytes)", len); + const struct udphdr *uhdr = (const struct udphdr *)pdata; + if (len < UDP_HDR_LEN) { + verbosef("udp: packet too short (%u bytes)", len); + sm->proto = IPPROTO_INVALID; /* don't do accounting! */ return; } sm->src_port = ntohs(uhdr->uh_sport); - sm->dest_port = ntohs(uhdr->uh_dport); - break; + sm->dst_port = ntohs(uhdr->uh_dport); + return; } + case IPPROTO_ICMP: + case IPPROTO_IGMP: case IPPROTO_ICMPV6: - case IPPROTO_AH: - case IPPROTO_ESP: case IPPROTO_OSPF: /* known protocol, don't complain about it */ + sm->proto = IPPROTO_INVALID; /* also don't do accounting */ break; default: - verbosef("ipv6: unknown protocol %d", sm->proto); + verbosef("ip_deeper: unknown protocol 0x%02x", sm->proto); } }