diff options
Diffstat (limited to 'src/pcap_layers/pcap_layers.c')
-rw-r--r-- | src/pcap_layers/pcap_layers.c | 678 |
1 files changed, 678 insertions, 0 deletions
diff --git a/src/pcap_layers/pcap_layers.c b/src/pcap_layers/pcap_layers.c new file mode 100644 index 0000000..c4bc42d --- /dev/null +++ b/src/pcap_layers/pcap_layers.c @@ -0,0 +1,678 @@ +/* + * Copyright (c) 2016 Duane Wessels and The Measurement Factory, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#define _DEFAULT_SOURCE 1 +#define _BSD_SOURCE 1 + +#include <sys/types.h> +#include <sys/time.h> +#include <sys/stat.h> + +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <ctype.h> +#include <assert.h> +#include <arpa/inet.h> +#include <arpa/nameser.h> + +#ifndef USE_IPV6 +#define USE_IPV6 1 +#endif + +#include "byteorder.h" +#include "pcap_layers.h" + +#ifndef PCAP_SNAPLEN +#define PCAP_SNAPLEN 1460 +#endif +#ifndef ETHER_HDR_LEN +#define ETHER_ADDR_LEN 6 +#define ETHER_TYPE_LEN 2 +#define ETHER_HDR_LEN (ETHER_ADDR_LEN * 2 + ETHER_TYPE_LEN) +#endif +#ifndef ETHERTYPE_8021Q +#define ETHERTYPE_8021Q 0x8100 +#endif + +#if USE_PPP +#include <net/if_ppp.h> +#define PPP_ADDRESS_VAL 0xff /* The address byte value */ +#define PPP_CONTROL_VAL 0x03 /* The control byte value */ +#endif + +#ifdef DLT_LINUX_SLL +#ifdef HAVE_PCAP_SLL_H +#include <pcap/sll.h> +#else +#error "DLT_LINUX_SLL defined but no <pcap/sll.h> (HAVE_PCAP_SLL_H)" +#endif +#endif + +#ifndef IP_OFFMASK +#define IP_OFFMASK 0x1fff +#endif + +#define XMIN(a,b) ((a)<(b)?(a):(b)) + +typedef struct _ipV4Frag { + uint32_t offset; + uint32_t len; + char *buf; + struct _ipV4Frag *next; + u_char more; +} ipV4Frag; + +typedef struct _ipV4Flow { + uint16_t ip_id; + uint8_t ip_p; + struct in_addr src; + struct in_addr dst; + struct _ipV4Flow *next; + ipV4Frag *frags; + time_t ts; +} ipV4Flow; + +static ipV4Flow *ipV4Flows = NULL; +static int _reassemble_fragments = 0; + +static void (*handle_datalink) (const u_char * pkt, int len, void *userdata)= NULL; + +int (*callback_ether) (const u_char * pkt, int len, void *userdata)= NULL; +int (*callback_vlan) (unsigned short vlan, void *userdata)= NULL; +int (*callback_ipv4) (const struct ip *ipv4, int len, void *userdata)= NULL; +int (*callback_ipv6) (const struct ip6_hdr *ipv6, int len, void *userdata)= NULL; +int (*callback_gre) (const u_char *pkt, int len, void *userdata)= NULL; +int (*callback_tcp) (const struct tcphdr *tcp, int len, void *userdata)= NULL; +int (*callback_udp) (const struct udphdr *udp, int len, void *userdata)= NULL; +int (*callback_tcp_sess) (const struct tcphdr *tcp, int len, void *userdata, l7_callback *)= NULL; +int (*callback_l7) (const u_char * l7, int len, void *userdata)= NULL; + +/* need prototypes for GRE recursion */ +void handle_ip(const u_char *pkt, int len, void *userdata); +static int is_ethertype_ip(unsigned short proto); + +void +handle_l7(const u_char * pkt, int len, void *userdata) +{ + if (callback_l7) + callback_l7(pkt, len, userdata); +} + +void +handle_tcp_session(const struct tcphdr *tcp, int len, void *userdata) +{ + if (callback_tcp_sess) + callback_tcp_sess(tcp, len, userdata, callback_l7); + else if (callback_l7) + callback_l7((u_char *) tcp + (tcp->th_off<<2), len - (tcp->th_off<<2), userdata); +} + +void +handle_udp(const struct udphdr *udp, int len, void *userdata) +{ + if (len < sizeof(*udp)) + return; + if (callback_udp) + if (0 != callback_udp(udp, len, userdata)) + return; + handle_l7((u_char *) (udp + 1), len - sizeof(*udp), userdata); +} + +void +handle_tcp(const struct tcphdr *tcp, int len, void *userdata) +{ + if (len < sizeof(*tcp)) + return; + if (callback_tcp) + if (0 != callback_tcp(tcp, len, userdata)) + return; + handle_tcp_session(tcp, len, userdata); +} + +void +pcap_layers_clear_fragments(time_t older_then) { + ipV4Flow *l; + ipV4Flow **L; + ipV4Frag *f = NULL; + ipV4Frag *ff = NULL; + +#if DEBUG + fprintf(stderr, "dropping frags older then %ld\n", older_then); +#endif + + for (L = &ipV4Flows; *L;) { + if ((*L)->ts < older_then) { + l = *L; + *L = (*L)->next; +#if DEBUG + fprintf(stderr, "dropping saved flow for i=%hx s=%x d=%x p=%d\n", l->ip_id, l->src.s_addr, l->dst.s_addr, l->ip_p); +#endif + for (f = l->frags; f;) { + ff = f; + f = f->next; + free(ff->buf); + free(ff); + } + free(l); + } + else { + L = &(*L)->next; + } + } +} + +void +handle_ipv4_fragment(const struct ip *ip, int len, void *userdata) +{ + ipV4Flow *l = NULL; + ipV4Flow **L = NULL; + ipV4Frag *f = NULL; + ipV4Frag *nf = NULL; + ipV4Frag **F = NULL; + uint16_t ip_off = ntohs(ip->ip_off), ip_len; + uint32_t s = 0; + char *newbuf = NULL; + if (ip_off & IP_OFFMASK) { + for (l = ipV4Flows; l; l = l->next) { + if (l->ip_id != ntohs(ip->ip_id)) + continue; + if (l->src.s_addr != ip->ip_src.s_addr) + continue; + if (l->dst.s_addr != ip->ip_dst.s_addr) + continue; + if (l->ip_p != ip->ip_p) + continue; + break; + } +#if DEBUG + if (l) + fprintf(stderr, "found saved flow for i=%hx s=%x d=%x p=%d\n", l->ip_id, l->src.s_addr, l->dst.s_addr, l->ip_p); +#endif + } else { + l = calloc(1, sizeof(*l)); + assert(l); + l->ip_id = ntohs(ip->ip_id); + l->ip_p = ip->ip_p; + l->src = ip->ip_src; + l->dst = ip->ip_dst; + l->next = ipV4Flows; + ipV4Flows = l; +#if DEBUG + fprintf(stderr, "created saved flow for i=%hx s=%x d=%x p=%d\n", l->ip_id, l->src.s_addr, l->dst.s_addr, l->ip_p); +#endif + } + if (NULL == l) /* didn't find or couldn't create state */ + return; + l->ts = time(NULL); + /* + * Store new frag + */ + ip_len = ntohs(ip->ip_len); + if (ip_len < (ip->ip_hl << 2)) + return; + f = calloc(1, sizeof(*f)); + assert(f); + f->offset = (ip_off & IP_OFFMASK) << 3; + f->len = ip_len - (ip->ip_hl << 2); + f->buf = malloc(f->len); + f->more = (ip_off & IP_MF) ? 1 : 0; + assert(f->buf); + memcpy(f->buf, (char *)ip + (ip->ip_hl << 2), f->len); + /* + * Insert frag into list ordered by offset + */ + for (F = &l->frags; *F && ((*F)->offset < f->offset); F = &(*F)->next); + f->next = *F; + *F = f; +#if DEBUG + fprintf(stderr, "saved frag o=%u l=%u\n", f->offset, f->len); +#endif + /* + * Do we have the whole packet? + */ + for (f = l->frags; f; f = f->next) { +#if DEBUG + fprintf(stderr, " frag %u:%u mf=%d\n", f->offset, f->len, f->more); +#endif + if (f->offset > s) /* gap */ + return; + s = f->offset + f->len; + if (!f->more) + break; + } + if (NULL == f) /* didn't find last frag */ + return; + +#if DEBUG + fprintf(stderr, "have whole packet s=%u, mf=%u\n", s, f->more); +#endif + /* + * Reassemble, free, deliver + */ + newbuf = malloc(s); + nf = l->frags; + while ((f = nf)) { + nf = f->next; + if (s >= f->offset + f->len) { + /* + * buffer overflow protection. When s was calculated above, + * the for loop breaks upon no more fragments. But there + * could be multiple fragments with more=0. So here we make + * sure the memcpy doesn't exceed the size of newbuf. + */ +#if DEBUG + fprintf(stderr, "reassemble memcpy (%p, %p, %u, more=%u\n", newbuf+f->offset,f->buf,f->len,f->more); +#endif + memcpy(newbuf + f->offset, f->buf, f->len); + } + free(f->buf); + free(f); + } + for (L = &ipV4Flows; *L; L = &(*L)->next) { + if (*L == l) { + *L = (*L)->next; + free(l); + break; + } + } +#if DEBUG + fprintf(stderr, "delivering reassmebled packet\n"); +#endif + if (IPPROTO_UDP == ip->ip_p) { + handle_udp((struct udphdr *)newbuf, s, userdata); + } else if (IPPROTO_TCP == ip->ip_p) { + handle_tcp((struct tcphdr *)newbuf, s, userdata); + } +#if DEBUG + fprintf(stderr, "freeing newbuf\n"); +#endif + free(newbuf); +} + +void +handle_gre(const u_char * gre, int len, void *userdata) +{ + int grelen = 4; + unsigned short flags, etype; + if (len < grelen) + return; + flags = nptohs(gre); + etype = nptohs(gre + 2); + if (callback_gre) + if (0 != callback_gre(gre, len, userdata)) + return; + + if (flags & 0x0001) /* checksum present? */ + grelen += 4; + if (flags & 0x0004) /* key present? */ + grelen += 4; + if (flags & 0x0008) /* sequence number present? */ + grelen += 4; + if (len < grelen) + return; + + gre += grelen; + len -= grelen; + + if (is_ethertype_ip(etype)) + handle_ip(gre, len, userdata); +} + +/* + * When passing on to the next layers, use the ip_len + * value for the length, unless the given len happens to + * to be less for some reason. Note that ip_len might + * be less than len due to Ethernet padding. + */ +void +handle_ipv4(const u_char * pkt, int len, void *userdata) +{ + const struct ip *ip = (const struct ip *)pkt; + int offset; + int iplen; + uint16_t ip_off; + + if (len < sizeof(*ip)) + return; + offset = ip->ip_hl << 2; + iplen = XMIN(nptohs(&ip->ip_len), len); + if (callback_ipv4) + if (0 != callback_ipv4(ip, iplen, userdata)) + return; + ip_off = ntohs(ip->ip_off); + if ((ip_off & (IP_OFFMASK | IP_MF))) { + if (_reassemble_fragments) + handle_ipv4_fragment(ip, iplen, userdata); + } else if (IPPROTO_UDP == ip->ip_p) { + handle_udp((struct udphdr *)((char *)ip + offset), iplen - offset, userdata); + } else if (IPPROTO_TCP == ip->ip_p) { + handle_tcp((struct tcphdr *)((char *)ip + offset), iplen - offset, userdata); + } else if (IPPROTO_GRE == ip->ip_p) { + handle_gre((u_char *)ip + offset, iplen - offset, userdata); + } +} + +void +handle_ipv6(const u_char * pkt, int len, void *userdata) +{ + const struct ip6_hdr *ip6 = (const struct ip6_hdr *)pkt; + int offset; + int nexthdr; + uint16_t payload_len; + + if (len < sizeof(*ip6)) + return; + if (callback_ipv6) + if (0 != callback_ipv6(ip6, len, userdata)) + return; + + offset = sizeof(struct ip6_hdr); + nexthdr = ip6->ip6_nxt; + payload_len = nptohs(&ip6->ip6_plen); + + /* + * Parse extension headers. This only handles the standard headers, as + * defined in RFC 2460, correctly. Fragments are discarded. + */ + while ((IPPROTO_ROUTING == nexthdr) /* routing header */ + ||(IPPROTO_HOPOPTS == nexthdr) /* Hop-by-Hop options. */ + ||(IPPROTO_FRAGMENT == nexthdr) /* fragmentation header. */ + ||(IPPROTO_DSTOPTS == nexthdr) /* destination options. */ + ||(IPPROTO_AH == nexthdr) /* authentication header. */ + ||(IPPROTO_ESP == nexthdr)) { /* encapsulating security payload. */ + typedef struct { + uint8_t nexthdr; + uint8_t length; + } ext_hdr_t; + ext_hdr_t *ext_hdr; + uint16_t ext_hdr_len; + + /* Catch broken packets */ + if ((offset + sizeof(ext_hdr)) > len) + return; + + /* Cannot handle fragments. */ + if (IPPROTO_FRAGMENT == nexthdr) + return; + + ext_hdr = (ext_hdr_t *) ((char *)ip6 + offset); + nexthdr = ext_hdr->nexthdr; + ext_hdr_len = (8 * (ext_hdr->length + 1)); + + /* This header is longer than the packets payload.. WTF? */ + if (ext_hdr_len > payload_len) + return; + + offset += ext_hdr_len; + payload_len -= ext_hdr_len; + } /* while */ + + /* Catch broken and empty packets */ + if (((offset + payload_len) > len) + || (payload_len == 0) + || (payload_len > PCAP_SNAPLEN)) + return; + + if (IPPROTO_UDP == nexthdr) { + handle_udp((struct udphdr *)((char *)ip6 + offset), payload_len, userdata); + } else if (IPPROTO_TCP == nexthdr) { + handle_tcp((struct tcphdr *)((char *)ip6 + offset), payload_len, userdata); + } else if (IPPROTO_GRE == nexthdr) { + handle_gre((u_char *)ip6 + offset, payload_len, userdata); + } +} + +void +handle_ip(const u_char * pkt, int len, void *userdata) +{ + if (len < 1) + return; + /* note: ip->ip_v does not work if header is not int-aligned */ + /* fprintf(stderr, "IPv %d\n", (*(uint8_t *) ip) >> 4); */ + switch (*pkt >> 4) { + case 4: + handle_ipv4(pkt, len, userdata); + break; + case 6: + handle_ipv6(pkt, len, userdata); + break; + default: + break; + } +} + +static int +is_ethertype_ip(unsigned short proto) +{ + if (ETHERTYPE_IP == proto) + return 1; +#if USE_PPP + if (PPP_IP == proto) + return 1; +#endif +#if USE_IPV6 && defined(ETHERTYPE_IPV6) + if (ETHERTYPE_IPV6 == proto) + return 1; +#endif + return 0; +} + +static int +is_family_inet(unsigned int family) +{ + if (AF_INET == family) + return 1; +#if USE_IPV6 + if (AF_INET6 == family) + return 1; +#endif + return 0; +} + +#if USE_PPP +void +handle_ppp(const u_char * pkt, int len, void *userdata) +{ + char buf[PCAP_SNAPLEN]; + unsigned short proto; + + if (len < 2) + return; + if (*pkt == PPP_ADDRESS_VAL && *(pkt + 1) == PPP_CONTROL_VAL) { + pkt += 2; /* ACFC not used */ + len -= 2; + } + if (len < 2) + return; + if (*pkt % 2) { + proto = *pkt; /* PFC is used */ + pkt++; + len--; + } else { + proto = nptohs(pkt); + pkt += 2; + len -= 2; + } + if (is_ethertype_ip(proto)) + handle_ip((struct ip *)pkt, len, userdata); +} +#endif + +void +handle_null(const u_char * pkt, int len, void *userdata) +{ + unsigned int family; + + if (len < 4) + return; + family = nptohl(pkt); + pkt += 4; + len -= 4; + + if (is_family_inet(family)) + handle_ip(pkt, len, userdata); +} + +#ifdef DLT_LOOP +void +handle_loop(const u_char * pkt, int len, void *userdata) +{ + unsigned int family; + + if (len < 4) + return; + family = nptohl(pkt); + pkt += 4; + len -= 4; + + if (is_family_inet(family)) + handle_ip(pkt, len, userdata); +} +#endif + +#ifdef DLT_RAW +void +handle_raw(const u_char * pkt, int len, void *userdata) +{ + handle_ip(pkt, len, userdata); +} +#endif + +#ifdef DLT_LINUX_SLL +void +handle_linux_sll(const u_char * pkt, int len, void *userdata) +{ + struct sll_header *s = (struct sll_header *)pkt; + unsigned short etype, eproto; + + if (len < SLL_HDR_LEN) + return; + etype = nptohs(&s->sll_pkttype); + if (callback_ether) + if (0 != callback_ether(pkt, len, userdata)) + return; + pkt += SLL_HDR_LEN; + len -= SLL_HDR_LEN; + if (ETHERTYPE_8021Q == etype) { + unsigned short vlan = nptohs(pkt); + if (len < 4) + return; + if (callback_vlan) + if (0 != callback_vlan(vlan, userdata)) + return; + // etype = nptohs(pkt + 2); + pkt += 4; + len -= 4; + } + eproto = nptohs(&s->sll_protocol); + /* fprintf(stderr, "linux cooked packet of len %d type %#04x proto %#04x\n", len, etype, eproto); */ + if (is_ethertype_ip(eproto)) { + handle_ip(pkt, len, userdata); + } +} +#endif + +void +handle_ether(const u_char * pkt, int len, void *userdata) +{ + struct ether_header *e = (struct ether_header *)pkt; + unsigned short etype; + + if (len < ETHER_HDR_LEN) + return; + etype = nptohs(&e->ether_type); + if (callback_ether) + if (0 != callback_ether(pkt, len, userdata)) + return; + pkt += ETHER_HDR_LEN; + len -= ETHER_HDR_LEN; + if (ETHERTYPE_8021Q == etype) { + unsigned short vlan = nptohs(pkt); + if (len < 4) + return; + if (callback_vlan) + if (0 != callback_vlan(vlan, userdata)) + return; + etype = nptohs(pkt + 2); + pkt += 4; + len -= 4; + } + /* fprintf(stderr, "Ethernet packet of len %d ethertype %#04x\n", len, etype); */ + if (is_ethertype_ip(etype)) { + handle_ip(pkt, len, userdata); + } +} + +void +handle_pcap(u_char * userdata, const struct pcap_pkthdr *hdr, const u_char * pkt) +{ + if (hdr->caplen < ETHER_HDR_LEN) + return; + handle_datalink(pkt, hdr->caplen, userdata); +} + + +int +pcap_layers_init(int dlt, int reassemble) +{ + switch (dlt) { + case DLT_EN10MB: + handle_datalink = handle_ether; + break; +#if USE_PPP + case DLT_PPP: + handle_datalink = handle_ppp; + break; +#endif +#ifdef DLT_LOOP + case DLT_LOOP: + handle_datalink = handle_loop; + break; +#endif +#ifdef DLT_RAW + case DLT_RAW: + handle_datalink = handle_raw; + break; +#endif +#ifdef DLT_LINUX_SLL + case DLT_LINUX_SLL: + handle_datalink = handle_linux_sll; + break; +#endif + case DLT_NULL: + handle_datalink = handle_null; + break; + default: + fprintf(stderr, "unsupported data link type %d", dlt); + exit(1); + break; + } + _reassemble_fragments = reassemble; + return 0; +} |