/* * Copyright (c) 2016-2023, 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 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; } /* * Determine if packet should be filtered based on query type * return values: -1 error * 0 don't filter * 1 filter * for non-zero return status, reason should be set to a * static string. */ static int _filter_by_qtype(const ldns_pkt* lpkt, char** reason) { ldns_rr_list* rrs = ldns_pkt_question(lpkt); if (!rrs) { *reason = "failed to get list of questions"; return -1; } /* 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) { *reason = "failed to get question"; return -1; } if (match_qtype && ldns_rr_get_type(rr) == match_qtype) { if (dumptrace >= 2) fprintf(stderr, "Packet query type %u matches filter on type == %u\n", ldns_rr_get_type(rr), match_qtype); return 0; } else if (nmatch_qtype && ldns_rr_get_type(rr) == nmatch_qtype) { if (dumptrace >= 2) fprintf(stderr, "Packet query type %u matches filter on type != %u\n", ldns_rr_get_type(rr), nmatch_qtype); *reason = "matched unwanted qtype"; return 1; } } if (match_qtype) { *reason = "didn't match wanted qtype"; return 1; // didn't match any question RRs } return 0; } /* * Determine if packet should be filtered based on query name * return values: -1 error * 0 don't filter * 1 filter * for non-zero return status, reason should be set to a * static string. */ static int _match_rr(const ldns_rr_list* rrs, char** reason, int* negmatch, int* match, ldns_buffer* buf) { /* Look at each RR in the section (or each QNAME in the question section). */ myregex_ptr myregex; 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) { *reason = "failed to get RR"; return -1; } ldns_buffer_clear(buf); if (ldns_rdf2buffer_str(buf, ldns_rr_owner(rr)) != LDNS_STATUS_OK) { *reason = "failed to get RR"; return -1; } for (myregex = HEAD(myregexes); myregex != NULL; myregex = NEXT(myregex, link)) { 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); } } } return 0; } static int _filter_by_qname(const ldns_pkt* lpkt, char** reason) { int match = -1; int negmatch = -1; ldns_buffer* buf = ldns_buffer_new(512); if (!buf) { fprintf(stderr, "%s: out of memory", ProgramName); exit(1); } /* * Initialize matching counters */ myregex_ptr myregex; for (myregex = HEAD(myregexes); myregex != NULL; myregex = NEXT(myregex, link)) { if (myregex->not ) { negmatch = 0; } else { match = 0; } } ldns_rr_list* rrs; if ((rrs = ldns_pkt_question(lpkt))) { if (_match_rr(rrs, reason, &negmatch, &match, buf)) return -1; } if ((rrs = ldns_pkt_answer(lpkt))) { if (_match_rr(rrs, reason, &negmatch, &match, buf)) return -1; } if ((rrs = ldns_pkt_authority(lpkt))) { if (_match_rr(rrs, reason, &negmatch, &match, buf)) return -1; } if ((rrs = ldns_pkt_additional(lpkt))) { if (_match_rr(rrs, reason, &negmatch, &match, buf)) return -1; } ldns_buffer_free(buf); /* * Fail if any negative matches or if no positive matches */ if (negmatch > 0 || match == 0) { *reason = "failed regex match"; return 1; } return 0; } 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; 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; /* 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 = pkt; dnslen = len; 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. */ tcpstate_free(tcpstate); 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) { ldns_pkt* lpkt = 0; 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; } } char* reason = 0; if ((match_qtype || nmatch_qtype) && _filter_by_qtype(lpkt, &reason)) { ldns_pkt_free(lpkt); tcpstate_discard(tcpstate, reason); return; } if (!EMPTY(myregexes) && _filter_by_qname(lpkt, &reason)) { ldns_pkt_free(lpkt); tcpstate_discard(tcpstate, reason); return; } 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; 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; 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. */ tcpstate_free(tcpstate); 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) { ldns_pkt* lpkt = 0; 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; } } char* reason = 0; if ((match_qtype || nmatch_qtype) && _filter_by_qtype(lpkt, &reason)) { ldns_pkt_free(lpkt); tcpstate_discard(tcpstate, reason); goto network_pkt_end; } if (!EMPTY(myregexes) && _filter_by_qname(lpkt, &reason)) { ldns_pkt_free(lpkt); tcpstate_discard(tcpstate, reason); goto network_pkt_end; } ldns_pkt_free(lpkt); } /* 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; } 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; }