diff options
Diffstat (limited to '')
-rw-r--r-- | src/network.c | 1834 |
1 files changed, 1834 insertions, 0 deletions
diff --git a/src/network.c b/src/network.c new file mode 100644 index 0000000..d0a4242 --- /dev/null +++ b/src/network.c @@ -0,0 +1,1834 @@ +/* + * Copyright (c) 2016-2021, OARC, 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 "network.h" +#include "iaddr.h" +#include "log.h" +#include "pcaps.h" +#include "dumper.h" +#include "endpoint.h" +#include "tcpstate.h" +#include "tcpreasm.h" +#include "endian_compat.h" + +#include <ldns/ldns.h> + +struct ip6_hdr* network_ipv6 = 0; +struct ip* network_ip = 0; +struct udphdr* network_udp = 0; + +extern tcpstate_ptr _curr_tcpstate; /* from tcpstate.c */ + +static inline uint16_t _need16(const void* ptr) +{ + uint16_t v; + memcpy(&v, ptr, sizeof(v)); + return be16toh(v); +} + +static inline uint32_t _need32(const void* ptr) +{ + uint32_t v; + memcpy(&v, ptr, sizeof(v)); + return be32toh(v); +} + +static int skip_vlan(unsigned vlan) +{ + if (!EMPTY(vlans_excl)) { + vlan_ptr vl; + + for (vl = HEAD(vlans_excl); vl != NULL; vl = NEXT(vl, link)) { + if (vl->vlan == vlan || vl->vlan == MAX_VLAN) + break; + } + + /* + * If there is no VLAN matching the packet, skip it + */ + if (vl == NULL) + return 1; + } else if (!EMPTY(vlans_incl)) { + vlan_ptr vl; + + for (vl = HEAD(vlans_incl); vl != NULL; vl = NEXT(vl, link)) { + if (vl->vlan == vlan || vl->vlan == MAX_VLAN) + break; + } + + /* + * If there is no VLAN matching the packet, and the packet is tagged, skip it + */ + if (vl == NULL && vlan != MAX_VLAN) + return 1; + } + + return 0; +} + +void layer_pkt(u_char* user, const pcap_thread_packet_t* packet, const u_char* payload, size_t length) +{ + mypcap_ptr mypcap = (mypcap_ptr)user; + size_t len; + unsigned vlan; + const pcap_thread_packet_t *prevpkt, *firstpkt = packet; + char descr[200]; + + if (!mypcap) + return; + if (!packet) + return; + + while (firstpkt->have_prevpkt) { + if (firstpkt->have_pkthdr) + break; + firstpkt = firstpkt->prevpkt; + } + if (!firstpkt->have_pkthdr) + return; + + if (only_offline_pcaps && start_time != 0 && firstpkt->pkthdr.ts.tv_sec < start_time) + return; + + len = firstpkt->pkthdr.caplen; + + last_ts = firstpkt->pkthdr.ts; + if (stop_time != 0 && firstpkt->pkthdr.ts.tv_sec >= stop_time) { + breakloop_pcaps(); + main_exit = TRUE; + } + + if (main_exit) + return; + + /* If ever SNAPLEN wasn't big enough, we have no recourse. */ + if (firstpkt->pkthdr.len != firstpkt->pkthdr.caplen) + return; + + vlan = MAX_VLAN; + for (prevpkt = packet; prevpkt; prevpkt = prevpkt->prevpkt) { + if (prevpkt->have_ieee802hdr) { + /* TODO: Only match first found VLAN or all? */ + vlan = prevpkt->ieee802hdr.vid; + len -= 4; + break; + } + if (!prevpkt->have_prevpkt) + break; + } + if (skip_vlan(vlan)) { + return; + } + + descr[0] = 0; + if (preso) { + char when[100]; + struct tm tm; + time_t t; + + /* + * Reduce `len` to report same captured length as `dl_pkt` + */ + for (prevpkt = packet; len && prevpkt; prevpkt = prevpkt->prevpkt) { + if (prevpkt->have_nullhdr) { + if (len > sizeof(prevpkt->nullhdr)) + len -= sizeof(prevpkt->nullhdr); + else + len = 0; + } + if (prevpkt->have_loophdr) { + if (len > sizeof(prevpkt->loophdr)) + len -= sizeof(prevpkt->loophdr); + else + len = 0; + } + if (prevpkt->have_ethhdr) { + if (len > sizeof(prevpkt->ethhdr)) + len -= sizeof(prevpkt->ethhdr); + else + len = 0; + } + if (prevpkt->have_linux_sll) { + if (len > sizeof(prevpkt->linux_sll)) + len -= sizeof(prevpkt->linux_sll); + else + len = 0; + } + + if (!prevpkt->have_prevpkt) + break; + } + + t = (time_t)firstpkt->pkthdr.ts.tv_sec; + gmtime_r(&t, &tm); + strftime(when, sizeof(when), "%Y-%m-%d %T", &tm); + + if (vlan != MAX_VLAN) { + snprintf(descr, sizeof(descr), "[%lu] %s.%06lu [#%ld %s (vlan %u) %u] \\\n", + (u_long)len, + when, + (u_long)firstpkt->pkthdr.ts.tv_usec, + (long)msgcount, + mypcap->name ? mypcap->name : "\"some interface\"", + vlan, + vlan); + } else { + snprintf(descr, sizeof(descr), "[%lu] %s.%06lu [#%ld %s %u] \\\n", + (u_long)len, + when, + (u_long)firstpkt->pkthdr.ts.tv_usec, + (long)msgcount, + mypcap->name ? mypcap->name : "\"some interface\"", + vlan); + } + } + + if (next_interval != 0 && firstpkt->pkthdr.ts.tv_sec >= next_interval) { + if (preso) + goto breakloop; + if (dumper_opened == dump_state) + dumper_close(firstpkt->pkthdr.ts); + if (dump_type == to_stdout) + goto breakloop; + } + if (dumper_closed == dump_state && dumper_open(firstpkt->pkthdr.ts)) + goto breakloop; + + network_pkt2(descr, firstpkt->pkthdr.ts, packet, payload, length); + + if (limit_packets != 0U && msgcount == limit_packets) { + if (preso) + goto breakloop; + if (dumper_opened == dump_state && dumper_close(firstpkt->pkthdr.ts)) + goto breakloop; + msgcount = 0; + } + + if (limit_pcapfilesize != 0U && capturedbytes >= limit_pcapfilesize) { + if (preso) { + goto breakloop; + } + if (dumper_opened == dump_state && dumper_close(firstpkt->pkthdr.ts)) { + goto breakloop; + } + capturedbytes = 0; + } + + return; +breakloop: + breakloop_pcaps(); + main_exit = TRUE; +} + +void dl_pkt(u_char* user, const struct pcap_pkthdr* hdr, const u_char* pkt, const char* name, const int dlt) +{ + mypcap_ptr mypcap = (mypcap_ptr)user; + size_t len = hdr->caplen; + unsigned etype, vlan, pf; + char descr[512]; + + if (only_offline_pcaps && start_time != 0 && hdr->ts.tv_sec < start_time) + return; + + last_ts = hdr->ts; + if (stop_time != 0 && hdr->ts.tv_sec >= stop_time) { + breakloop_pcaps(); + main_exit = TRUE; + } + + if (main_exit) + return; + + /* If ever SNAPLEN wasn't big enough, we have no recourse. */ + if (hdr->len != hdr->caplen) + return; + + /* Data link. */ + vlan = MAX_VLAN; /* MAX_VLAN (0xFFF) is reserved and shouldn't appear on the wire */ + switch (dlt) { + case DLT_NULL: { + uint32_t x; + + if (len < 4) + return; + x = _need32(pkt); + if (x == PF_INET) + etype = ETHERTYPE_IP; + else if (x == PF_INET6) + etype = ETHERTYPE_IPV6; + else + return; + pkt += 4; + len -= 4; + break; + } + case DLT_LOOP: { + uint32_t x; + + if (len < 4) + return; + x = _need32(pkt); + if (x == PF_INET) + etype = ETHERTYPE_IP; + else if (x == PF_INET6) + etype = ETHERTYPE_IPV6; + else + return; + pkt += 4; + len -= 4; + break; + } + case DLT_RAW: { + if (len < 1) + return; + switch (*(const uint8_t*)pkt >> 4) { + case 4: + etype = ETHERTYPE_IP; + break; + case 6: + etype = ETHERTYPE_IPV6; + break; + default: + return; + } + break; + } + case DLT_EN10MB: { + const struct ether_header* ether; + + if (len < ETHER_HDR_LEN) + return; + ether = (const struct ether_header*)pkt; + etype = ntohs(ether->ether_type); + pkt += ETHER_HDR_LEN; + len -= ETHER_HDR_LEN; + if (etype == ETHERTYPE_VLAN) { + if (len < 4) + return; + vlan = _need16(pkt) & 0xFFF; + pkt += 2; + len -= 2; + etype = _need16(pkt); + pkt += 2; + len -= 2; + } + break; + } +#ifdef DLT_LINUX_SLL + case DLT_LINUX_SLL: { + if (len < 16) + return; + etype = _need16(&pkt[14]); + pkt += 16; + len -= 16; + break; + } +#endif + default: + return; + } + + if (!EMPTY(vlans_excl)) { + vlan_ptr vl; + + for (vl = HEAD(vlans_excl); + vl != NULL; + vl = NEXT(vl, link)) + if (vl->vlan == vlan || vl->vlan == MAX_VLAN) + break; + /* + * If there is no VLAN matching the packet, skip it + */ + if (vl == NULL) + return; + } else if (!EMPTY(vlans_incl)) { + vlan_ptr vl; + + for (vl = HEAD(vlans_incl); + vl != NULL; + vl = NEXT(vl, link)) + if (vl->vlan == vlan || vl->vlan == MAX_VLAN) + break; + /* + * If there is no VLAN matching the packet, and the packet is tagged, skip it + */ + if (vl == NULL && vlan != MAX_VLAN) + return; + } + + switch (etype) { + case ETHERTYPE_IP: + pf = PF_INET; + break; + case ETHERTYPE_IPV6: + pf = PF_INET6; + break; + default: + return; + } + + if (preso) { + char when[100], via[100]; + const char* viap; + struct tm tm; + time_t t; + + t = (time_t)hdr->ts.tv_sec; + gmtime_r(&t, &tm); + strftime(when, sizeof when, "%Y-%m-%d %T", &tm); + if (vlan != MAX_VLAN) { + snprintf(via, sizeof(via), "%s (vlan %u)", mypcap->name ? mypcap->name : "\"some interface\"", vlan); + viap = via; + } else if (mypcap->name) { + viap = mypcap->name; + } else { + viap = "\"some interface\""; + } + snprintf(descr, sizeof(descr), "[%lu] %s.%06lu [#%ld %s %u] \\\n", + (u_long)len, when, (u_long)hdr->ts.tv_usec, (long)msgcount, viap, vlan); + } else { + descr[0] = '\0'; + } + + if (next_interval != 0 && hdr->ts.tv_sec >= next_interval) { + if (preso) + goto breakloop; + if (dumper_opened == dump_state) + dumper_close(hdr->ts); + if (dump_type == to_stdout) + goto breakloop; + } + if (dumper_closed == dump_state && dumper_open(hdr->ts)) + goto breakloop; + + network_pkt(descr, hdr->ts, pf, pkt, len); + + if (limit_packets != 0U && msgcount == limit_packets) { + if (preso) + goto breakloop; + if (dumper_opened == dump_state && dumper_close(hdr->ts)) + goto breakloop; + msgcount = 0; + } + + if (limit_pcapfilesize != 0U && capturedbytes >= limit_pcapfilesize) { + if (preso) { + goto breakloop; + } + if (dumper_opened == dump_state && dumper_close(hdr->ts)) { + goto breakloop; + } + capturedbytes = 0; + } + + return; +breakloop: + breakloop_pcaps(); + main_exit = TRUE; +} + +void network_pkt2(const char* descr, my_bpftimeval ts, const pcap_thread_packet_t* packet, const u_char* payload, size_t length) +{ + u_char pkt_copy[SNAPLEN], *pkt = pkt_copy; + const u_char* dnspkt = 0; + unsigned proto, sport, dport; + iaddr from, to, initiator, responder; + int response, m; + unsigned flags = DNSCAP_OUTPUT_ISLAYER; + tcpstate_ptr tcpstate = NULL; + size_t len, dnslen = 0; + HEADER dns; + ldns_pkt* lpkt = 0; + + /* Make a writable copy of the packet and use that copy from now on. */ + if (length > SNAPLEN) + return; + memcpy(pkt, payload, len = length); + + /* Network. */ + sport = dport = 0; + if (packet->have_iphdr) { + if (dumptrace >= 4) + fprintf(stderr, "processing IPv4 packet: len=%zu\n", length); + + memset(&from, 0, sizeof from); + from.af = AF_INET; + memcpy(&from.u.a4, &(packet->iphdr.ip_src), sizeof(struct in_addr)); + memset(&to, 0, sizeof to); + to.af = AF_INET; + memcpy(&to.u.a4, &(packet->iphdr.ip_dst), sizeof(struct in_addr)); + } else if (packet->have_ip6hdr) { + if (dumptrace >= 4) + fprintf(stderr, "processing IPv6 packet: len=%zu\n", length); + + memset(&from, 0, sizeof from); + from.af = AF_INET6; + memcpy(&from.u.a6, &(packet->ip6hdr.ip6_src), sizeof(struct in6_addr)); + memset(&to, 0, sizeof to); + to.af = AF_INET6; + memcpy(&to.u.a6, &(packet->ip6hdr.ip6_dst), sizeof(struct in6_addr)); + } else { + if (dumptrace >= 4) + fprintf(stderr, "processing unknown packet: len=%zu\n", length); + from.af = AF_UNSPEC; + to.af = AF_UNSPEC; + } + + /* Transport. */ + if (packet->have_icmphdr) { + output(descr, from, to, IPPROTO_ICMP, flags, sport, dport, ts, pkt_copy, length, pkt, len); + return; + } else if (packet->have_icmpv6hdr) { + output(descr, from, to, IPPROTO_ICMPV6, flags, sport, dport, ts, pkt_copy, length, pkt, len); + return; + } else if (packet->have_udphdr) { + proto = IPPROTO_UDP; + sport = packet->udphdr.uh_sport; + dport = packet->udphdr.uh_dport; + dnspkt = payload; + dnslen = length; + flags |= DNSCAP_OUTPUT_ISDNS; + } else if (packet->have_tcphdr) { + uint32_t seq = packet->tcphdr.th_seq; + + proto = IPPROTO_TCP; + sport = packet->tcphdr.th_sport; + dport = packet->tcphdr.th_dport; + + /* + * TCP processing. + * + * We need to capture enough to allow a later analysis to + * reassemble the TCP stream, but we don't want to keep all + * the state required to do reassembly here. + * When we get a SYN, we don't yet know if the DNS message + * will pass the filters, so we always output it, and also + * generate a tcpstate to keep track of the stream. (An + * alternative would be to store the SYN packet on the + * tcpstate and not output it until a later packet passes the + * filter, but that would require more memory and would + * reorder packets in the pcap output.) + * When we get the _first_ DNS header on the stream, then we + * can apply the DNS header filters; if the packet passes, we + * output the packet and keep the tcpstate; if it fails, we + * discard the packet and the tcpstate. + * When we get any other packet with DNS payload, we output it + * only if there is a corresponding tcpstate indicating that + * the header passed the filters. + * Packets with no TCP payload (e.g., packets containing only + * an ACK) are discarded, since they carry no DNS information + * and are not needed for stream reassembly. + * FIN packets are always output to match the SYN, even if the + * DNS header failed the filter, to be friendly to later + * analysis programs that allocate state for each SYN. + * -- kkeys@caida.org + */ + + tcpstate = tcpstate_find(from, to, sport, dport, ts.tv_sec); + if (dumptrace >= 3) { + fprintf(stderr, "%s: tcp pkt: %lu.%06lu [%4lu] %15s -> ", + ProgramName, + (u_long)ts.tv_sec, + (u_long)ts.tv_usec, + (u_long)len, + ia_str(from)); + fprintf(stderr, "%15s; ", ia_str(to)); + + if (tcpstate) + fprintf(stderr, "want=%08x; ", tcpstate->start); + else + fprintf(stderr, "no state; "); + + fprintf(stderr, "seq=%08x; ", seq); + } + if (packet->tcphdr.th_flags & (TH_FIN | TH_RST)) { + if (dumptrace >= 3) + fprintf(stderr, "FIN|RST\n"); + + /* Always output FIN and RST segments. */ + _curr_tcpstate = tcpstate; + output(descr, from, to, proto, flags, sport, dport, ts, pkt_copy, length, NULL, 0); + _curr_tcpstate = 0; + + /* End of stream; deallocate the tcpstate. */ + if (tcpstate) { + UNLINK(tcpstates, tcpstate, link); + if (tcpstate->reasm) { + tcpreasm_free(tcpstate->reasm); + } + free(tcpstate); + tcpstate_count--; + } + return; + } + if (packet->tcphdr.th_flags & TH_SYN) { + if (dumptrace >= 3) + fprintf(stderr, "SYN\n"); + + if (tcpstate) { + if (tcpstate->start == seq + 1) { + /* repeated SYN */ + } else { + /* Assume existing state is stale and recycle it. */ + + /* + * Disabled because warning may scare user, and + * there's nothing else we can do anyway. + */ + + /* + if (ts.tv_sec - tcpstate->last_use < MAX_TCP_IDLE_TIME) + fprintf(stderr, "warning: recycling state for " + "duplicate tcp stream after only %ld " + "seconds idle\n", + (u_long)(ts.tv_sec - tcpstate->last_use)); + */ + } + } else { + /* create new tcpstate */ + tcpstate = tcpstate_new(from, to, sport, dport); + } + tcpstate->last_use = ts.tv_sec; + tcpstate->start = seq + 1; /* add 1 for the SYN */ + tcpstate->maxdiff = 1; + tcpstate->dnslen = 0; + tcpstate->lastdns = 0; + + /* Always output SYN segments. */ + _curr_tcpstate = tcpstate; + output(descr, from, to, proto, flags, sport, dport, ts, pkt_copy, length, NULL, 0); + _curr_tcpstate = 0; + + return; + } + if (options.parse_ongoing_tcp && !tcpstate && len) { + tcpstate = tcpstate_new(from, to, sport, dport); + tcpstate->last_use = ts.tv_sec; + tcpstate->start = seq; + tcpstate->maxdiff = 0; + tcpstate->dnslen = 0; + tcpstate->lastdns = seq; + } + if (tcpstate && options.reassemble_tcp) { + if (!tcpstate->reasm) { + if (!(tcpstate->reasm = calloc(1, sizeof(tcpreasm_t)))) { + logerr("out of memory, TCP reassembly failed"); + return; + } + tcpstate->reasm->seq_start = tcpstate->start; + tcpstate->reasm->seq_bfb = tcpstate->start; + } + if (options.allow_reset_tcpstate) { + if (tcpstate->reasm_faults > options.reassemble_tcp_faultreset) { + if (dumptrace >= 3) + fprintf(stderr, "fault reset "); + tcpstate_reset(tcpstate, "too many reassembly faults"); + tcpstate->reasm->seq_start = seq; + tcpstate->reasm->seq_bfb = seq; + tcpstate->reasm_faults = 0; + } + if (dumptrace >= 3) + fprintf(stderr, "reassemble\n"); + if (pcap_handle_tcp_segment(pkt, len, seq, tcpstate)) { + tcpstate->reasm_faults++; + } + } else { + if (dumptrace >= 3) + fprintf(stderr, "reassemble\n"); + (void)pcap_handle_tcp_segment(pkt, len, seq, tcpstate); + } + } else if (tcpstate) { + uint32_t seqdiff = seq - tcpstate->start; + + tcpstate->currseq = seq; + tcpstate->currlen = len; + + if (options.allow_reset_tcpstate && tcpstate->lastdns && seq > tcpstate->lastdns + 2) { + /* + * seq received is beyond where we expect next DNS message + * to be, reset tcpstate and continue + */ + tcpstate->maxdiff = 0; + tcpstate->dnslen = 0; + tcpstate->lastdns = seq; + } + + if (dumptrace >= 3) + fprintf(stderr, "diff=%08x; lastdns=%08x; ", seqdiff, tcpstate->lastdns); + + if (tcpstate->lastdns && seq == tcpstate->lastdns && len > 2) { + if (dumptrace >= 3) + fprintf(stderr, "+len+hdr\n"); + dnslen = tcpstate->dnslen = (pkt[0] << 8) | (pkt[1] << 0); + dnspkt = pkt + 2; + if (dnslen > len - 2) + dnslen = len - 2; + flags |= DNSCAP_OUTPUT_ISDNS; + tcpstate->maxdiff = (uint32_t)len; + tcpstate->lastdns = seq + 2 + tcpstate->dnslen; + } else if (tcpstate->lastdns && seq == tcpstate->lastdns && len == 2) { + if (dumptrace >= 3) + fprintf(stderr, "+len\n"); + tcpstate->dnslen = (pkt[0] << 8) | (pkt[1] << 0); + tcpstate->maxdiff = (uint32_t)len; + + _curr_tcpstate = tcpstate; + output(descr, from, to, proto, flags, sport, dport, ts, pkt_copy, length, NULL, 0); + _curr_tcpstate = 0; + return; + } else if (tcpstate->lastdns && ((seq == tcpstate->lastdns && len == 1) || seqdiff == 1)) { + tcpstate_discard(tcpstate, NULL); + return; + } else if (tcpstate->lastdns && seq == tcpstate->lastdns + 2) { + if (dumptrace >= 3) + fprintf(stderr, "+hdr\n"); + tcpstate->maxdiff = seqdiff + (uint32_t)len; + dnslen = tcpstate->dnslen; + dnspkt = pkt; + if (dnslen == 0) /* we never received it */ + dnslen = len; + if (dnslen > len) + dnslen = len; + flags |= DNSCAP_OUTPUT_ISDNS; + tcpstate->lastdns = seq + tcpstate->dnslen; + } else if (seqdiff == 0 && len > 2) { + if (dumptrace >= 3) + fprintf(stderr, "len+hdr\n"); + + /* + * This is the first segment of the stream, and + * contains the dnslen and dns header, so we can + * filter on it. + */ + dnslen = tcpstate->dnslen = (pkt[0] << 8) | (pkt[1] << 0); + dnspkt = pkt + 2; + if (dnslen > len - 2) + dnslen = len - 2; + flags |= DNSCAP_OUTPUT_ISDNS; + tcpstate->maxdiff = (uint32_t)len; + tcpstate->lastdns = seq + 2 + tcpstate->dnslen; + } else if (seqdiff == 0 && len == 2) { + if (dumptrace >= 3) + fprintf(stderr, "len\n"); + + /* + * This is the first segment of the stream, but only + * contains the dnslen. + */ + tcpstate->dnslen = (pkt[0] << 8) | (pkt[1] << 0); + tcpstate->maxdiff = (uint32_t)len; + + _curr_tcpstate = tcpstate; + output(descr, from, to, proto, flags, sport, dport, ts, pkt_copy, length, NULL, 0); + _curr_tcpstate = 0; + return; + } else if ((seqdiff == 0 && len == 1) || seqdiff == 1) { + /* shouldn't happen */ + tcpstate_discard(tcpstate, NULL); + return; + } else if (seqdiff == 2) { + if (dumptrace >= 3) + fprintf(stderr, "hdr\n"); + + /* + * This is not the first segment, but it does contain + * the first dns header, so we can filter on it. + */ + tcpstate->maxdiff = seqdiff + (uint32_t)len; + dnslen = tcpstate->dnslen; + dnspkt = pkt; + if (dnslen == 0) /* we never received it */ + dnslen = len; + if (dnslen > len) + dnslen = len; + flags |= DNSCAP_OUTPUT_ISDNS; + tcpstate->lastdns = seq + tcpstate->dnslen; + } else if (seqdiff > tcpstate->maxdiff + MAX_TCP_WINDOW) { + if (dumptrace >= 3) + fprintf(stderr, "out of window\n"); + + /* This segment is outside the window. */ + return; + } else if (len == 0) { + if (dumptrace >= 3) + fprintf(stderr, "empty\n"); + + /* No payload (e.g., an ACK) */ + return; + } else { + if (dumptrace >= 3) + fprintf(stderr, "keep\n"); + + /* non-first */ + if (tcpstate->maxdiff < seqdiff + (uint32_t)len) + tcpstate->maxdiff = seqdiff + (uint32_t)len; + + _curr_tcpstate = tcpstate; + output(descr, from, to, proto, flags, sport, dport, ts, pkt_copy, length, NULL, 0); + _curr_tcpstate = 0; + return; + } + } else { + if (dumptrace >= 3) + fprintf(stderr, "no state\n"); + + /* + * There is no state for this stream. Either we never saw + * a SYN for this stream, or we have already decided to + * discard this stream. + */ + return; + } + } else { + return; + } + + for (m = 0; m < MAX_TCP_DNS_MSG; m++) { + if (tcpstate && tcpstate->reasm) { + if (!tcpstate->reasm->dnsmsg[m]) + continue; + dnslen = tcpstate->reasm->dnsmsg[m]->dnslen; + dnspkt = tcpstate->reasm->dnsmsg[m]->dnspkt; + flags |= DNSCAP_OUTPUT_ISDNS; + if (tcpstate->reasm->dnsmsg[m]->segments_seen > 1) { + /* emulate dnslen in own packet */ + _curr_tcpstate = tcpstate; + output(descr, from, to, proto, flags, sport, dport, ts, pkt_copy, length, NULL, 0); + _curr_tcpstate = 0; + } + } + + /* Application. */ + if (!dnspkt) { + tcpstate_discard(tcpstate, "no dns"); + return; + } + if (dnslen < sizeof dns) { + tcpstate_discard(tcpstate, "too small"); + return; + } + memcpy(&dns, dnspkt, sizeof dns); + + /* Policy filtering. */ + if (dns.qr == 0 && dport == dns_port) { + if ((dir_wanted & DIR_INITIATE) == 0) { + tcpstate_discard(tcpstate, "unwanted dir=i"); + return; + } + initiator = from; + responder = to; + response = FALSE; + } else if (dns.qr != 0 && sport == dns_port) { + if ((dir_wanted & DIR_RESPONSE) == 0) { + tcpstate_discard(tcpstate, "unwanted dir=r"); + return; + } + initiator = to; + responder = from; + response = TRUE; + } else { + tcpstate_discard(tcpstate, "unwanted direction/port"); + return; + } + if ((!EMPTY(initiators) && !ep_present(&initiators, initiator)) || (!EMPTY(responders) && !ep_present(&responders, responder))) { + tcpstate_discard(tcpstate, "unwanted host"); + return; + } + if ((!EMPTY(not_initiators) && ep_present(¬_initiators, initiator)) || (!EMPTY(not_responders) && ep_present(¬_responders, responder))) { + tcpstate_discard(tcpstate, "missing required host"); + return; + } + if (!(((msg_wanted & MSG_QUERY) != 0 && dns.opcode == LDNS_PACKET_QUERY) || ((msg_wanted & MSG_UPDATE) != 0 && dns.opcode == LDNS_PACKET_UPDATE) || ((msg_wanted & MSG_NOTIFY) != 0 && dns.opcode == LDNS_PACKET_NOTIFY))) { + tcpstate_discard(tcpstate, "unwanted opcode"); + return; + } + if (response) { + int match_tc = (dns.tc != 0 && err_wanted & ERR_TRUNC); + int match_rcode = err_wanted & (ERR_RCODE_BASE << dns.rcode); + + if (!match_tc && !match_rcode) { + tcpstate_discard(tcpstate, "unwanted error code"); + return; + } + if (!EMPTY(drop_responders) && ep_present(&drop_responders, responder)) { + tcpstate_discard(tcpstate, "dropped response due to -Y"); + return; + } + } + if (!EMPTY(myregexes) || match_qtype || nmatch_qtype) { + if (ldns_wire2pkt(&lpkt, dnspkt, dnslen) != LDNS_STATUS_OK) { + /* DNS message may have padding, try get actual size */ + size_t dnslen2 = calcdnslen(dnspkt, dnslen); + if (dnslen2 > 0 && dnslen2 < dnslen) { + if (ldns_wire2pkt(&lpkt, dnspkt, dnslen2) != LDNS_STATUS_OK) { + tcpstate_discard(tcpstate, "failed parse"); + return; + } + } else { + tcpstate_discard(tcpstate, "failed parse"); + return; + } + } + } + if (match_qtype || nmatch_qtype) { + ldns_rr_list* rrs = ldns_pkt_question(lpkt); + if (!rrs) { + ldns_pkt_free(lpkt); + tcpstate_discard(tcpstate, "failed to get list of questions"); + return; + } + /* Look at each RR in the section (or each QNAME in + the question section). */ + size_t i, n; + for (i = 0, n = ldns_rr_list_rr_count(rrs); i < n; i++) { + ldns_rr* rr = ldns_rr_list_rr(rrs, i); + if (!rr) { + ldns_pkt_free(lpkt); + tcpstate_discard(tcpstate, "failed to get question"); + return; + } + + if (match_qtype && ldns_rr_get_type(rr) != match_qtype) { + ldns_pkt_free(lpkt); + tcpstate_discard(tcpstate, "qtype not match"); + return; + } else if (nmatch_qtype && ldns_rr_get_type(rr) == nmatch_qtype) { + ldns_pkt_free(lpkt); + tcpstate_discard(tcpstate, "!qtype match"); + return; + } + } + } + if (!EMPTY(myregexes)) { + int match, negmatch; + ldns_buffer* buf = ldns_buffer_new(512); + + if (!buf) { + fprintf(stderr, "%s: out of memory", ProgramName); + exit(1); + } + + match = -1; + negmatch = -1; + /* Look at each section of the message: + question, answer, authority, additional */ + ldns_rr_list* rrs = ldns_pkt_all(lpkt); + if (!rrs) { + ldns_pkt_free(lpkt); + ldns_buffer_free(buf); + tcpstate_discard(tcpstate, "failed to get list of RRs"); + return; + } + /* Look at each RR in the section (or each QNAME in + the question section). */ + size_t i, n; + for (i = 0, n = ldns_rr_list_rr_count(rrs); i < n; i++) { + ldns_rr* rr = ldns_rr_list_rr(rrs, i); + if (!rr) { + ldns_rr_list_free(rrs); + ldns_pkt_free(lpkt); + ldns_buffer_free(buf); + tcpstate_discard(tcpstate, "failed to get RR"); + return; + } + + ldns_buffer_clear(buf); + if (ldns_rdf2buffer_str(buf, ldns_rr_owner(rr)) != LDNS_STATUS_OK) { + ldns_rr_list_free(rrs); + ldns_pkt_free(lpkt); + ldns_buffer_free(buf); + tcpstate_discard(tcpstate, "failed to get RR"); + return; + } + + myregex_ptr myregex; + for (myregex = HEAD(myregexes); + myregex != NULL; + myregex = NEXT(myregex, link)) { + if (myregex->not ) { + if (negmatch < 0) + negmatch = 0; + } else { + if (match < 0) + match = 0; + } + + if (regexec(&myregex->reg, (char*)ldns_buffer_begin(buf), 0, NULL, 0) == 0) { + if (myregex->not ) + negmatch++; + else + match++; + + if (dumptrace >= 2) + fprintf(stderr, + "; \"%s\" %s~ /%s/ %d %d\n", + (char*)ldns_buffer_begin(buf), + myregex->not ? "!" : "", + myregex->str, + match, + negmatch); + } + } + } + ldns_rr_list_free(rrs); + ldns_buffer_free(buf); + + /* + * Fail if any negative matching or if no match, match can be -1 which + * indicates that there are only negative matching + */ + if (negmatch > 0 || match == 0) { + ldns_pkt_free(lpkt); + tcpstate_discard(tcpstate, "failed regex match"); + return; + } + } + if (lpkt) { + ldns_pkt_free(lpkt); + } + + /* + * TODO: Policy hiding. + */ + + _curr_tcpstate = tcpstate; + output(descr, from, to, proto, flags, sport, dport, ts, pkt_copy, length, dnspkt, dnslen); + _curr_tcpstate = 0; + + if (tcpstate && tcpstate->reasm) { + free(tcpstate->reasm->dnsmsg[m]); + tcpstate->reasm->dnsmsg[m] = 0; + tcpstate->reasm->dnsmsgs--; + } else + break; + } +} + +void network_pkt(const char* descr, my_bpftimeval ts, unsigned pf, + const u_char* opkt, size_t olen) +{ + u_char pkt_copy[SNAPLEN], *pkt = pkt_copy; + const u_char* dnspkt = 0; + unsigned proto, sport, dport; + iaddr from, to, initiator, responder; + struct ip6_hdr* ipv6; + int response, m; + unsigned flags = 0; + struct udphdr* udp = NULL; + struct tcphdr* tcp = NULL; + tcpstate_ptr tcpstate = NULL; + struct ip* ip; + size_t len, dnslen = 0; + HEADER dns; + ldns_pkt* lpkt = 0; + + if (dumptrace >= 4) + fprintf(stderr, "processing %s packet: len=%zu\n", (pf == PF_INET ? "IPv4" : (pf == PF_INET6 ? "IPv6" : "unknown")), olen); + + /* Make a writable copy of the packet and use that copy from now on. */ + memcpy(pkt, opkt, len = olen); + + /* Network. */ + ip = NULL; + ipv6 = NULL; + sport = dport = 0; + switch (pf) { + case PF_INET: { + unsigned offset; + + if (len < sizeof *ip) + return; + network_ip = ip = (void*)pkt; + network_ipv6 = 0; + if (ip->ip_v != IPVERSION) + goto network_pkt_end; + proto = ip->ip_p; + memset(&from, 0, sizeof from); + from.af = AF_INET; + memcpy(&from.u.a4, &ip->ip_src, sizeof(struct in_addr)); + memset(&to, 0, sizeof to); + to.af = AF_INET; + memcpy(&to.u.a4, &ip->ip_dst, sizeof(struct in_addr)); + offset = ip->ip_hl << 2; + if (len > ntohs(ip->ip_len)) /* small IP packets have L2 padding */ + len = ntohs(ip->ip_len); + if (len <= (size_t)offset) + goto network_pkt_end; + pkt += offset; + len -= offset; + offset = ntohs(ip->ip_off); + if ((offset & IP_MF) != 0 || (offset & IP_OFFMASK) != 0) { + if (wantfrags) { + flags |= DNSCAP_OUTPUT_ISFRAG; + output(descr, from, to, ip->ip_p, flags, sport, dport, ts, pkt_copy, olen, NULL, 0); + goto network_pkt_end; + } + goto network_pkt_end; + } + break; + } + case PF_INET6: { + uint16_t payload_len; + uint8_t nexthdr; + unsigned offset; + + if (len < sizeof *ipv6) + return; + network_ipv6 = ipv6 = (void*)pkt; + network_ip = 0; + if ((ipv6->ip6_vfc & IPV6_VERSION_MASK) != IPV6_VERSION) + goto network_pkt_end; + + nexthdr = ipv6->ip6_nxt; + offset = sizeof(struct ip6_hdr); + payload_len = ntohs(ipv6->ip6_plen); + + memset(&from, 0, sizeof from); + from.af = AF_INET6; + memcpy(&from.u.a6, &ipv6->ip6_src, sizeof(struct in6_addr)); + memset(&to, 0, sizeof to); + to.af = AF_INET6; + memcpy(&to.u.a6, &ipv6->ip6_dst, sizeof(struct in6_addr)); + + while (nexthdr == IPPROTO_ROUTING || /* routing header */ + nexthdr == IPPROTO_HOPOPTS || /* Hop-by-Hop opts */ + nexthdr == IPPROTO_FRAGMENT || /* fragmentation hdr */ + nexthdr == IPPROTO_DSTOPTS || /* destination opts */ + nexthdr == IPPROTO_AH || /* destination opts */ + nexthdr == IPPROTO_ESP) /* encap sec payload */ + { + struct { + uint8_t nexthdr; + uint8_t length; + } ext_hdr; + uint16_t ext_hdr_len; + + /* Catch broken packets */ + if ((offset + sizeof ext_hdr) > len) + goto network_pkt_end; + + /* Cannot handle fragments. */ + if (nexthdr == IPPROTO_FRAGMENT) { + if (wantfrags) { + flags |= DNSCAP_OUTPUT_ISFRAG; + output(descr, from, to, IPPROTO_FRAGMENT, flags, sport, dport, ts, pkt_copy, olen, NULL, 0); + goto network_pkt_end; + } + goto network_pkt_end; + } + + memcpy(&ext_hdr, (u_char*)ipv6 + offset, + sizeof ext_hdr); + nexthdr = ext_hdr.nexthdr; + ext_hdr_len = (8 * (ntohs(ext_hdr.length) + 1)); + + if (ext_hdr_len > payload_len) + goto network_pkt_end; + + offset += ext_hdr_len; + payload_len -= ext_hdr_len; + } + + if ((offset + payload_len) > len || payload_len == 0) + goto network_pkt_end; + + proto = nexthdr; + pkt += offset; + len -= offset; + break; + } + default: + goto network_pkt_end; + } + + /* Transport. */ + switch (proto) { + case IPPROTO_ICMP: + case IPPROTO_ICMPV6: + network_udp = 0; + output(descr, from, to, proto, flags, sport, dport, ts, pkt_copy, olen, pkt, len); + goto network_pkt_end; + case IPPROTO_UDP: { + if (len < sizeof *udp) + goto network_pkt_end; + network_udp = udp = (void*)pkt; + switch (from.af) { + case AF_INET: + case AF_INET6: + sport = ntohs(udp->uh_sport); + dport = ntohs(udp->uh_dport); + break; + default: + abort(); + } + pkt += sizeof *udp; + len -= sizeof *udp; + dnspkt = pkt; + dnslen = len; + flags |= DNSCAP_OUTPUT_ISDNS; + break; + } + case IPPROTO_TCP: { + network_udp = 0; + + /* TCP processing. + * We need to capture enough to allow a later analysis to + * reassemble the TCP stream, but we don't want to keep all + * the state required to do reassembly here. + * When we get a SYN, we don't yet know if the DNS message + * will pass the filters, so we always output it, and also + * generate a tcpstate to keep track of the stream. (An + * alternative would be to store the SYN packet on the + * tcpstate and not output it until a later packet passes the + * filter, but that would require more memory and would + * reorder packets in the pcap output.) + * When we get the _first_ DNS header on the stream, then we + * can apply the DNS header filters; if the packet passes, we + * output the packet and keep the tcpstate; if it fails, we + * discard the packet and the tcpstate. + * When we get any other packet with DNS payload, we output it + * only if there is a corresponding tcpstate indicating that + * the header passed the filters. + * Packets with no TCP payload (e.g., packets containing only + * an ACK) are discarded, since they carry no DNS information + * and are not needed for stream reassembly. + * FIN packets are always output to match the SYN, even if the + * DNS header failed the filter, to be friendly to later + * analysis programs that allocate state for each SYN. + * -- kkeys@caida.org + */ + unsigned offset; + uint32_t seq; + if (!wanttcp) + goto network_pkt_end; + if (len < sizeof *tcp) + goto network_pkt_end; + tcp = (void*)pkt; + switch (from.af) { + case AF_INET: + case AF_INET6: + sport = ntohs(tcp->th_sport); + dport = ntohs(tcp->th_dport); + seq = ntohl(tcp->th_seq); + break; + default: + abort(); + } + offset = tcp->th_off * 4; + pkt += offset; + len -= offset; + + tcpstate = tcpstate_find(from, to, sport, dport, ts.tv_sec); + if (dumptrace >= 3) { + fprintf(stderr, "%s: tcp pkt: %lu.%06lu [%4lu] ", ProgramName, + (u_long)ts.tv_sec, (u_long)ts.tv_usec, (u_long)len); + fprintf(stderr, "%15s -> ", ia_str(from)); + fprintf(stderr, "%15s; ", ia_str(to)); + if (tcpstate) + fprintf(stderr, "want=%08x; ", tcpstate->start); + else + fprintf(stderr, "no state; "); + fprintf(stderr, "seq=%08x; ", seq); + } + if (tcp->th_flags & (TH_FIN | TH_RST)) { + /* Always output FIN and RST segments. */ + if (dumptrace >= 3) + fprintf(stderr, "FIN|RST\n"); + _curr_tcpstate = tcpstate; + output(descr, from, to, proto, flags, sport, dport, ts, + pkt_copy, olen, NULL, 0); + _curr_tcpstate = 0; + /* End of stream; deallocate the tcpstate. */ + if (tcpstate) { + UNLINK(tcpstates, tcpstate, link); + if (tcpstate->reasm) { + tcpreasm_free(tcpstate->reasm); + } + free(tcpstate); + tcpstate_count--; + } + goto network_pkt_end; + } + if (tcp->th_flags & TH_SYN) { + if (dumptrace >= 3) + fprintf(stderr, "SYN\n"); + if (tcpstate) { +#if 0 + /* Disabled because warning may scare user, and + * there's nothing else we can do anyway. */ + if (tcpstate->start == seq + 1) { + /* repeated SYN */ + } else { + /* Assume existing state is stale and recycle it. */ + if (ts.tv_sec - tcpstate->last_use < MAX_TCP_IDLE_TIME) + fprintf(stderr, "warning: recycling state for " + "duplicate tcp stream after only %ld " + "seconds idle\n", + (u_long)(ts.tv_sec - tcpstate->last_use)); + } +#endif + } else { + /* create new tcpstate */ + tcpstate = tcpstate_new(from, to, sport, dport); + } + tcpstate->last_use = ts.tv_sec; + tcpstate->start = seq + 1; /* add 1 for the SYN */ + tcpstate->maxdiff = 1; + tcpstate->dnslen = 0; + tcpstate->lastdns = 0; + + /* Always output SYN segments. */ + _curr_tcpstate = tcpstate; + output(descr, from, to, proto, flags, sport, dport, ts, pkt_copy, olen, NULL, 0); + _curr_tcpstate = 0; + + goto network_pkt_end; + } + if (options.parse_ongoing_tcp && !tcpstate && len) { + tcpstate = tcpstate_new(from, to, sport, dport); + tcpstate->last_use = ts.tv_sec; + tcpstate->start = seq; + tcpstate->maxdiff = 0; + tcpstate->dnslen = 0; + tcpstate->lastdns = seq; + } + if (tcpstate && options.reassemble_tcp) { + if (!tcpstate->reasm) { + if (!(tcpstate->reasm = calloc(1, sizeof(tcpreasm_t)))) { + logerr("out of memory, TCP reassembly failed"); + goto network_pkt_end; + } + tcpstate->reasm->seq_start = tcpstate->start; + tcpstate->reasm->seq_bfb = tcpstate->start; + } + if (options.allow_reset_tcpstate) { + if (tcpstate->reasm_faults > options.reassemble_tcp_faultreset) { + if (dumptrace >= 3) + fprintf(stderr, "fault reset "); + tcpstate_reset(tcpstate, "too many reassembly faults"); + tcpstate->reasm->seq_start = seq; + tcpstate->reasm->seq_bfb = seq; + tcpstate->reasm_faults = 0; + } + if (dumptrace >= 3) + fprintf(stderr, "reassemble\n"); + if (pcap_handle_tcp_segment(pkt, len, seq, tcpstate)) { + tcpstate->reasm_faults++; + } + } else { + if (dumptrace >= 3) + fprintf(stderr, "reassemble\n"); + (void)pcap_handle_tcp_segment(pkt, len, seq, tcpstate); + } + } else if (tcpstate) { + uint32_t seqdiff = seq - tcpstate->start; + tcpstate->currseq = seq; + tcpstate->currlen = len; + if (options.allow_reset_tcpstate && tcpstate->lastdns && seq > tcpstate->lastdns + 2) { + /* + * seq received is beyond where we expect next DNS message + * to be, reset tcpstate and continue + */ + tcpstate->maxdiff = 0; + tcpstate->dnslen = 0; + tcpstate->lastdns = seq; + } + if (dumptrace >= 3) + fprintf(stderr, "diff=%08x; lastdns=%08x; ", seqdiff, tcpstate->lastdns); + if (tcpstate->lastdns && seq == tcpstate->lastdns && len > 2) { + if (dumptrace >= 3) + fprintf(stderr, "+len+hdr\n"); + dnslen = tcpstate->dnslen = (pkt[0] << 8) | (pkt[1] << 0); + dnspkt = pkt + 2; + if (dnslen > len - 2) + dnslen = len - 2; + flags |= DNSCAP_OUTPUT_ISDNS; + tcpstate->maxdiff = (uint32_t)len; + tcpstate->lastdns = seq + 2 + tcpstate->dnslen; + } else if (tcpstate->lastdns && seq == tcpstate->lastdns && len == 2) { + if (dumptrace >= 3) + fprintf(stderr, "+len\n"); + tcpstate->dnslen = (pkt[0] << 8) | (pkt[1] << 0); + tcpstate->maxdiff = (uint32_t)len; + + _curr_tcpstate = tcpstate; + output(descr, from, to, proto, flags, sport, dport, ts, + pkt_copy, olen, NULL, 0); + _curr_tcpstate = 0; + goto network_pkt_end; + } else if (tcpstate->lastdns && ((seq == tcpstate->lastdns && len == 1) || seqdiff == 1)) { + tcpstate_discard(tcpstate, NULL); + goto network_pkt_end; + } else if (tcpstate->lastdns && seq == tcpstate->lastdns + 2) { + if (dumptrace >= 3) + fprintf(stderr, "+hdr\n"); + tcpstate->maxdiff = seqdiff + (uint32_t)len; + dnslen = tcpstate->dnslen; + dnspkt = pkt; + if (dnslen == 0) /* we never received it */ + dnslen = len; + if (dnslen > len) + dnslen = len; + flags |= DNSCAP_OUTPUT_ISDNS; + tcpstate->lastdns = seq + tcpstate->dnslen; + } else if (seqdiff == 0 && len > 2) { + /* This is the first segment of the stream, and + * contains the dnslen and dns header, so we can + * filter on it. */ + if (dumptrace >= 3) + fprintf(stderr, "len+hdr\n"); + dnslen = tcpstate->dnslen = (pkt[0] << 8) | (pkt[1] << 0); + dnspkt = pkt + 2; + if (dnslen > len - 2) + dnslen = len - 2; + flags |= DNSCAP_OUTPUT_ISDNS; + tcpstate->maxdiff = (uint32_t)len; + tcpstate->lastdns = seq + 2 + tcpstate->dnslen; + } else if (seqdiff == 0 && len == 2) { + /* This is the first segment of the stream, but only + * contains the dnslen. */ + if (dumptrace >= 3) + fprintf(stderr, "len\n"); + tcpstate->dnslen = (pkt[0] << 8) | (pkt[1] << 0); + tcpstate->maxdiff = (uint32_t)len; + + _curr_tcpstate = tcpstate; + output(descr, from, to, proto, flags, sport, dport, ts, + pkt_copy, olen, NULL, 0); + _curr_tcpstate = 0; + goto network_pkt_end; + } else if ((seqdiff == 0 && len == 1) || seqdiff == 1) { + /* shouldn't happen */ + tcpstate_discard(tcpstate, NULL); + goto network_pkt_end; + } else if (seqdiff == 2) { + /* This is not the first segment, but it does contain + * the first dns header, so we can filter on it. */ + if (dumptrace >= 3) + fprintf(stderr, "hdr\n"); + tcpstate->maxdiff = seqdiff + (uint32_t)len; + dnslen = tcpstate->dnslen; + dnspkt = pkt; + if (dnslen == 0) /* we never received it */ + dnslen = len; + if (dnslen > len) + dnslen = len; + flags |= DNSCAP_OUTPUT_ISDNS; + tcpstate->lastdns = seq + tcpstate->dnslen; + } else if (seqdiff > tcpstate->maxdiff + MAX_TCP_WINDOW) { + /* This segment is outside the window. */ + if (dumptrace >= 3) + fprintf(stderr, "out of window\n"); + goto network_pkt_end; + } else if (len == 0) { + /* No payload (e.g., an ACK) */ + if (dumptrace >= 3) + fprintf(stderr, "empty\n"); + goto network_pkt_end; + } else { + /* non-first */ + if (dumptrace >= 3) + fprintf(stderr, "keep\n"); + if (tcpstate->maxdiff < seqdiff + (uint32_t)len) + tcpstate->maxdiff = seqdiff + (uint32_t)len; + + _curr_tcpstate = tcpstate; + output(descr, from, to, proto, flags, sport, dport, ts, + pkt_copy, olen, NULL, 0); + _curr_tcpstate = 0; + goto network_pkt_end; + } + } else { + if (dumptrace >= 3) + fprintf(stderr, "no state\n"); + /* There is no state for this stream. Either we never saw + * a SYN for this stream, or we have already decided to + * discard this stream. */ + goto network_pkt_end; + } + break; + } + default: + goto network_pkt_end; + } + + for (m = 0; m < MAX_TCP_DNS_MSG; m++) { + if (tcpstate && tcpstate->reasm) { + if (!tcpstate->reasm->dnsmsg[m]) + continue; + dnslen = tcpstate->reasm->dnsmsg[m]->dnslen; + dnspkt = tcpstate->reasm->dnsmsg[m]->dnspkt; + flags |= DNSCAP_OUTPUT_ISDNS; + if (tcpstate->reasm->dnsmsg[m]->segments_seen > 1) { + /* emulate dnslen in own packet */ + _curr_tcpstate = tcpstate; + output(descr, from, to, proto, flags, sport, dport, ts, + pkt_copy, olen, NULL, 0); + _curr_tcpstate = 0; + } + } + + /* Application. */ + if (!dnspkt) { + tcpstate_discard(tcpstate, "no dns"); + goto network_pkt_end; + } + if (dnslen < sizeof dns) { + tcpstate_discard(tcpstate, "too small"); + goto network_pkt_end; + } + memcpy(&dns, dnspkt, sizeof dns); + + /* Policy filtering. */ + if (dns.qr == 0 && dport == dns_port) { + if ((dir_wanted & DIR_INITIATE) == 0) { + tcpstate_discard(tcpstate, "unwanted dir=i"); + goto network_pkt_end; + } + initiator = from; + responder = to; + response = FALSE; + } else if (dns.qr != 0 && sport == dns_port) { + if ((dir_wanted & DIR_RESPONSE) == 0) { + tcpstate_discard(tcpstate, "unwanted dir=r"); + goto network_pkt_end; + } + initiator = to; + responder = from; + response = TRUE; + } else { + tcpstate_discard(tcpstate, "unwanted direction/port"); + goto network_pkt_end; + } + if ((!EMPTY(initiators) && !ep_present(&initiators, initiator)) || (!EMPTY(responders) && !ep_present(&responders, responder))) { + tcpstate_discard(tcpstate, "unwanted host"); + goto network_pkt_end; + } + if ((!EMPTY(not_initiators) && ep_present(¬_initiators, initiator)) || (!EMPTY(not_responders) && ep_present(¬_responders, responder))) { + tcpstate_discard(tcpstate, "missing required host"); + goto network_pkt_end; + } + if (!(((msg_wanted & MSG_QUERY) != 0 && dns.opcode == LDNS_PACKET_QUERY) || ((msg_wanted & MSG_UPDATE) != 0 && dns.opcode == LDNS_PACKET_UPDATE) || ((msg_wanted & MSG_NOTIFY) != 0 && dns.opcode == LDNS_PACKET_NOTIFY))) { + tcpstate_discard(tcpstate, "unwanted opcode"); + goto network_pkt_end; + } + if (response) { + int match_tc = (dns.tc != 0 && err_wanted & ERR_TRUNC); + int match_rcode = err_wanted & (ERR_RCODE_BASE << dns.rcode); + + if (!match_tc && !match_rcode) { + tcpstate_discard(tcpstate, "unwanted error code"); + goto network_pkt_end; + } + if (!EMPTY(drop_responders) && ep_present(&drop_responders, responder)) { + tcpstate_discard(tcpstate, "dropped response due to -Y"); + goto network_pkt_end; + } + } + if (!EMPTY(myregexes) || match_qtype || nmatch_qtype) { + if (ldns_wire2pkt(&lpkt, dnspkt, dnslen) != LDNS_STATUS_OK) { + /* DNS message may have padding, try get actual size */ + size_t dnslen2 = calcdnslen(dnspkt, dnslen); + if (dnslen2 > 0 && dnslen2 < dnslen) { + if (ldns_wire2pkt(&lpkt, dnspkt, dnslen2) != LDNS_STATUS_OK) { + tcpstate_discard(tcpstate, "failed parse"); + goto network_pkt_end; + } + } else { + tcpstate_discard(tcpstate, "failed parse"); + goto network_pkt_end; + } + } + } + if (match_qtype || nmatch_qtype) { + ldns_rr_list* rrs = ldns_pkt_question(lpkt); + if (!rrs) { + tcpstate_discard(tcpstate, "failed to get list of questions"); + goto network_pkt_end; + } + /* Look at each RR in the section (or each QNAME in + the question section). */ + size_t i, n; + for (i = 0, n = ldns_rr_list_rr_count(rrs); i < n; i++) { + ldns_rr* rr = ldns_rr_list_rr(rrs, i); + if (!rr) { + tcpstate_discard(tcpstate, "failed to get question"); + goto network_pkt_end; + } + + if (match_qtype && ldns_rr_get_type(rr) != match_qtype) { + tcpstate_discard(tcpstate, "qtype not match"); + goto network_pkt_end; + } else if (nmatch_qtype && ldns_rr_get_type(rr) == nmatch_qtype) { + tcpstate_discard(tcpstate, "!qtype match"); + goto network_pkt_end; + } + } + } + if (!EMPTY(myregexes)) { + int match, negmatch; + ldns_buffer* buf = ldns_buffer_new(512); + + if (!buf) { + fprintf(stderr, "%s: out of memory", ProgramName); + exit(1); + } + + match = -1; + negmatch = -1; + /* Look at each section of the message: + question, answer, authority, additional */ + ldns_rr_list* rrs = ldns_pkt_all(lpkt); + if (!rrs) { + ldns_buffer_free(buf); + tcpstate_discard(tcpstate, "failed to get list of RRs"); + goto network_pkt_end; + } + /* Look at each RR in the section (or each QNAME in + the question section). */ + size_t i, n; + for (i = 0, n = ldns_rr_list_rr_count(rrs); i < n; i++) { + ldns_rr* rr = ldns_rr_list_rr(rrs, i); + if (!rr) { + ldns_rr_list_free(rrs); + ldns_buffer_free(buf); + tcpstate_discard(tcpstate, "failed to get RR"); + goto network_pkt_end; + } + + ldns_buffer_clear(buf); + if (ldns_rdf2buffer_str(buf, ldns_rr_owner(rr)) != LDNS_STATUS_OK) { + ldns_rr_list_free(rrs); + ldns_buffer_free(buf); + tcpstate_discard(tcpstate, "failed to get RR"); + goto network_pkt_end; + } + + myregex_ptr myregex; + for (myregex = HEAD(myregexes); + myregex != NULL; + myregex = NEXT(myregex, link)) { + if (myregex->not ) { + if (negmatch < 0) + negmatch = 0; + } else { + if (match < 0) + match = 0; + } + + if (regexec(&myregex->reg, (char*)ldns_buffer_begin(buf), 0, NULL, 0) == 0) { + if (myregex->not ) + negmatch++; + else + match++; + + if (dumptrace >= 2) + fprintf(stderr, + "; \"%s\" %s~ /%s/ %d %d\n", + (char*)ldns_buffer_begin(buf), + myregex->not ? "!" : "", + myregex->str, + match, + negmatch); + } + } + } + ldns_rr_list_free(rrs); + ldns_buffer_free(buf); + + /* + * Fail if any negative matching or if no match, match can be -1 which + * indicates that there are only negative matching + */ + if (negmatch > 0 || match == 0) { + tcpstate_discard(tcpstate, "failed regex match"); + goto network_pkt_end; + } + } + + /* Policy hiding. */ + if (end_hide != 0) { + switch (from.af) { + case AF_INET: { + void * init_addr, *resp_addr; + uint16_t* init_port; + + if (dns.qr == 0) { + init_addr = (void*)&ip->ip_src; + resp_addr = (void*)&ip->ip_dst; + init_port = tcp ? &tcp->th_sport : &udp->uh_sport; + } else { + init_addr = (void*)&ip->ip_dst; + resp_addr = (void*)&ip->ip_src; + init_port = tcp ? &tcp->th_dport : &udp->uh_dport; + } + + if ((end_hide & END_INITIATOR) != 0) { + memcpy(init_addr, HIDE_INET, sizeof(struct in_addr)); + *init_port = htons(HIDE_PORT); + } + if ((end_hide & END_RESPONDER) != 0) + memcpy(resp_addr, HIDE_INET, sizeof(struct in_addr)); + + ip->ip_sum = 0; + ip->ip_sum = ~in_checksum((u_char*)ip, sizeof *ip); + if (udp) + udp->uh_sum = 0U; + break; + } + case AF_INET6: { + void * init_addr, *resp_addr; + uint16_t* init_port; + + if (dns.qr == 0) { + init_addr = (void*)&ipv6->ip6_src; + resp_addr = (void*)&ipv6->ip6_dst; + init_port = tcp ? &tcp->th_sport : &udp->uh_sport; + } else { + init_addr = (void*)&ipv6->ip6_dst; + resp_addr = (void*)&ipv6->ip6_src; + init_port = tcp ? &tcp->th_dport : &udp->uh_dport; + } + + if ((end_hide & END_INITIATOR) != 0) { + memcpy(init_addr, HIDE_INET6, sizeof(struct in6_addr)); + *init_port = htons(HIDE_PORT); + } + if ((end_hide & END_RESPONDER) != 0) + memcpy(resp_addr, HIDE_INET6, sizeof(struct in6_addr)); + + if (udp) + udp->uh_sum = 0U; + break; + } + default: + abort(); + } + } + _curr_tcpstate = tcpstate; + output(descr, from, to, proto, flags, sport, dport, ts, + pkt_copy, olen, dnspkt, dnslen); + _curr_tcpstate = 0; + + if (tcpstate && tcpstate->reasm) { + free(tcpstate->reasm->dnsmsg[m]); + tcpstate->reasm->dnsmsg[m] = 0; + tcpstate->reasm->dnsmsgs--; + } else + break; + } + +network_pkt_end: + network_ip = 0; + network_ipv6 = 0; + if (lpkt) { + ldns_pkt_free(lpkt); + } +} + +uint16_t in_checksum(const u_char* ptr, size_t len) +{ + unsigned sum = 0, top; + + /* Main body. */ + while (len >= 2) { + sum += *(const uint16_t*)ptr; + ptr += 2; + len -= 2; + } + + /* Leftover octet? */ + if (len != 0) + sum += *ptr; + + /* Leftover carries? */ + while ((top = (sum >> 16)) != 0) + sum = ((uint16_t)sum) + top; + + /* Caller should ~ this result. */ + return ((uint16_t)sum); +} + +static size_t calcrr(int q, const u_char* p, size_t l, size_t t) +{ + while (l < t) { + if ((p[l] & 0xc0) == 0xc0) { + l += 2; + } else if (p[l] & 0xc0) { + l += 1; + } else if (p[l]) { + l += p[l]; + } else { + break; + } + } + l += 4; /* type + class */ + if (q) + return l; + l += 6; /* ttl + rdlength */ + if (l < t) { + l += (p[l - 2] << 8) + p[l - 1]; /* rdata */ + } + return l; +} + +size_t calcdnslen(const u_char* dnspkt, size_t dnslen) +{ + HEADER dns; + size_t n, len; + + if (dnslen > 65535 || dnslen < sizeof(dns)) { + return 0; + } + memcpy(&dns, dnspkt, sizeof dns); + len = sizeof(dns); + + for (n = 0; len < dnslen && n < dns.qdcount; n++) { + len = calcrr(1, dnspkt, len, dnslen); + } + for (n = 0; len < dnslen && n < dns.ancount; n++) { + len = calcrr(0, dnspkt, len, dnslen); + } + for (n = 0; len < dnslen && n < dns.nscount; n++) { + len = calcrr(0, dnspkt, len, dnslen); + } + for (n = 0; len < dnslen && n < dns.arcount; n++) { + len = calcrr(0, dnspkt, len, dnslen); + } + if (len < dnslen) + return len; + return dnslen; +} |