/* * Copyright (c) 2008-2024 OARC, Inc. * Copyright (c) 2007-2008, Internet Systems Consortium, Inc. * Copyright (c) 2003-2007, The Measurement Factory, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * 3. Neither the name of the copyright holder nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #include "pcap.h" #include "xmalloc.h" #include "syslog_debug.h" #include "hashtbl.h" #include "pcap_layers/byteorder.h" #include "pcap_layers/pcap_layers.h" #include "dns_protocol.h" #include "pcap-thread/pcap_thread.h" #include "compat.h" #include #include #include #include #include #define PCAP_SNAPLEN 65536 #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 #ifdef __OpenBSD__ #define assign_timeval(A, B) \ A.tv_sec = B.tv_sec; \ A.tv_usec = B.tv_usec #else #define assign_timeval(A, B) A = B #endif /* We might need to define ETHERTYPE_IPV6 */ #ifndef ETHERTYPE_IPV6 #define ETHERTYPE_IPV6 0x86dd #endif #ifdef __GLIBC__ #define uh_dport dest #define uh_sport source #define th_off doff #define th_dport dest #define th_sport source #define th_seq seq #define TCPFLAGFIN(a) (a)->fin #define TCPFLAGSYN(a) (a)->syn #define TCPFLAGRST(a) (a)->rst #else #define TCPFLAGSYN(a) ((a)->th_flags & TH_SYN) #define TCPFLAGFIN(a) ((a)->th_flags & TH_FIN) #define TCPFLAGRST(a) ((a)->th_flags & TH_RST) #endif #ifndef IP_OFFMASK #define IP_OFFMASK 0x1fff #endif struct _interface { char* device; struct pcap_stat ps0, ps1; unsigned int pkts_captured; }; #define MAX_N_INTERFACES 10 static int n_interfaces = 0; static struct _interface* interfaces = NULL; unsigned short port53 = 53; pcap_thread_t pcap_thread = PCAP_THREAD_T_INIT; int n_pcap_offline = 0; /* global so daemon.c can use it */ char* bpf_program_str = NULL; int vlan_tag_needs_byte_conversion = 1; #if 0 static int debug_count = 20; #endif struct timeval last_ts; static struct timeval start_ts; static struct timeval finish_ts; #define MAX_VLAN_IDS 100 static int n_vlan_ids = 0; static int vlan_ids[MAX_VLAN_IDS]; static hashtbl* tcpHash; static int pcap_udp_handler(const struct udphdr* udp, int len, void* udata) { transport_message* tm = udata; tm->src_port = nptohs(&udp->uh_sport); tm->dst_port = nptohs(&udp->uh_dport); tm->proto = IPPROTO_UDP; if (port53 != tm->dst_port && port53 != tm->src_port) return 1; return 0; } #define MAX_DNS_LENGTH 0xFFFF #define MAX_TCP_WINDOW_SIZE (0xFFFF << 14) #define MAX_TCP_STATE 65535 #define MAX_TCP_IDLE 60 /* tcpstate is tossed if idle for this many seconds */ #define MAX_FRAG_IDLE 60 /* keep fragments in pcap_layers for this many seconds */ /* These numbers define the sizes of small arrays which are simpler to work * with than dynamically allocated lists. */ #define MAX_TCP_MSGS 8 /* messages being reassembled (per connection) */ #define MAX_TCP_SEGS 8 /* segments not assigned to a message (per connection) */ #define MAX_TCP_HOLES 8 /* holes in a msg buf (per message) */ typedef struct { inX_addr src_ip_addr; inX_addr dst_ip_addr; uint16_t dport; uint16_t sport; } tcpHashkey_t; /* Description of hole in tcp reassembly buffer. */ typedef struct { uint16_t start; /* start of hole, measured from beginning of msgbuf->buf */ uint16_t len; /* length of hole (0 == unused) */ } tcphole_t; /* TCP message reassembly buffer */ typedef struct { uint32_t seq; /* seq# of first byte of header of this DNS msg */ uint16_t dnslen; /* length of dns message, and size of buf */ tcphole_t hole[MAX_TCP_HOLES]; int holes; /* number of holes remaining in message */ u_char buf[]; /* reassembled message (C99 flexible array member) */ } tcp_msgbuf_t; /* held TCP segment */ typedef struct { uint32_t seq; /* sequence number of first byte of segment */ uint16_t len; /* length of segment, and size of buf */ u_char buf[]; /* segment payload (C99 flexible array member) */ } tcp_segbuf_t; /* TCP reassembly state */ typedef struct tcpstate { tcpHashkey_t key; struct tcpstate *newer, *older; long last_use; uint32_t seq_start; /* seq# of length field of next DNS msg */ short msgbufs; /* number of msgbufs in use */ u_char dnslen_buf[2]; /* full dnslen field might not arrive in first segment */ u_char dnslen_bytes_seen_mask; /* bitmask, when == 3 we have full dnslen */ int8_t fin; /* have we seen a FIN? */ tcp_msgbuf_t* msgbuf[MAX_TCP_MSGS]; tcp_segbuf_t* segbuf[MAX_TCP_SEGS]; } tcpstate_t; /* List of tcpstates ordered by time of last use, so we can quickly identify * and discard stale entries. */ struct { tcpstate_t* oldest; tcpstate_t* newest; } tcpList; static void tcpstate_reset(tcpstate_t* tcpstate, uint32_t seq) { int i; tcpstate->seq_start = seq; tcpstate->fin = 0; if (tcpstate->msgbufs > 0) { tcpstate->msgbufs = 0; for (i = 0; i < MAX_TCP_MSGS; i++) { if (tcpstate->msgbuf[i]) { xfree(tcpstate->msgbuf[i]); tcpstate->msgbuf[i] = NULL; } } } for (i = 0; i < MAX_TCP_SEGS; i++) { if (tcpstate->segbuf[i]) { xfree(tcpstate->segbuf[i]); tcpstate->segbuf[i] = NULL; } } } static void tcpstate_free(void* p) { tcpstate_reset((tcpstate_t*)p, 0); xfree(p); } inline static void tcpkey_set(tcpHashkey_t* key, inX_addr src, uint16_t sport, inX_addr dst, uint16_t dport) { memset(key, 0, sizeof(*key)); key->src_ip_addr.family = src.family; if (src.family == AF_INET6) { key->src_ip_addr.in6 = src.in6; } else { key->src_ip_addr.in4 = src.in4; } key->sport = sport; key->dst_ip_addr.family = dst.family; if (dst.family == AF_INET6) { key->dst_ip_addr.in6 = dst.in6; } else { key->dst_ip_addr.in4 = dst.in4; } key->dport = dport; } static unsigned int tcp_hashfunc(const void* key) { if (!(sizeof(tcpHashkey_t) % 4)) { return hashword(key, sizeof(tcpHashkey_t) / 4, 0); } return hashendian(key, sizeof(tcpHashkey_t), 0); } static int tcp_cmpfunc(const void* a, const void* b) { return memcmp(a, b, sizeof(tcpHashkey_t)); } /* TCP Reassembly. * * When we see a SYN, we allocate a new tcpstate for the connection, and * establish the initial sequence number of the first dns message (seq_start) * on the connection. We assume that no other segment can arrive before the * SYN (if one does, it is discarded, and if is not repeated the message it * belongs to can never be completely reassembled). * * Then, for each segment that arrives on the connection: * - If it's the first segment of a message (containing the 2-byte message * length), we allocate a msgbuf, and check for any held segments that might * belong to it. * - If the first byte of the segment belongs to any msgbuf, we fill * in the holes of that message. If the message has no more holes, we * handle the complete dns message. If the tail of the segment was longer * than the hole, we recurse on the tail. * - Otherwise, if the segment could be within the tcp window, we hold onto it * pending the creation of a matching msgbuf. * * This algorithm handles segments that arrive out of order, duplicated or * overlapping (including segments from different dns messages arriving out of * order), and dns messages that do not necessarily start on segment * boundaries. * */ static void pcap_handle_tcp_segment(u_char* segment, int len, uint32_t seq, tcpstate_t* tcpstate, transport_message* tm) { int i, m, s; uint16_t dnslen; int segoff, seglen; dfprintf(1, "pcap_handle_tcp_segment: seq=%u, len=%d", seq, len); if (len <= 0) /* there is no more payload */ return; if (seq - tcpstate->seq_start < 2) { /* this segment contains all or part of the 2-byte DNS length field */ uint32_t o = seq - tcpstate->seq_start; int l = (len > 1 && o == 0) ? 2 : 1; dfprintf(1, "pcap_handle_tcp_segment: copying %d bytes to dnslen_buf[%d]", l, o); memcpy(&tcpstate->dnslen_buf[o], segment, l); if (l == 2) tcpstate->dnslen_bytes_seen_mask = 3; else tcpstate->dnslen_bytes_seen_mask |= (1 << o); len -= l; segment += l; seq += l; } if (3 == tcpstate->dnslen_bytes_seen_mask) { /* We have the dnslen stored now */ dnslen = nptohs(tcpstate->dnslen_buf) & 0xffff; /* * Next we poison the mask to indicate we are in to the message body. * If one doesn't remember we're past the then, * one loops forever getting more msgbufs rather than filling * in the contents of THIS message. * * We need to later reset that mask when we process the message * (method: tcpstate->dnslen_bytes_seen_mask = 0). */ tcpstate->dnslen_bytes_seen_mask = 7; tcpstate->seq_start += sizeof(uint16_t) + dnslen; dfprintf(1, "pcap_handle_tcp_segment: first segment; dnslen = %d", dnslen); if (len >= dnslen) { /* this segment contains a complete message - avoid the reassembly * buffer and just handle the message immediately */ dns_protocol_handler(segment, dnslen, tm); tcpstate->dnslen_bytes_seen_mask = 0; /* go back for another message in this tcp connection */ /* handle the trailing part of the segment? */ if (len > dnslen) { dfprintf(1, "pcap_handle_tcp_segment: %s", "segment tail"); pcap_handle_tcp_segment(segment + dnslen, len - dnslen, seq + dnslen, tcpstate, tm); } return; } /* * At this point we KNOW we have an incomplete message and need to do reassembly. * i.e.: assert(len < dnslen); */ dfprintf(2, "pcap_handle_tcp_segment: %s", "buffering segment"); /* allocate a msgbuf for reassembly */ for (m = 0; tcpstate->msgbuf[m];) { if (++m >= MAX_TCP_MSGS) { dfprintf(1, "pcap_handle_tcp_segment: %s", "out of msgbufs"); return; } } tcpstate->msgbuf[m] = xcalloc(1, sizeof(tcp_msgbuf_t) + dnslen); if (NULL == tcpstate->msgbuf[m]) { dsyslogf(LOG_ERR, "out of memory for tcp_msgbuf (%d)", dnslen); return; } tcpstate->msgbufs++; tcpstate->msgbuf[m]->seq = seq; tcpstate->msgbuf[m]->dnslen = dnslen; tcpstate->msgbuf[m]->holes = 1; tcpstate->msgbuf[m]->hole[0].start = len; tcpstate->msgbuf[m]->hole[0].len = dnslen - len; dfprintf(1, "pcap_handle_tcp_segment: new msgbuf %d: seq = %u, dnslen = %d, hole start = %d, hole len = %d", m, tcpstate->msgbuf[m]->seq, tcpstate->msgbuf[m]->dnslen, tcpstate->msgbuf[m]->hole[0].start, tcpstate->msgbuf[m]->hole[0].len); /* copy segment to appropriate location in reassembly buffer */ memcpy(tcpstate->msgbuf[m]->buf, segment, len); /* Now that we know the length of this message, we must check any held * segments to see if they belong to it. */ for (s = 0; s < MAX_TCP_SEGS; s++) { if (!tcpstate->segbuf[s]) continue; if ((int64_t)tcpstate->segbuf[s]->seq - seq > 0 && (int64_t)tcpstate->segbuf[s]->seq - seq < dnslen) { tcp_segbuf_t* segbuf = tcpstate->segbuf[s]; tcpstate->segbuf[s] = NULL; dfprintf(1, "pcap_handle_tcp_segment: %s", "message reassembled"); pcap_handle_tcp_segment(segbuf->buf, segbuf->len, segbuf->seq, tcpstate, tm); /* * Note that our recursion will also cover any tail messages (I hope). * Thus we do not need to do so here and can return. */ xfree(segbuf); } } return; } /* * Welcome to reassembly-land. */ /* find the message to which the first byte of this segment belongs */ for (m = 0; m < MAX_TCP_MSGS; m++) { if (!tcpstate->msgbuf[m]) continue; segoff = seq - tcpstate->msgbuf[m]->seq; if (segoff >= 0 && segoff < tcpstate->msgbuf[m]->dnslen) { /* segment starts in this msgbuf */ dfprintf(1, "pcap_handle_tcp_segment: seg matches msg %d: seq = %u, dnslen = %d", m, tcpstate->msgbuf[m]->seq, tcpstate->msgbuf[m]->dnslen); if (segoff + len > tcpstate->msgbuf[m]->dnslen) { /* segment would overflow msgbuf */ seglen = tcpstate->msgbuf[m]->dnslen - segoff; dfprintf(1, "pcap_handle_tcp_segment: using partial segment %d", seglen); } else { seglen = len; } break; } } if (m >= MAX_TCP_MSGS) { /* seg does not match any msgbuf; just hold on to it. */ dfprintf(1, "pcap_handle_tcp_segment: %s", "seg does not match any msgbuf"); if (seq - tcpstate->seq_start > MAX_TCP_WINDOW_SIZE) { dfprintf(1, "pcap_handle_tcp_segment: %s", "seg is outside window; discarding"); return; } for (s = 0; s < MAX_TCP_SEGS; s++) { if (tcpstate->segbuf[s]) continue; tcpstate->segbuf[s] = xcalloc(1, sizeof(tcp_segbuf_t) + len); tcpstate->segbuf[s]->seq = seq; tcpstate->segbuf[s]->len = len; memcpy(tcpstate->segbuf[s]->buf, segment, len); dfprintf(1, "pcap_handle_tcp_segment: new segbuf %d: seq = %u, len = %d", s, tcpstate->segbuf[s]->seq, tcpstate->segbuf[s]->len); return; } dfprintf(1, "pcap_handle_tcp_segment: %s", "out of segbufs"); return; } /* Reassembly algorithm adapted from RFC 815. */ for (i = 0; i < MAX_TCP_HOLES; i++) { tcphole_t* newhole; uint16_t hole_start, hole_len; if (tcpstate->msgbuf[m]->hole[i].len == 0) continue; /* hole descriptor is not in use */ hole_start = tcpstate->msgbuf[m]->hole[i].start; hole_len = tcpstate->msgbuf[m]->hole[i].len; if (segoff >= hole_start + hole_len) continue; /* segment is totally after hole */ if (segoff + seglen <= hole_start) continue; /* segment is totally before hole */ /* The segment overlaps this hole. Delete the hole. */ dfprintf(1, "pcap_handle_tcp_segment: overlaping hole %d: %d %d", i, hole_start, hole_len); tcpstate->msgbuf[m]->hole[i].len = 0; tcpstate->msgbuf[m]->holes--; if (segoff + seglen < hole_start + hole_len) { /* create a new hole after the segment (common case) */ newhole = &tcpstate->msgbuf[m]->hole[i]; /* hole[i] is guaranteed free */ newhole->start = segoff + seglen; newhole->len = (hole_start + hole_len) - newhole->start; tcpstate->msgbuf[m]->holes++; dfprintf(1, "pcap_handle_tcp_segment: new post-hole %d: %d %d", i, newhole->start, newhole->len); } if (segoff > hole_start) { /* create a new hole before the segment */ int j; for (j = 0; j < MAX_TCP_HOLES; j++) { if (tcpstate->msgbuf[m]->hole[j].len == 0) { newhole = &tcpstate->msgbuf[m]->hole[j]; break; } } if (j >= MAX_TCP_HOLES) { dfprintf(1, "pcap_handle_tcp_segment: %s", "out of hole descriptors"); return; } tcpstate->msgbuf[m]->holes++; newhole->start = hole_start; newhole->len = segoff - hole_start; dfprintf(1, "pcap_handle_tcp_segment: new pre-hole %d: %d %d", j, newhole->start, newhole->len); } if (segoff >= hole_start && (hole_len == 0 || segoff + seglen < hole_start + hole_len)) { /* The segment does not extend past hole boundaries; there is * no need to look for other matching holes. */ break; } } /* copy payload to appropriate location in reassembly buffer */ memcpy(&tcpstate->msgbuf[m]->buf[segoff], segment, seglen); dfprintf(1, "pcap_handle_tcp_segment: holes remaining: %d", tcpstate->msgbuf[m]->holes); if (tcpstate->msgbuf[m]->holes == 0) { /* We now have a completely reassembled dns message */ dfprintf(2, "pcap_handle_tcp_segment: %s", "reassembly to dns_protocol_handler"); dns_protocol_handler(tcpstate->msgbuf[m]->buf, tcpstate->msgbuf[m]->dnslen, tm); tcpstate->dnslen_bytes_seen_mask = 0; /* go back for another message in this tcp connection */ xfree(tcpstate->msgbuf[m]); tcpstate->msgbuf[m] = NULL; tcpstate->msgbufs--; } if (seglen < len) { dfprintf(1, "pcap_handle_tcp_segment: %s", "segment tail after reassembly"); pcap_handle_tcp_segment(segment + seglen, len - seglen, seq + seglen, tcpstate, tm); } else { dfprintf(1, "pcap_handle_tcp_segment: %s", "nothing more after reassembly"); }; } static void tcpList_add_newest(tcpstate_t* tcpstate) { tcpstate->older = tcpList.newest; tcpstate->newer = NULL; *(tcpList.newest ? &tcpList.newest->newer : &tcpList.oldest) = tcpstate; tcpList.newest = tcpstate; } static void tcpList_remove(tcpstate_t* tcpstate) { *(tcpstate->older ? &tcpstate->older->newer : &tcpList.oldest) = tcpstate->newer; *(tcpstate->newer ? &tcpstate->newer->older : &tcpList.newest) = tcpstate->older; } static void tcpList_remove_older_than(long t) { int n = 0; tcpstate_t* tcpstate; while (tcpList.oldest && tcpList.oldest->last_use < t) { tcpstate = tcpList.oldest; tcpList_remove(tcpstate); hash_remove(&tcpstate->key, tcpHash); n++; } dfprintf(1, "discarded %d old tcpstates", n); } /* * This function always returns 1 because we do our own assembly and * we don't want pcap_layers to do any further processing of this * packet. */ static int pcap_tcp_handler(const struct tcphdr* tcp, int len, void* udata) { transport_message* tm = udata; int offset = tcp->th_off << 2; uint32_t seq; tcpstate_t* tcpstate = NULL; tcpHashkey_t key; tm->src_port = nptohs(&tcp->th_sport); tm->dst_port = nptohs(&tcp->th_dport); tm->proto = IPPROTO_TCP; tcpkey_set(&key, tm->src_ip_addr, tm->src_port, tm->dst_ip_addr, tm->dst_port); if (debug_flag > 1) { char src[128], dst[128]; inXaddr_ntop(&key.src_ip_addr, src, sizeof(src)); inXaddr_ntop(&key.dst_ip_addr, dst, sizeof(dst)); dfprintf(1, "handle_tcp: %s:%d %s:%d", src, key.sport, dst, key.dport); } if (port53 != key.dport && port53 != key.sport) return 1; if (NULL == tcpHash) { dfprintf(2, "pcap_tcp_handler: %s", "hash_create"); tcpHash = hash_create(MAX_TCP_STATE, tcp_hashfunc, tcp_cmpfunc, 0, NULL, tcpstate_free); if (NULL == tcpHash) return 1; } seq = nptohl(&tcp->th_seq); len -= offset; /* len = length of TCP payload */ dfprintf(1, "handle_tcp: seq = %u, len = %d", seq, len); tcpstate = hash_find(&key, tcpHash); if (tcpstate) dfprintf(1, "handle_tcp: tcpstate->seq_start = %u, ->msgs = %d", tcpstate->seq_start, tcpstate->msgbufs); if (!tcpstate && !(TCPFLAGSYN(tcp))) { /* There's no existing state, and this is not the start of a stream. * We have no way to synchronize with the stream, so we give up. * (This commonly happens for the final ACK in response to a FIN.) */ dfprintf(1, "handle_tcp: %s", "no state"); return 1; } if (tcpstate) tcpList_remove(tcpstate); /* remove from its current position */ if (TCPFLAGRST(tcp)) { dfprintf(1, "handle_tcp: RST at %u", seq); /* remove the state for this direction */ if (tcpstate) hash_remove(&key, tcpHash); /* this also frees tcpstate */ /* remove the state for the opposite direction */ tcpkey_set(&key, tm->dst_ip_addr, tm->dst_port, tm->src_ip_addr, tm->src_port); tcpstate = hash_find(&key, tcpHash); if (tcpstate) { tcpList_remove(tcpstate); hash_remove(&key, tcpHash); /* this also frees tcpstate */ } return 1; } if (TCPFLAGSYN(tcp)) { dfprintf(1, "handle_tcp: SYN at %u", seq); seq++; /* skip the syn */ if (tcpstate) { dfprintf(2, "handle_tcp: %s", "...resetting existing tcpstate"); tcpstate_reset(tcpstate, seq); } else { dfprintf(2, "handle_tcp: %s", "...creating new tcpstate"); tcpstate = xcalloc(1, sizeof(*tcpstate)); if (!tcpstate) return 1; tcpstate_reset(tcpstate, seq); tcpstate->key = key; if (0 != hash_add(&tcpstate->key, tcpstate, tcpHash)) { tcpstate_free(tcpstate); return 1; } } } pcap_handle_tcp_segment((uint8_t*)tcp + offset, len, seq, tcpstate, tm); if (TCPFLAGFIN(tcp) && !tcpstate->fin) { /* End of tcp stream */ dfprintf(1, "handle_tcp: FIN at %u", seq); tcpstate->fin = 1; } if (tcpstate->fin && tcpstate->msgbufs == 0) { /* FIN was seen, and there are no incomplete msgbufs left */ dfprintf(1, "handle_tcp: %s", "connection done"); hash_remove(&key, tcpHash); /* this also frees tcpstate */ } else { /* We're keeping this tcpstate. Store it in tcpList by age. */ tcpstate->last_use = tm->ts.tv_sec; tcpList_add_newest(tcpstate); } return 1; } static int pcap_ipv4_handler(const struct ip* ip4, int len, void* udata) { transport_message* tm = udata; #ifdef __FreeBSD__ /* FreeBSD uses packed struct ip */ struct in_addr a; memcpy(&a, &ip4->ip_src, sizeof(a)); inXaddr_assign_v4(&tm->src_ip_addr, &a); memcpy(&a, &ip4->ip_dst, sizeof(a)); inXaddr_assign_v4(&tm->dst_ip_addr, &a); #else inXaddr_assign_v4(&tm->src_ip_addr, &ip4->ip_src); inXaddr_assign_v4(&tm->dst_ip_addr, &ip4->ip_dst); #endif tm->ip_version = 4; return 0; } static int pcap_ipv6_handler(const struct ip6_hdr* ip6, int len, void* udata) { transport_message* tm = udata; #ifdef __FreeBSD__ /* FreeBSD uses packed struct ip6_hdr */ struct in6_addr a; memcpy(&a, &ip6->ip6_src, sizeof(a)); inXaddr_assign_v6(&tm->src_ip_addr, &a); memcpy(&a, &ip6->ip6_dst, sizeof(a)); inXaddr_assign_v6(&tm->dst_ip_addr, &a); #else inXaddr_assign_v6(&tm->src_ip_addr, &ip6->ip6_src); inXaddr_assign_v6(&tm->dst_ip_addr, &ip6->ip6_dst); #endif tm->ip_version = 6; return 0; } static int pcap_match_vlan(unsigned short vlan, void* udata) { int i; if (vlan_tag_needs_byte_conversion) vlan = ntohs(vlan); dfprintf(1, "vlan is %d", vlan); for (i = 0; i < n_vlan_ids; i++) if (vlan_ids[i] == vlan) return 0; return 1; } /* * Forward declares for pcap_layers since we need to call datalink * handlers directly. */ #if USE_PPP void handle_ppp(const u_char* pkt, int len, void* userdata); #endif void handle_null(const u_char* pkt, int len, void* userdata); #ifdef DLT_LOOP void handle_loop(const u_char* pkt, int len, void* userdata); #endif #ifdef DLT_RAW void handle_raw(const u_char* pkt, int len, void* userdata); #endif void handle_ether(const u_char* pkt, int len, void* userdata); #ifdef DLT_LINUX_SLL void handle_linux_sll(const u_char* pkt, int len, void* userdata); #endif static void pcap_handle_packet(u_char* udata, const struct pcap_pkthdr* hdr, const u_char* pkt, const char* name, int dlt) { void (*handle_datalink)(const u_char* pkt, int len, void* userdata); transport_message tm; #if 0 /* enable this to test code with unaligned headers */ char buf[PCAP_SNAPLEN + 1]; memcpy(buf + 1, pkt, hdr->caplen); pkt = buf + 1; #endif assign_timeval(last_ts, hdr->ts); if (hdr->caplen < ETHER_HDR_LEN) return; memset(&tm, 0, sizeof(tm)); assign_timeval(tm.ts, hdr->ts); 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); } handle_datalink(pkt, hdr->caplen, (u_char*)&tm); } /* ========================================================================= */ extern int sig_while_processing; void _callback(u_char* user, const struct pcap_pkthdr* pkthdr, const u_char* pkt, const char* name, int dlt) { struct _interface* i; if (!user) { dsyslog(LOG_ERR, "internal error"); exit(2); } i = (struct _interface*)user; i->pkts_captured++; pcap_handle_packet(user, pkthdr, pkt, name, dlt); } void Pcap_init(const char* device, int promisc, int monitor, int immediate, int threads, int buffer_size) { char errbuf[512]; struct stat sb; struct _interface* i; int err; extern int pt_timeout; if (interfaces == NULL) { interfaces = xcalloc(MAX_N_INTERFACES, sizeof(*interfaces)); if ((err = pcap_thread_set_promiscuous(&pcap_thread, promisc))) { dsyslogf(LOG_ERR, "unable to set promiscuous mode: %s", pcap_thread_strerr(err)); exit(1); } if ((err = pcap_thread_set_monitor(&pcap_thread, monitor))) { dsyslogf(LOG_ERR, "unable to set monitor mode: %s", pcap_thread_strerr(err)); exit(1); } if ((err = pcap_thread_set_immediate_mode(&pcap_thread, immediate))) { dsyslogf(LOG_ERR, "unable to set immediate mode: %s", pcap_thread_strerr(err)); exit(1); } if ((err = pcap_thread_set_use_threads(&pcap_thread, threads))) { dsyslogf(LOG_ERR, "unable to set use threads: %s", pcap_thread_strerr(err)); exit(1); } if ((err = pcap_thread_set_snaplen(&pcap_thread, PCAP_SNAPLEN))) { dsyslogf(LOG_ERR, "unable to set snap length: %s", pcap_thread_strerr(err)); exit(1); } if (bpf_program_str && (err = pcap_thread_set_filter(&pcap_thread, bpf_program_str, strlen(bpf_program_str)))) { dsyslogf(LOG_ERR, "unable to set pcap filter: %s", pcap_thread_strerr(err)); exit(1); } if ((err = pcap_thread_set_callback(&pcap_thread, _callback))) { dsyslogf(LOG_ERR, "unable to set pcap callback: %s", pcap_thread_strerr(err)); exit(1); } if (buffer_size > 0 && (err = pcap_thread_set_buffer_size(&pcap_thread, buffer_size))) { dsyslogf(LOG_ERR, "unable to set pcap buffer size: %s", pcap_thread_strerr(err)); exit(1); } if (pt_timeout > 0 && (err = pcap_thread_set_timeout(&pcap_thread, pt_timeout))) { dsyslogf(LOG_ERR, "unable to set pcap-thread timeout: %s", pcap_thread_strerr(err)); exit(1); } } assert(interfaces); assert(n_interfaces < MAX_N_INTERFACES); i = &interfaces[n_interfaces]; i->device = strdup(device); last_ts.tv_sec = last_ts.tv_usec = 0; finish_ts.tv_sec = finish_ts.tv_usec = 0; if (!stat(device, &sb)) { if ((err = pcap_thread_open_offline(&pcap_thread, device, i))) { dsyslogf(LOG_ERR, "unable to open offline file %s: %s", device, pcap_thread_strerr(err)); if (err == PCAP_THREAD_EPCAP) { dsyslogf(LOG_ERR, "libpcap error [%d]: %s (%s)", pcap_thread_status(&pcap_thread), pcap_statustostr(pcap_thread_status(&pcap_thread)), pcap_thread_errbuf(&pcap_thread)); } else if (err == PCAP_THREAD_ERRNO) { dsyslogf(LOG_ERR, "system error [%d]: %s (%s)\n", errno, dsc_strerror(errno, errbuf, sizeof(errbuf)), pcap_thread_errbuf(&pcap_thread)); } exit(1); } n_pcap_offline++; } else { if ((err = pcap_thread_open(&pcap_thread, device, i))) { dsyslogf(LOG_ERR, "unable to open interface %s: %s", device, pcap_thread_strerr(err)); if (err == PCAP_THREAD_EPCAP) { dsyslogf(LOG_ERR, "libpcap error [%d]: %s (%s)", pcap_thread_status(&pcap_thread), pcap_statustostr(pcap_thread_status(&pcap_thread)), pcap_thread_errbuf(&pcap_thread)); } else if (err == PCAP_THREAD_ERRNO) { dsyslogf(LOG_ERR, "system error [%d]: %s (%s)\n", errno, dsc_strerror(errno, errbuf, sizeof(errbuf)), pcap_thread_errbuf(&pcap_thread)); } exit(1); } } if (0 == n_interfaces) { extern int drop_ip_fragments; /* * Initialize pcap_layers library and specifiy IP fragment reassembly * Datalink type is handled in callback */ pcap_layers_init(DLT_EN10MB, drop_ip_fragments ? 0 : 1); if (n_vlan_ids) callback_vlan = pcap_match_vlan; callback_ipv4 = pcap_ipv4_handler; callback_ipv6 = pcap_ipv6_handler; callback_udp = pcap_udp_handler; callback_tcp = pcap_tcp_handler; callback_l7 = dns_protocol_handler; } n_interfaces++; if (n_pcap_offline > 1 || (n_pcap_offline > 0 && n_interfaces > n_pcap_offline)) { dsyslog(LOG_ERR, "offline interface must be only interface"); exit(1); } } void _stats(u_char* user, const struct pcap_stat* stats, const char* name, int dlt) { int i; struct _interface* I = 0; for (i = 0; i < n_interfaces; i++) { if (!strcmp(name, interfaces[i].device)) { I = &interfaces[i]; break; } } if (I) { I->ps0 = I->ps1; I->ps1 = *stats; } } int Pcap_run(void) { int i, err; extern uint64_t statistics_interval; for (i = 0; i < n_interfaces; i++) interfaces[i].pkts_captured = 0; if (n_pcap_offline > 0) { if (finish_ts.tv_sec > 0) { start_ts.tv_sec = finish_ts.tv_sec; finish_ts.tv_sec += statistics_interval; } else { /* * First run, need to walk each pcap savefile and find * the first start time */ if ((err = pcap_thread_next_reset(&pcap_thread))) { dsyslogf(LOG_ERR, "unable to reset pcap thread next: %s", pcap_thread_strerr(err)); return 0; } for (i = 0; i < n_pcap_offline; i++) { if ((err = pcap_thread_next(&pcap_thread))) { if (err != PCAP_THREAD_EPCAP) { dsyslogf(LOG_ERR, "unable to do pcap thread next: %s", pcap_thread_strerr(err)); return 0; } continue; } if (!start_ts.tv_sec || last_ts.tv_sec < start_ts.tv_sec || (last_ts.tv_sec == start_ts.tv_sec && last_ts.tv_usec < start_ts.tv_usec)) { start_ts = last_ts; } } if (!start_ts.tv_sec) { return 0; } finish_ts.tv_sec = ((start_ts.tv_sec / statistics_interval) + 1) * statistics_interval; finish_ts.tv_usec = 0; } i = 0; do { err = pcap_thread_next(&pcap_thread); if (err == PCAP_THREAD_EPCAP) { /* * Potential EOF, count number of times */ i++; } else if (err) { dsyslogf(LOG_ERR, "unable to do pcap thread next: %s", pcap_thread_strerr(err)); return 0; } else { i = 0; } if (i == n_pcap_offline || sig_while_processing) { /* * All pcaps reports EOF or we got a signal, nothing more to do */ finish_ts = last_ts; return 0; } } while (last_ts.tv_sec < finish_ts.tv_sec); } else { gettimeofday(&start_ts, NULL); gettimeofday(&last_ts, NULL); finish_ts.tv_sec = ((start_ts.tv_sec / statistics_interval) + 1) * statistics_interval; finish_ts.tv_usec = 0; if ((err = pcap_thread_set_timedrun_to(&pcap_thread, finish_ts))) { dsyslogf(LOG_ERR, "unable to set pcap thread timed run: %s", pcap_thread_strerr(err)); return 0; } if ((err = pcap_thread_run(&pcap_thread))) { if (err == PCAP_THREAD_ERRNO && errno == EINTR && sig_while_processing) { dsyslog(LOG_INFO, "pcap thread run interruped by signal"); } else { dsyslogf(LOG_ERR, "unable to pcap thread run: %s", pcap_thread_strerr(err)); if (err == PCAP_THREAD_EPCAP) { dsyslogf(LOG_ERR, "libpcap error [%d]: %s (%s)", pcap_thread_status(&pcap_thread), pcap_statustostr(pcap_thread_status(&pcap_thread)), pcap_thread_errbuf(&pcap_thread)); } else if (err == PCAP_THREAD_ERRNO) { char errbuf[512]; dsyslogf(LOG_ERR, "system error [%d]: %s (%s)\n", errno, dsc_strerror(errno, errbuf, sizeof(errbuf)), pcap_thread_errbuf(&pcap_thread)); } return 0; } } if (sig_while_processing) finish_ts = last_ts; if ((err = pcap_thread_stats(&pcap_thread, _stats, 0))) { dsyslogf(LOG_ERR, "unable to get pcap thread stats: %s", pcap_thread_strerr(err)); if (err == PCAP_THREAD_EPCAP) { dsyslogf(LOG_ERR, "libpcap error [%d]: %s (%s)", pcap_thread_status(&pcap_thread), pcap_statustostr(pcap_thread_status(&pcap_thread)), pcap_thread_errbuf(&pcap_thread)); } return 0; } } tcpList_remove_older_than(last_ts.tv_sec - MAX_TCP_IDLE); pcap_layers_clear_fragments(time(NULL) - MAX_FRAG_IDLE); return 1; } void Pcap_stop(void) { pcap_thread_stop(&pcap_thread); } void Pcap_close(void) { int i; pcap_thread_close(&pcap_thread); for (i = 0; i < n_interfaces; i++) if (interfaces[i].device) free(interfaces[i].device); xfree(interfaces); interfaces = NULL; } int Pcap_start_time(void) { return (int)start_ts.tv_sec; } int Pcap_finish_time(void) { return (int)finish_ts.tv_sec; } void pcap_set_match_vlan(int vlan) { assert(n_vlan_ids < MAX_VLAN_IDS); vlan_ids[n_vlan_ids++] = vlan; } /* ========== PCAP_STAT INDEXER ========== */ int pcap_ifname_iterator(const char**); int pcap_stat_iterator(const char**); static indexer indexers[] = { { "ifname", 0, 0, pcap_ifname_iterator }, { "pcap_stat", 0, 0, pcap_stat_iterator }, { 0 }, }; int pcap_ifname_iterator(const char** label) { static int next_iter = 0; if (NULL == label) { next_iter = 0; return n_interfaces; } if (next_iter >= 0 && next_iter < n_interfaces) { *label = interfaces[next_iter].device; return next_iter++; } return -1; } int pcap_stat_iterator(const char** label) { static int next_iter = 0; if (NULL == label) { next_iter = 0; return 3; } if (0 == next_iter) *label = "pkts_captured"; else if (1 == next_iter) *label = "filter_received"; else if (2 == next_iter) *label = "kernel_dropped"; else return -1; return next_iter++; } void pcap_report(FILE* fp, md_array_printer* printer) { int i; md_array* theArray = acalloc(1, sizeof(*theArray)); if (!theArray) { dsyslog(LOG_ERR, "unable to write report, out of memory"); return; } theArray->name = "pcap_stats"; theArray->d1.indexer = &indexers[0]; theArray->d1.type = "ifname"; theArray->d1.alloc_sz = n_interfaces; theArray->d2.indexer = &indexers[1]; theArray->d2.type = "pcap_stat"; theArray->d2.alloc_sz = 3; theArray->array = acalloc(n_interfaces, sizeof(*theArray->array)); if (!theArray->array) { dsyslog(LOG_ERR, "unable to write report, out of memory"); return; } for (i = 0; i < n_interfaces; i++) { struct _interface* I = &interfaces[i]; theArray->array[i].alloc_sz = 3; theArray->array[i].array = acalloc(3, sizeof(int)); theArray->array[i].array[0] = I->pkts_captured; theArray->array[i].array[1] = I->ps1.ps_recv - I->ps0.ps_recv; theArray->array[i].array[2] = I->ps1.ps_drop - I->ps0.ps_drop; } md_array_print(theArray, printer, fp); }