mirror of
				https://github.com/telekom-security/tpotce.git
				synced 2025-10-31 04:22:52 +00:00 
			
		
		
		
	
		
			
	
	
		
			1549 lines
		
	
	
	
		
			34 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
		
		
			
		
	
	
			1549 lines
		
	
	
	
		
			34 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
|   | /*
 | ||
|  |    p0f - packet capture and overall host / flow bookkeeping | ||
|  |    -------------------------------------------------------- | ||
|  | 
 | ||
|  |    Copyright (C) 2012 by Michal Zalewski <lcamtuf@coredump.cx> | ||
|  | 
 | ||
|  |    Distributed under the terms and conditions of GNU LGPL. | ||
|  | 
 | ||
|  |  */ | ||
|  | 
 | ||
|  | #include <stdio.h>
 | ||
|  | #include <stdlib.h>
 | ||
|  | #include <unistd.h>
 | ||
|  | #include <pcap.h>
 | ||
|  | #include <time.h>
 | ||
|  | #include <ctype.h>
 | ||
|  | 
 | ||
|  | #include <sys/fcntl.h>
 | ||
|  | #include <netinet/in.h>
 | ||
|  | #include <sys/types.h>
 | ||
|  | #include <sys/socket.h>
 | ||
|  | #include <arpa/inet.h>
 | ||
|  | #include <sys/time.h>
 | ||
|  | #include <sys/stat.h>
 | ||
|  | 
 | ||
|  | #include "types.h"
 | ||
|  | #include "config.h"
 | ||
|  | #include "debug.h"
 | ||
|  | #include "alloc-inl.h"
 | ||
|  | #include "process.h"
 | ||
|  | #include "hash.h"
 | ||
|  | #include "tcp.h"
 | ||
|  | #include "readfp.h"
 | ||
|  | #include "p0f.h"
 | ||
|  | 
 | ||
|  | #include "fp_tcp.h"
 | ||
|  | #include "fp_mtu.h"
 | ||
|  | #include "fp_http.h"
 | ||
|  | 
 | ||
|  | u64 packet_cnt;                         /* Total number of packets processed  */ | ||
|  | 
 | ||
|  | static s8 link_off = -1;                /* Link-specific IP header offset     */ | ||
|  | static u8 bad_packets;                  /* Seen non-IP packets?               */ | ||
|  | 
 | ||
|  | static struct host_data *host_by_age,   /* All host entries, by last mod      */ | ||
|  |                         *newest_host;   /* Tail of the list                   */ | ||
|  | 
 | ||
|  | static struct packet_flow *flow_by_age, /* All flows, by creation time        */ | ||
|  |                           *newest_flow; /* Tail of the list                   */ | ||
|  | 
 | ||
|  | static struct timeval* cur_time;        /* Current time, courtesy of pcap     */ | ||
|  | 
 | ||
|  | /* Bucketed hosts and flows: */ | ||
|  | 
 | ||
|  | static struct host_data    *host_b[HOST_BUCKETS]; | ||
|  | static struct packet_flow  *flow_b[FLOW_BUCKETS]; | ||
|  | 
 | ||
|  | static u32 host_cnt, flow_cnt;          /* Counters for bookkeeping purposes  */ | ||
|  | 
 | ||
|  | static void flow_dispatch(struct packet_data* pk); | ||
|  | static void nuke_flows(u8 silent); | ||
|  | static void expire_cache(void); | ||
|  | 
 | ||
|  | 
 | ||
|  | /* Get unix time in milliseconds. */ | ||
|  | 
 | ||
|  | u64 get_unix_time_ms(void) { | ||
|  | 
 | ||
|  |   return ((u64)cur_time->tv_sec) * 1000 + (cur_time->tv_usec / 1000); | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | /* Get unix time in seconds. */ | ||
|  | 
 | ||
|  | u32 get_unix_time(void) { | ||
|  |   return cur_time->tv_sec; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | /* Find link-specific offset (pcap knows, but won't tell). */ | ||
|  | 
 | ||
|  | static void find_offset(const u8* data, s32 total_len) { | ||
|  | 
 | ||
|  |   u8 i; | ||
|  | 
 | ||
|  |   /* Check hardcoded values for some of the most common options. */ | ||
|  | 
 | ||
|  |   switch (link_type) { | ||
|  | 
 | ||
|  |     case DLT_RAW:        link_off = 0;  return; | ||
|  | 
 | ||
|  |     case DLT_NULL: | ||
|  |     case DLT_PPP:        link_off = 4;  return; | ||
|  | 
 | ||
|  |     case DLT_LOOP: | ||
|  | 
 | ||
|  | #ifdef DLT_PPP_SERIAL
 | ||
|  |     case DLT_PPP_SERIAL: | ||
|  | #endif /* DLT_PPP_SERIAL */
 | ||
|  | 
 | ||
|  |     case DLT_PPP_ETHER:  link_off = 8;  return; | ||
|  | 
 | ||
|  |     case DLT_EN10MB:     link_off = 14; return; | ||
|  | 
 | ||
|  | #ifdef DLT_LINUX_SLL
 | ||
|  |     case DLT_LINUX_SLL:  link_off = 16; return; | ||
|  | #endif /* DLT_LINUX_SLL */
 | ||
|  | 
 | ||
|  |     case DLT_PFLOG:      link_off = 28; return; | ||
|  | 
 | ||
|  |     case DLT_IEEE802_11: link_off = 32; return; | ||
|  |   } | ||
|  | 
 | ||
|  |   /* If this fails, try to auto-detect. There is a slight risk that if the
 | ||
|  |      first packet we see is maliciously crafted, and somehow gets past the | ||
|  |      configured BPF filter, we will configure the wrong offset. But that | ||
|  |      seems fairly unlikely. */ | ||
|  | 
 | ||
|  |   for (i = 0; i < 40; i += 2, total_len -= 2) { | ||
|  | 
 | ||
|  |     if (total_len < MIN_TCP4) break; | ||
|  | 
 | ||
|  |     /* Perhaps this is IPv6? We check three things: IP version (first 4 bits);
 | ||
|  |        total length sufficient to accommodate IPv6 and TCP headers; and the | ||
|  |        "next protocol" field equal to PROTO_TCP. */ | ||
|  | 
 | ||
|  |     if (total_len >= MIN_TCP6 && (data[i] >> 4) == IP_VER6) { | ||
|  | 
 | ||
|  |       struct ipv6_hdr* hdr = (struct ipv6_hdr*)(data + i); | ||
|  | 
 | ||
|  |       if (hdr->proto == PROTO_TCP) { | ||
|  | 
 | ||
|  |         DEBUG("[#] Detected packet offset of %u via IPv6 (link type %u).\n", i, | ||
|  |               link_type); | ||
|  |         link_off = i; | ||
|  |         break; | ||
|  | 
 | ||
|  |       } | ||
|  |        | ||
|  |     } | ||
|  | 
 | ||
|  |     /* Okay, let's try IPv4 then. The same approach, except the shortest packet
 | ||
|  |        size must be just enough to accommodate IPv4 + TCP (already checked). */ | ||
|  | 
 | ||
|  |     if ((data[i] >> 4) == IP_VER4) { | ||
|  | 
 | ||
|  |       struct ipv4_hdr* hdr = (struct ipv4_hdr*)(data + i); | ||
|  | 
 | ||
|  |       if (hdr->proto == PROTO_TCP) { | ||
|  | 
 | ||
|  |         DEBUG("[#] Detected packet offset of %u via IPv4 (link type %u).\n", i, | ||
|  |               link_type); | ||
|  |         link_off = i; | ||
|  |         break; | ||
|  | 
 | ||
|  |       } | ||
|  | 
 | ||
|  |     } | ||
|  | 
 | ||
|  |   } | ||
|  | 
 | ||
|  |   /* If we found something, adjust for VLAN tags (ETH_P_8021Q == 0x8100). Else,
 | ||
|  |      complain once and try again soon. */ | ||
|  | 
 | ||
|  |   if (link_off >= 4 && data[i-4] == 0x81 && data[i-3] == 0x00) { | ||
|  | 
 | ||
|  |     DEBUG("[#] Adjusting offset due to VLAN tagging.\n"); | ||
|  |     link_off -= 4; | ||
|  | 
 | ||
|  |   } else if (link_off == -1) { | ||
|  | 
 | ||
|  |     link_off = -2; | ||
|  |     WARN("Unable to find link-specific packet offset. This is bad."); | ||
|  | 
 | ||
|  |   } | ||
|  | 
 | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | /* Convert IPv4 or IPv6 address to a human-readable form. */ | ||
|  | 
 | ||
|  | u8* addr_to_str(u8* data, u8 ip_ver) { | ||
|  | 
 | ||
|  |   static char tmp[128]; | ||
|  | 
 | ||
|  |   /* We could be using inet_ntop(), but on systems that have older libc
 | ||
|  |      but still see passing IPv6 traffic, we would be in a pickle. */ | ||
|  | 
 | ||
|  |   if (ip_ver == IP_VER4) { | ||
|  | 
 | ||
|  |     sprintf(tmp, "%u.%u.%u.%u", data[0], data[1], data[2], data[3]); | ||
|  | 
 | ||
|  |   } else { | ||
|  | 
 | ||
|  |     sprintf(tmp, "%x:%x:%x:%x:%x:%x:%x:%x", | ||
|  |             (data[0] << 8) | data[1], (data[2] << 8) | data[3],  | ||
|  |             (data[4] << 8) | data[5], (data[6] << 8) | data[7],  | ||
|  |             (data[8] << 8) | data[9], (data[10] << 8) | data[11],  | ||
|  |             (data[12] << 8) | data[13], (data[14] << 8) | data[15]); | ||
|  | 
 | ||
|  |   } | ||
|  | 
 | ||
|  |   return (u8*)tmp; | ||
|  | 
 | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | /* Parse PCAP input, with plenty of sanity checking. Store interesting details
 | ||
|  |    in a protocol-agnostic buffer that will be then examined upstream. */ | ||
|  | 
 | ||
|  | void parse_packet(void* junk, const struct pcap_pkthdr* hdr, const u8* data) { | ||
|  | 
 | ||
|  |   struct tcp_hdr* tcp; | ||
|  |   struct packet_data pk; | ||
|  | 
 | ||
|  |   s32 packet_len; | ||
|  |   u32 tcp_doff; | ||
|  | 
 | ||
|  |   u8* opt_end; | ||
|  | 
 | ||
|  |   packet_cnt++; | ||
|  |    | ||
|  |   cur_time = (struct timeval*)&hdr->ts; | ||
|  | 
 | ||
|  |   if (!(packet_cnt % EXPIRE_INTERVAL)) expire_cache(); | ||
|  | 
 | ||
|  |   /* Be paranoid about how much data we actually have off the wire. */ | ||
|  | 
 | ||
|  |   packet_len = MIN(hdr->len, hdr->caplen); | ||
|  |   if (packet_len > SNAPLEN) packet_len = SNAPLEN; | ||
|  | 
 | ||
|  |   // DEBUG("[#] Received packet: len = %d, caplen = %d, limit = %d\n",
 | ||
|  |   //    hdr->len, hdr->caplen, SNAPLEN);
 | ||
|  | 
 | ||
|  |   /* Account for link-level headers. */ | ||
|  | 
 | ||
|  |   if (link_off < 0) find_offset(data, packet_len); | ||
|  | 
 | ||
|  |   if (link_off > 0) { | ||
|  | 
 | ||
|  |     data += link_off; | ||
|  |     packet_len -= link_off; | ||
|  | 
 | ||
|  |   } | ||
|  | 
 | ||
|  |   /* If there is no way we could have received a complete TCP packet, bail
 | ||
|  |      out early. */ | ||
|  | 
 | ||
|  |   if (packet_len < MIN_TCP4) { | ||
|  |     DEBUG("[#] Packet too short for any IPv4 + TCP headers, giving up!\n"); | ||
|  |     return; | ||
|  |   } | ||
|  | 
 | ||
|  |   pk.quirks = 0; | ||
|  | 
 | ||
|  |   if ((*data >> 4) == IP_VER4) { | ||
|  | 
 | ||
|  |     /************************
 | ||
|  |      * IPv4 header parsing. * | ||
|  |      ************************/ | ||
|  |      | ||
|  |     const struct ipv4_hdr* ip4 = (struct ipv4_hdr*)data; | ||
|  | 
 | ||
|  |     u32 hdr_len = (ip4->ver_hlen & 0x0F) * 4; | ||
|  |     u16 flags_off = ntohs(RD16(ip4->flags_off)); | ||
|  |     u16 tot_len = ntohs(RD16(ip4->tot_len)); | ||
|  | 
 | ||
|  |     /* If the packet claims to be shorter than what we received off the wire,
 | ||
|  |        honor this claim to account for etherleak-type bugs. */ | ||
|  | 
 | ||
|  |     if (packet_len > tot_len) { | ||
|  |       packet_len = tot_len; | ||
|  |       // DEBUG("[#] ipv4.tot_len = %u, adjusted accordingly.\n", tot_len);
 | ||
|  |     } | ||
|  | 
 | ||
|  |     /* Bail out if the result leaves no room for IPv4 + TCP headers. */ | ||
|  | 
 | ||
|  |     if (packet_len < MIN_TCP4) { | ||
|  |       DEBUG("[#] packet_len = %u. Too short for IPv4 + TCP, giving up!\n", | ||
|  |             packet_len); | ||
|  |       return; | ||
|  |     } | ||
|  | 
 | ||
|  |     /* Bail out if the declared length of IPv4 headers is nonsensical. */ | ||
|  | 
 | ||
|  |     if (hdr_len < sizeof(struct ipv4_hdr)) { | ||
|  |       DEBUG("[#] ipv4.hdr_len = %u. Too short for IPv4, giving up!\n", | ||
|  |             hdr_len); | ||
|  |       return; | ||
|  |     } | ||
|  | 
 | ||
|  |     /* If the packet claims to be longer than the recv buffer, best to back
 | ||
|  |        off - even though we could just ignore this and recover. */ | ||
|  | 
 | ||
|  |     if (tot_len > packet_len) { | ||
|  |       DEBUG("[#] ipv4.tot_len = %u but packet_len = %u, bailing out!\n", | ||
|  |             tot_len, packet_len); | ||
|  |       return; | ||
|  |     } | ||
|  | 
 | ||
|  |     /* And finally, bail out if after skipping the IPv4 header as specified
 | ||
|  |        (including options), there wouldn't be enough room for TCP. */ | ||
|  | 
 | ||
|  |     if (hdr_len + sizeof(struct tcp_hdr) > packet_len) { | ||
|  |       DEBUG("[#] ipv4.hdr_len = %u, packet_len = %d, no room for TCP!\n", | ||
|  |             hdr_len, packet_len); | ||
|  |       return; | ||
|  |     } | ||
|  | 
 | ||
|  |     /* Bail out if the subsequent protocol is not TCP. */ | ||
|  | 
 | ||
|  |     if (ip4->proto != PROTO_TCP) { | ||
|  |       DEBUG("[#] Whoa, IPv4 packet with non-TCP payload (%u)?\n", ip4->proto); | ||
|  |       return; | ||
|  |     } | ||
|  | 
 | ||
|  |     /* Ignore any traffic with MF or non-zero fragment offset specified. We
 | ||
|  |        can do enough just fingerprinting the non-fragmented traffic. */ | ||
|  | 
 | ||
|  |     if (flags_off & ~(IP4_DF | IP4_MBZ)) { | ||
|  |       DEBUG("[#] Packet fragment (0x%04x), letting it slide!\n", flags_off); | ||
|  |       return; | ||
|  |     } | ||
|  | 
 | ||
|  |     /* Store some relevant information about the packet. */ | ||
|  | 
 | ||
|  |     pk.ip_ver = IP_VER4; | ||
|  | 
 | ||
|  |     pk.ip_opt_len = hdr_len - 20; | ||
|  | 
 | ||
|  |     memcpy(pk.src, ip4->src, 4); | ||
|  |     memcpy(pk.dst, ip4->dst, 4); | ||
|  | 
 | ||
|  |     pk.tos = ip4->tos_ecn >> 2; | ||
|  | 
 | ||
|  |     pk.ttl = ip4->ttl; | ||
|  | 
 | ||
|  |     if (ip4->tos_ecn & (IP_TOS_CE | IP_TOS_ECT)) pk.quirks |= QUIRK_ECN; | ||
|  | 
 | ||
|  |     /* Tag some of the corner cases associated with implementation quirks. */ | ||
|  |      | ||
|  |     if (flags_off & IP4_MBZ) pk.quirks |= QUIRK_NZ_MBZ; | ||
|  | 
 | ||
|  |     if (flags_off & IP4_DF) { | ||
|  | 
 | ||
|  |       pk.quirks |= QUIRK_DF; | ||
|  |       if (RD16(ip4->id)) pk.quirks |= QUIRK_NZ_ID; | ||
|  | 
 | ||
|  |     } else { | ||
|  | 
 | ||
|  |       if (!RD16(ip4->id)) pk.quirks |= QUIRK_ZERO_ID; | ||
|  | 
 | ||
|  |     } | ||
|  | 
 | ||
|  |     pk.tot_hdr = hdr_len; | ||
|  | 
 | ||
|  |     tcp = (struct tcp_hdr*)(data + hdr_len); | ||
|  |     packet_len -= hdr_len; | ||
|  |      | ||
|  |   } else if ((*data >> 4) == IP_VER6) { | ||
|  | 
 | ||
|  |     /************************
 | ||
|  |      * IPv6 header parsing. * | ||
|  |      ************************/ | ||
|  |      | ||
|  |     const struct ipv6_hdr* ip6 = (struct ipv6_hdr*)data; | ||
|  |     u32 ver_tos = ntohl(RD32(ip6->ver_tos)); | ||
|  |     u32 tot_len = ntohs(RD16(ip6->pay_len)) + sizeof(struct ipv6_hdr); | ||
|  | 
 | ||
|  |     /* If the packet claims to be shorter than what we received off the wire,
 | ||
|  |        honor this claim to account for etherleak-type bugs. */ | ||
|  | 
 | ||
|  |     if (packet_len > tot_len) { | ||
|  |       packet_len = tot_len; | ||
|  |       // DEBUG("[#] ipv6.tot_len = %u, adjusted accordingly.\n", tot_len);
 | ||
|  |     } | ||
|  | 
 | ||
|  |     /* Bail out if the result leaves no room for IPv6 + TCP headers. */ | ||
|  | 
 | ||
|  |     if (packet_len < MIN_TCP6) { | ||
|  |       DEBUG("[#] packet_len = %u. Too short for IPv6 + TCP, giving up!\n", | ||
|  |             packet_len); | ||
|  |       return; | ||
|  |     } | ||
|  | 
 | ||
|  |     /* If the packet claims to be longer than the data we have, best to back
 | ||
|  |        off - even though we could just ignore this and recover. */ | ||
|  | 
 | ||
|  |     if (tot_len > packet_len) { | ||
|  |       DEBUG("[#] ipv6.tot_len = %u but packet_len = %u, bailing out!\n", | ||
|  |             tot_len, packet_len); | ||
|  |       return; | ||
|  |     } | ||
|  | 
 | ||
|  |     /* Bail out if the subsequent protocol is not TCP. One day, we may try
 | ||
|  |        to parse and skip IPv6 extensions, but there seems to be no point in | ||
|  |        it today. */ | ||
|  | 
 | ||
|  |     if (ip6->proto != PROTO_TCP) { | ||
|  |       DEBUG("[#] IPv6 packet with non-TCP payload (%u).\n", ip6->proto); | ||
|  |       return; | ||
|  |     } | ||
|  | 
 | ||
|  |     /* Store some relevant information about the packet. */ | ||
|  | 
 | ||
|  |     pk.ip_ver = IP_VER6; | ||
|  | 
 | ||
|  |     pk.ip_opt_len = 0; | ||
|  | 
 | ||
|  |     memcpy(pk.src, ip6->src, 16); | ||
|  |     memcpy(pk.dst, ip6->dst, 16); | ||
|  | 
 | ||
|  |     pk.tos = (ver_tos >> 22) & 0x3F; | ||
|  | 
 | ||
|  |     pk.ttl = ip6->ttl; | ||
|  | 
 | ||
|  |     if (ver_tos & 0xFFFFF) pk.quirks |= QUIRK_FLOW; | ||
|  | 
 | ||
|  |     if ((ver_tos >> 20) & (IP_TOS_CE | IP_TOS_ECT)) pk.quirks |= QUIRK_ECN; | ||
|  | 
 | ||
|  |     pk.tot_hdr = sizeof(struct ipv6_hdr); | ||
|  | 
 | ||
|  |     tcp = (struct tcp_hdr*)(ip6 + 1); | ||
|  |     packet_len -= sizeof(struct ipv6_hdr); | ||
|  | 
 | ||
|  |   } else { | ||
|  | 
 | ||
|  |     if (!bad_packets) { | ||
|  |       WARN("Unknown packet type %u, link detection issue?", *data >> 4); | ||
|  |       bad_packets = 1; | ||
|  |     } | ||
|  | 
 | ||
|  |     return; | ||
|  | 
 | ||
|  |   } | ||
|  | 
 | ||
|  |   /***************
 | ||
|  |    * TCP parsing * | ||
|  |    ***************/ | ||
|  | 
 | ||
|  |   data = (u8*)tcp; | ||
|  | 
 | ||
|  |   tcp_doff = (tcp->doff_rsvd >> 4) * 4; | ||
|  | 
 | ||
|  |   /* As usual, let's start with sanity checks. */ | ||
|  | 
 | ||
|  |   if (tcp_doff < sizeof(struct tcp_hdr)) { | ||
|  |     DEBUG("[#] tcp.hdr_len = %u, not enough for TCP!\n", tcp_doff); | ||
|  |     return; | ||
|  |   } | ||
|  | 
 | ||
|  |   if (tcp_doff > packet_len) { | ||
|  |     DEBUG("[#] tcp.hdr_len = %u, past end of packet!\n", tcp_doff); | ||
|  |     return; | ||
|  |   } | ||
|  | 
 | ||
|  |   pk.tot_hdr += tcp_doff; | ||
|  | 
 | ||
|  |   pk.sport = ntohs(RD16(tcp->sport)); | ||
|  |   pk.dport = ntohs(RD16(tcp->dport)); | ||
|  | 
 | ||
|  |   pk.tcp_type = tcp->flags & (TCP_SYN | TCP_ACK | TCP_FIN | TCP_RST); | ||
|  | 
 | ||
|  |   /* NUL, SYN+FIN, SYN+RST, FIN+RST, etc, should go to /dev/null. */ | ||
|  | 
 | ||
|  |   if (((tcp->flags & TCP_SYN) && (tcp->flags & (TCP_FIN | TCP_RST))) || | ||
|  |       ((tcp->flags & TCP_FIN) && (tcp->flags & TCP_RST)) || | ||
|  |       !pk.tcp_type) { | ||
|  | 
 | ||
|  |     DEBUG("[#] Silly combination of TCP flags: 0x%02x.\n", tcp->flags); | ||
|  |     return; | ||
|  | 
 | ||
|  |   } | ||
|  | 
 | ||
|  |   pk.win = ntohs(RD16(tcp->win)); | ||
|  | 
 | ||
|  |   pk.seq = ntohl(RD32(tcp->seq)); | ||
|  | 
 | ||
|  |   /* Take note of miscellanous features and quirks. */ | ||
|  | 
 | ||
|  |   if ((tcp->flags & (TCP_ECE | TCP_CWR)) ||  | ||
|  |       (tcp->doff_rsvd & TCP_NS_RES)) pk.quirks |= QUIRK_ECN; | ||
|  | 
 | ||
|  |   if (!pk.seq) pk.quirks |= QUIRK_ZERO_SEQ; | ||
|  | 
 | ||
|  |   if (tcp->flags & TCP_ACK) { | ||
|  | 
 | ||
|  |     if (!RD32(tcp->ack)) pk.quirks |= QUIRK_ZERO_ACK; | ||
|  | 
 | ||
|  |   } else { | ||
|  | 
 | ||
|  |     /* A good proportion of RSTs tend to have "illegal" ACK numbers, so
 | ||
|  |        ignore these. */ | ||
|  | 
 | ||
|  |     if (RD32(tcp->ack) & !(tcp->flags & TCP_RST)) { | ||
|  | 
 | ||
|  |       DEBUG("[#] Non-zero ACK on a non-ACK packet: 0x%08x.\n", | ||
|  |             ntohl(RD32(tcp->ack))); | ||
|  | 
 | ||
|  |       pk.quirks |= QUIRK_NZ_ACK; | ||
|  | 
 | ||
|  |     } | ||
|  | 
 | ||
|  |   } | ||
|  | 
 | ||
|  |   if (tcp->flags & TCP_URG) { | ||
|  | 
 | ||
|  |     pk.quirks |= QUIRK_URG; | ||
|  | 
 | ||
|  |   } else { | ||
|  | 
 | ||
|  |     if (RD16(tcp->urg)) { | ||
|  | 
 | ||
|  |       DEBUG("[#] Non-zero UPtr on a non-URG packet: 0x%08x.\n", | ||
|  |             ntohl(RD16(tcp->urg))); | ||
|  | 
 | ||
|  |       pk.quirks |= QUIRK_NZ_URG; | ||
|  | 
 | ||
|  |     } | ||
|  | 
 | ||
|  |   } | ||
|  | 
 | ||
|  |   if (tcp->flags & TCP_PUSH) pk.quirks |= QUIRK_PUSH; | ||
|  | 
 | ||
|  |   /* Handle payload data. */ | ||
|  | 
 | ||
|  |   if (tcp_doff == packet_len) { | ||
|  | 
 | ||
|  |     pk.payload = NULL; | ||
|  |     pk.pay_len = 0; | ||
|  | 
 | ||
|  |   } else { | ||
|  | 
 | ||
|  |     pk.payload = (u8*)data + tcp_doff; | ||
|  |     pk.pay_len = packet_len - tcp_doff; | ||
|  | 
 | ||
|  |   } | ||
|  | 
 | ||
|  |   /**********************
 | ||
|  |    * TCP option parsing * | ||
|  |    **********************/ | ||
|  | 
 | ||
|  |   opt_end = (u8*)data + tcp_doff; /* First byte of non-option data */ | ||
|  |   data = (u8*)(tcp + 1); | ||
|  | 
 | ||
|  |   pk.opt_cnt     = 0; | ||
|  |   pk.opt_eol_pad = 0; | ||
|  |   pk.mss         = 0; | ||
|  |   pk.wscale      = 0; | ||
|  |   pk.ts1         = 0; | ||
|  | 
 | ||
|  |   /* Option parsing problems are non-fatal, but we want to keep track of
 | ||
|  |      them to spot buggy TCP stacks. */ | ||
|  | 
 | ||
|  |   while (data < opt_end && pk.opt_cnt < MAX_TCP_OPT) { | ||
|  | 
 | ||
|  |     pk.opt_layout[pk.opt_cnt++] = *data; | ||
|  | 
 | ||
|  |     switch (*data++) { | ||
|  | 
 | ||
|  |       case TCPOPT_EOL: | ||
|  | 
 | ||
|  |         /* EOL is a single-byte option that aborts further option parsing.
 | ||
|  |            Take note of how many bytes of option data are left, and if any of | ||
|  |            them are non-zero. */ | ||
|  | 
 | ||
|  |         pk.opt_eol_pad = opt_end - data; | ||
|  |          | ||
|  |         while (data < opt_end && !*data++); | ||
|  | 
 | ||
|  |         if (data != opt_end) { | ||
|  |           pk.quirks |= QUIRK_OPT_EOL_NZ; | ||
|  |           data = opt_end; | ||
|  |         } | ||
|  | 
 | ||
|  |         break; | ||
|  | 
 | ||
|  |       case TCPOPT_NOP: | ||
|  | 
 | ||
|  |         /* NOP is a single-byte option that does nothing. */ | ||
|  | 
 | ||
|  |         break; | ||
|  |    | ||
|  |       case TCPOPT_MAXSEG: | ||
|  | 
 | ||
|  |         /* MSS is a four-byte option with specified size. */ | ||
|  | 
 | ||
|  |         if (data + 3 > opt_end) { | ||
|  |           DEBUG("[#] MSS option would end past end of header (%u left).\n", | ||
|  |                 opt_end - data); | ||
|  |           goto abort_options; | ||
|  |         } | ||
|  | 
 | ||
|  |         if (*data != 4) { | ||
|  |           DEBUG("[#] MSS option expected to have 4 bytes, not %u.\n", *data); | ||
|  |           pk.quirks |= QUIRK_OPT_BAD; | ||
|  |         } | ||
|  | 
 | ||
|  |         pk.mss = ntohs(RD16p(data+1)); | ||
|  | 
 | ||
|  |         data += 3; | ||
|  | 
 | ||
|  |         break; | ||
|  | 
 | ||
|  |       case TCPOPT_WSCALE: | ||
|  | 
 | ||
|  |         /* WS is a three-byte option with specified size. */ | ||
|  | 
 | ||
|  |         if (data + 2 > opt_end) { | ||
|  |           DEBUG("[#] WS option would end past end of header (%u left).\n", | ||
|  |                 opt_end - data); | ||
|  |           goto abort_options; | ||
|  |         } | ||
|  | 
 | ||
|  |         if (*data != 3) { | ||
|  |           DEBUG("[#] WS option expected to have 3 bytes, not %u.\n", *data); | ||
|  |           pk.quirks |= QUIRK_OPT_BAD; | ||
|  |         } | ||
|  | 
 | ||
|  |         pk.wscale = data[1]; | ||
|  | 
 | ||
|  |         if (pk.wscale > 14) pk.quirks |= QUIRK_OPT_EXWS; | ||
|  | 
 | ||
|  |         data += 2; | ||
|  | 
 | ||
|  |         break; | ||
|  | 
 | ||
|  |       case TCPOPT_SACKOK: | ||
|  | 
 | ||
|  |         /* SACKOK is a two-byte option with specified size. */ | ||
|  | 
 | ||
|  |         if (data + 1 > opt_end) { | ||
|  |           DEBUG("[#] SACKOK option would end past end of header (%u left).\n", | ||
|  |                 opt_end - data); | ||
|  |           goto abort_options; | ||
|  |         } | ||
|  | 
 | ||
|  |         if (*data != 2) { | ||
|  |           DEBUG("[#] SACKOK option expected to have 2 bytes, not %u.\n", *data); | ||
|  |           pk.quirks |= QUIRK_OPT_BAD; | ||
|  |         } | ||
|  | 
 | ||
|  |         data++; | ||
|  | 
 | ||
|  |         break; | ||
|  | 
 | ||
|  |       case TCPOPT_SACK: | ||
|  | 
 | ||
|  |         /* SACK is a variable-length option of 10 to 34 bytes. Because we don't
 | ||
|  |            know the size any better, we need to bail out if it looks wonky. */ | ||
|  | 
 | ||
|  |         if (data == opt_end) { | ||
|  |           DEBUG("[#] SACK option without room for length field."); | ||
|  |           goto abort_options; | ||
|  |         } | ||
|  | 
 | ||
|  |         if (*data < 10 || *data > 34) { | ||
|  |           DEBUG("[#] SACK length out of range (%u), bailing out.\n", *data); | ||
|  |           goto abort_options; | ||
|  |         } | ||
|  | 
 | ||
|  |         if (data - 1 + *data > opt_end) { | ||
|  |           DEBUG("[#] SACK option (len %u) is too long (%u left).\n", | ||
|  |                 *data, opt_end - data); | ||
|  |           goto abort_options; | ||
|  |         } | ||
|  | 
 | ||
|  |         data += *data - 1; | ||
|  | 
 | ||
|  |         break; | ||
|  | 
 | ||
|  |       case TCPOPT_TSTAMP: | ||
|  | 
 | ||
|  |         /* Timestamp is a ten-byte option with specified size. */ | ||
|  | 
 | ||
|  |         if (data + 9 > opt_end) { | ||
|  |           DEBUG("[#] TStamp option would end past end of header (%u left).\n", | ||
|  |                 opt_end - data); | ||
|  |           goto abort_options; | ||
|  |         } | ||
|  | 
 | ||
|  |         if (*data != 10) { | ||
|  |           DEBUG("[#] TStamp option expected to have 10 bytes, not %u.\n", | ||
|  |                 *data); | ||
|  |           pk.quirks |= QUIRK_OPT_BAD; | ||
|  |         } | ||
|  | 
 | ||
|  |         pk.ts1 = ntohl(RD32p(data + 1)); | ||
|  | 
 | ||
|  |         if (!pk.ts1) pk.quirks |= QUIRK_OPT_ZERO_TS1; | ||
|  | 
 | ||
|  |         if (pk.tcp_type == TCP_SYN && RD32p(data + 5)) { | ||
|  | 
 | ||
|  |           DEBUG("[#] Non-zero second timestamp: 0x%08x.\n", | ||
|  |                 ntohl(*(u32*)(data + 5))); | ||
|  | 
 | ||
|  |           pk.quirks |= QUIRK_OPT_NZ_TS2; | ||
|  | 
 | ||
|  |         } | ||
|  | 
 | ||
|  |         data += 9; | ||
|  | 
 | ||
|  |         break; | ||
|  | 
 | ||
|  |       default: | ||
|  | 
 | ||
|  |         /* Unknown option, presumably with specified size. */ | ||
|  | 
 | ||
|  |         if (data == opt_end) { | ||
|  |           DEBUG("[#] Unknown option 0x%02x without room for length field.", | ||
|  |                 data[-1]); | ||
|  |           goto abort_options; | ||
|  |         } | ||
|  | 
 | ||
|  |         if (*data < 2 || *data > 40) { | ||
|  |           DEBUG("[#] Unknown option 0x%02x has invalid length %u.\n", | ||
|  |                 data[-1], *data); | ||
|  |           goto abort_options; | ||
|  |         } | ||
|  | 
 | ||
|  |         if (data - 1 + *data > opt_end) { | ||
|  |           DEBUG("[#] Unknown option 0x%02x (len %u) is too long (%u left).\n", | ||
|  |                 data[-1], *data, opt_end - data); | ||
|  |           goto abort_options; | ||
|  |         } | ||
|  | 
 | ||
|  |         data += *data - 1; | ||
|  | 
 | ||
|  |     } | ||
|  | 
 | ||
|  |   } | ||
|  | 
 | ||
|  |   if (data != opt_end) { | ||
|  | 
 | ||
|  | abort_options: | ||
|  | 
 | ||
|  |     DEBUG("[#] Option parsing aborted (cnt = %u, remainder = %u).\n", | ||
|  |           pk.opt_cnt, opt_end - data); | ||
|  | 
 | ||
|  |     pk.quirks |= QUIRK_OPT_BAD; | ||
|  | 
 | ||
|  |   } | ||
|  | 
 | ||
|  |   flow_dispatch(&pk); | ||
|  | 
 | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | /* Calculate hash bucket for packet_flow. Keep the hash symmetrical: switching
 | ||
|  |    source and dest should have no effect. */ | ||
|  | 
 | ||
|  | static u32 get_flow_bucket(struct packet_data* pk) { | ||
|  | 
 | ||
|  |   u32 bucket; | ||
|  | 
 | ||
|  |   if (pk->ip_ver == IP_VER4) { | ||
|  |     bucket = hash32(pk->src, 4, hash_seed) ^ hash32(pk->dst, 4, hash_seed); | ||
|  |   } else { | ||
|  |     bucket = hash32(pk->src, 16, hash_seed) ^ hash32(pk->dst, 16, hash_seed); | ||
|  |   } | ||
|  | 
 | ||
|  |   bucket ^= hash32(&pk->sport, 2, hash_seed) ^ hash32(&pk->dport, 2, hash_seed); | ||
|  | 
 | ||
|  |   return bucket % FLOW_BUCKETS; | ||
|  | 
 | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | /* Calculate hash bucket for host_data. */ | ||
|  | 
 | ||
|  | static u32 get_host_bucket(u8* addr, u8 ip_ver) { | ||
|  | 
 | ||
|  |   u32 bucket; | ||
|  | 
 | ||
|  |   bucket = hash32(addr, (ip_ver == IP_VER4) ? 4 : 16, hash_seed); | ||
|  | 
 | ||
|  |   return bucket % HOST_BUCKETS; | ||
|  | 
 | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | /* Look up host data. */ | ||
|  | 
 | ||
|  | struct host_data* lookup_host(u8* addr, u8 ip_ver) { | ||
|  | 
 | ||
|  |   u32 bucket = get_host_bucket(addr, ip_ver); | ||
|  |   struct host_data* h = host_b[bucket]; | ||
|  | 
 | ||
|  |   while (CP(h)) { | ||
|  | 
 | ||
|  |     if (ip_ver == h->ip_ver && | ||
|  |         !memcmp(addr, h->addr, (h->ip_ver == IP_VER4) ? 4 : 16)) | ||
|  |       return h; | ||
|  | 
 | ||
|  |     h = h->next; | ||
|  | 
 | ||
|  |   } | ||
|  | 
 | ||
|  |   return NULL; | ||
|  | 
 | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | /* Destroy host data. */ | ||
|  | 
 | ||
|  | static void destroy_host(struct host_data* h) { | ||
|  | 
 | ||
|  |   u32 bucket;  | ||
|  | 
 | ||
|  |   bucket = get_host_bucket(CP(h)->addr, h->ip_ver); | ||
|  | 
 | ||
|  |   if (h->use_cnt) FATAL("Attempt to destroy used host data."); | ||
|  | 
 | ||
|  |   DEBUG("[#] Destroying host data: %s (bucket %d)\n", | ||
|  |         addr_to_str(h->addr, h->ip_ver), bucket); | ||
|  | 
 | ||
|  |   /* Remove it from the bucketed linked list. */ | ||
|  | 
 | ||
|  |   if (CP(h->next)) h->next->prev = h->prev; | ||
|  |    | ||
|  |   if (CP(h->prev)) h->prev->next = h->next; | ||
|  |   else host_b[bucket] = h->next; | ||
|  | 
 | ||
|  |   /* Remove from the by-age linked list. */ | ||
|  | 
 | ||
|  |   if (CP(h->newer)) h->newer->older = h->older; | ||
|  |   else newest_host = h->older; | ||
|  | 
 | ||
|  |   if (CP(h->older)) h->older->newer = h->newer; | ||
|  |   else host_by_age = h->newer;  | ||
|  | 
 | ||
|  |   /* Free memory. */ | ||
|  | 
 | ||
|  |   ck_free(h->last_syn); | ||
|  |   ck_free(h->last_synack); | ||
|  | 
 | ||
|  |   ck_free(h->http_resp); | ||
|  |   ck_free(h->http_req_os); | ||
|  | 
 | ||
|  |   ck_free(h); | ||
|  | 
 | ||
|  |   host_cnt--; | ||
|  | 
 | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | /* Indiscriminately kill some of the older hosts. */ | ||
|  | 
 | ||
|  | static void nuke_hosts(void) { | ||
|  | 
 | ||
|  |   u32 kcnt = 1 + (host_cnt * KILL_PERCENT / 100); | ||
|  |   struct host_data* target = host_by_age; | ||
|  | 
 | ||
|  |   if (!read_file) | ||
|  |     WARN("Too many host entries, deleting %u. Use -m to adjust.", kcnt); | ||
|  | 
 | ||
|  |   nuke_flows(1); | ||
|  | 
 | ||
|  |   while (kcnt && CP(target)) { | ||
|  |     struct host_data* next = target->older; | ||
|  |     if (!target->use_cnt) { kcnt--; destroy_host(target); } | ||
|  |     target = next; | ||
|  |   } | ||
|  | 
 | ||
|  | } | ||
|  |    | ||
|  | 
 | ||
|  | 
 | ||
|  | /* Create a minimal host data. */ | ||
|  | 
 | ||
|  | static struct host_data* create_host(u8* addr, u8 ip_ver) { | ||
|  | 
 | ||
|  |   u32 bucket = get_host_bucket(addr, ip_ver); | ||
|  |   struct host_data* nh; | ||
|  | 
 | ||
|  |   if (host_cnt > max_hosts) nuke_hosts(); | ||
|  | 
 | ||
|  |   DEBUG("[#] Creating host data: %s (bucket %u)\n", | ||
|  |         addr_to_str(addr, ip_ver), bucket); | ||
|  | 
 | ||
|  |   nh = ck_alloc(sizeof(struct host_data)); | ||
|  | 
 | ||
|  |   /* Insert into the bucketed linked list. */ | ||
|  | 
 | ||
|  |   if (CP(host_b[bucket])) { | ||
|  |     host_b[bucket]->prev = nh; | ||
|  |     nh->next = host_b[bucket]; | ||
|  |   } | ||
|  | 
 | ||
|  |   host_b[bucket] = nh; | ||
|  | 
 | ||
|  |   /* Insert into the by-age linked list. */ | ||
|  |   | ||
|  |   if (CP(newest_host)) { | ||
|  | 
 | ||
|  |     newest_host->newer = nh; | ||
|  |     nh->older = newest_host; | ||
|  | 
 | ||
|  |   } else host_by_age = nh; | ||
|  | 
 | ||
|  |   newest_host = nh; | ||
|  | 
 | ||
|  |   /* Populate other data. */ | ||
|  | 
 | ||
|  |   nh->ip_ver = ip_ver; | ||
|  |   memcpy(nh->addr, addr, (ip_ver == IP_VER4) ? 4 : 16); | ||
|  | 
 | ||
|  |   nh->last_seen = nh->first_seen = get_unix_time(); | ||
|  | 
 | ||
|  |   nh->last_up_min     = -1; | ||
|  |   nh->last_class_id   = -1; | ||
|  |   nh->last_name_id    = -1; | ||
|  |   nh->http_name_id    = -1; | ||
|  |   nh->distance        = -1; | ||
|  | 
 | ||
|  |   host_cnt++; | ||
|  | 
 | ||
|  |   return nh; | ||
|  | 
 | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | /* Touch host data to make it more recent. */ | ||
|  | 
 | ||
|  | static void touch_host(struct host_data* h) { | ||
|  | 
 | ||
|  |   CP(h); | ||
|  | 
 | ||
|  |   DEBUG("[#] Refreshing host data: %s\n", addr_to_str(h->addr, h->ip_ver)); | ||
|  | 
 | ||
|  |   if (h != CP(newest_host)) { | ||
|  | 
 | ||
|  |     /* Remove from the the by-age linked list. */ | ||
|  | 
 | ||
|  |     CP(h->newer); | ||
|  |     h->newer->older = h->older; | ||
|  | 
 | ||
|  |     if (CP(h->older)) h->older->newer = h->newer; | ||
|  |     else host_by_age = h->newer;  | ||
|  | 
 | ||
|  |     /* Re-insert in front. */ | ||
|  | 
 | ||
|  |     newest_host->newer = h; | ||
|  |     h->older = newest_host; | ||
|  |     h->newer = NULL; | ||
|  | 
 | ||
|  |     newest_host = h; | ||
|  | 
 | ||
|  |     /* This wasn't the only entry on the list, so there is no
 | ||
|  |        need to update the tail (host_by_age). */ | ||
|  | 
 | ||
|  |   } | ||
|  | 
 | ||
|  |   /* Update last seen time. */ | ||
|  | 
 | ||
|  |   h->last_seen = get_unix_time(); | ||
|  | 
 | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | 
 | ||
|  | /* Destroy a flow. */ | ||
|  | 
 | ||
|  | static void destroy_flow(struct packet_flow* f) { | ||
|  | 
 | ||
|  |   CP(f); | ||
|  |   CP(f->client); | ||
|  |   CP(f->server); | ||
|  | 
 | ||
|  |   DEBUG("[#] Destroying flow: %s/%u -> ", | ||
|  |         addr_to_str(f->client->addr, f->client->ip_ver), f->cli_port); | ||
|  | 
 | ||
|  |   DEBUG("%s/%u (bucket %u)\n", | ||
|  |         addr_to_str(f->server->addr, f->server->ip_ver), f->srv_port, | ||
|  |         f->bucket); | ||
|  | 
 | ||
|  |   /* Remove it from the bucketed linked list. */ | ||
|  | 
 | ||
|  |   if (CP(f->next)) f->next->prev = f->prev; | ||
|  |    | ||
|  |   if (CP(f->prev)) f->prev->next = f->next; | ||
|  |   else { CP(flow_b[f->bucket]); flow_b[f->bucket] = f->next; } | ||
|  | 
 | ||
|  |   /* Remove from the by-age linked list. */ | ||
|  | 
 | ||
|  |   if (CP(f->newer)) f->newer->older = f->older; | ||
|  |   else { CP(newest_flow); newest_flow = f->older; } | ||
|  | 
 | ||
|  |   if (CP(f->older)) f->older->newer = f->newer; | ||
|  |   else flow_by_age = f->newer;  | ||
|  | 
 | ||
|  |   /* Free memory, etc. */ | ||
|  | 
 | ||
|  |   f->client->use_cnt--; | ||
|  |   f->server->use_cnt--; | ||
|  | 
 | ||
|  |   free_sig_hdrs(&f->http_tmp); | ||
|  | 
 | ||
|  |   ck_free(f->request); | ||
|  |   ck_free(f->response); | ||
|  |   ck_free(f); | ||
|  | 
 | ||
|  |   flow_cnt--;   | ||
|  | 
 | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | /* Indiscriminately kill some of the oldest flows. */ | ||
|  | 
 | ||
|  | static void nuke_flows(u8 silent) { | ||
|  | 
 | ||
|  |   u32 kcnt = 1 + (flow_cnt * KILL_PERCENT / 100); | ||
|  | 
 | ||
|  |   if (silent) | ||
|  |     DEBUG("[#] Pruning connections - trying to delete %u...\n",kcnt); | ||
|  |   else if (!read_file) | ||
|  |     WARN("Too many tracked connections, deleting %u. " | ||
|  |          "Use -m to adjust.", kcnt); | ||
|  | 
 | ||
|  |   while (kcnt-- && flow_by_age) destroy_flow(flow_by_age); | ||
|  | 
 | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | 
 | ||
|  | /* Create flow, and host data if necessary. If counts exceeded, prune old. */ | ||
|  | 
 | ||
|  | static struct packet_flow* create_flow_from_syn(struct packet_data* pk) { | ||
|  | 
 | ||
|  |   u32 bucket = get_flow_bucket(pk); | ||
|  |   struct packet_flow* nf; | ||
|  | 
 | ||
|  |   if (flow_cnt > max_conn) nuke_flows(0); | ||
|  | 
 | ||
|  |   DEBUG("[#] Creating flow from SYN: %s/%u -> ", | ||
|  |         addr_to_str(pk->src, pk->ip_ver), pk->sport); | ||
|  | 
 | ||
|  |   DEBUG("%s/%u (bucket %u)\n", | ||
|  |         addr_to_str(pk->dst, pk->ip_ver), pk->dport, bucket); | ||
|  | 
 | ||
|  |   nf = ck_alloc(sizeof(struct packet_flow)); | ||
|  | 
 | ||
|  |   nf->client = lookup_host(pk->src, pk->ip_ver); | ||
|  | 
 | ||
|  |   if (nf->client) touch_host(nf->client); | ||
|  |   else nf->client = create_host(pk->src, pk->ip_ver); | ||
|  | 
 | ||
|  |   nf->server = lookup_host(pk->dst, pk->ip_ver); | ||
|  | 
 | ||
|  |   if (nf->server) touch_host(nf->server); | ||
|  |   else nf->server = create_host(pk->dst, pk->ip_ver); | ||
|  | 
 | ||
|  |   nf->client->use_cnt++; | ||
|  |   nf->server->use_cnt++; | ||
|  | 
 | ||
|  |   nf->client->total_conn++; | ||
|  |   nf->server->total_conn++; | ||
|  | 
 | ||
|  |   /* Insert into the bucketed linked list.*/ | ||
|  | 
 | ||
|  |   if (CP(flow_b[bucket])) { | ||
|  |     flow_b[bucket]->prev = nf; | ||
|  |     nf->next = flow_b[bucket]; | ||
|  |   } | ||
|  | 
 | ||
|  |   flow_b[bucket] = nf; | ||
|  | 
 | ||
|  |   /* Insert into the by-age linked list */ | ||
|  |   | ||
|  |   if (CP(newest_flow)) { | ||
|  |     newest_flow->newer = nf; | ||
|  |     nf->older = newest_flow; | ||
|  |   } else flow_by_age = nf; | ||
|  | 
 | ||
|  |   newest_flow = nf; | ||
|  | 
 | ||
|  |   /* Populate other data */ | ||
|  | 
 | ||
|  |   nf->cli_port = pk->sport; | ||
|  |   nf->srv_port = pk->dport; | ||
|  |   nf->bucket   = bucket; | ||
|  |   nf->created  = get_unix_time(); | ||
|  | 
 | ||
|  |   nf->next_cli_seq = pk->seq + 1; | ||
|  | 
 | ||
|  |   flow_cnt++; | ||
|  |   return nf; | ||
|  | 
 | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | /* Look up an existing flow. */ | ||
|  | 
 | ||
|  | static struct packet_flow* lookup_flow(struct packet_data* pk, u8* to_srv) { | ||
|  | 
 | ||
|  |   u32 bucket = get_flow_bucket(pk); | ||
|  |   struct packet_flow* f = flow_b[bucket]; | ||
|  | 
 | ||
|  |   while (CP(f)) { | ||
|  | 
 | ||
|  |     CP(f->client); | ||
|  |     CP(f->server); | ||
|  | 
 | ||
|  |     if (pk->ip_ver != f->client->ip_ver) goto lookup_next; | ||
|  | 
 | ||
|  |     if (pk->sport == f->cli_port && pk->dport == f->srv_port && | ||
|  |         !memcmp(pk->src, f->client->addr, (pk->ip_ver == IP_VER4) ? 4 : 16) && | ||
|  |         !memcmp(pk->dst, f->server->addr, (pk->ip_ver == IP_VER4) ? 4 : 16)) { | ||
|  | 
 | ||
|  |       *to_srv = 1; | ||
|  |       return f; | ||
|  | 
 | ||
|  |     } | ||
|  | 
 | ||
|  |     if (pk->dport == f->cli_port && pk->sport == f->srv_port && | ||
|  |         !memcmp(pk->dst, f->client->addr, (pk->ip_ver == IP_VER4) ? 4 : 16) && | ||
|  |         !memcmp(pk->src, f->server->addr, (pk->ip_ver == IP_VER4) ? 4 : 16)) { | ||
|  | 
 | ||
|  |       *to_srv = 0; | ||
|  |       return f; | ||
|  | 
 | ||
|  |     } | ||
|  | 
 | ||
|  | lookup_next: | ||
|  | 
 | ||
|  |     f = f->next; | ||
|  | 
 | ||
|  |   } | ||
|  | 
 | ||
|  |   return NULL; | ||
|  | 
 | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | /* Go through host and flow cache, expire outdated items. */ | ||
|  | 
 | ||
|  | static void expire_cache(void) { | ||
|  |   struct host_data* target; | ||
|  |   static u32 pt; | ||
|  | 
 | ||
|  |   u32 ct = get_unix_time(); | ||
|  | 
 | ||
|  |   if (ct == pt) return; | ||
|  |   pt = ct; | ||
|  | 
 | ||
|  |   DEBUG("[#] Cache expiration kicks in...\n"); | ||
|  | 
 | ||
|  |   while (CP(flow_by_age) && ct - flow_by_age->created > conn_max_age) | ||
|  |     destroy_flow(flow_by_age); | ||
|  | 
 | ||
|  |   target = host_by_age; | ||
|  | 
 | ||
|  |   while (CP(target) && ct - target->last_seen > host_idle_limit * 60) { | ||
|  |     struct host_data* newer = target->newer; | ||
|  |     if (!target->use_cnt) destroy_host(target); | ||
|  |     target = newer; | ||
|  |   } | ||
|  | 
 | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | /* Insert data from a packet into a flow, call handlers as appropriate. */ | ||
|  | 
 | ||
|  | static void flow_dispatch(struct packet_data* pk) { | ||
|  | 
 | ||
|  |   struct packet_flow* f; | ||
|  |   struct tcp_sig* tsig; | ||
|  |   u8 to_srv = 0; | ||
|  |   u8 need_more = 0; | ||
|  | 
 | ||
|  |   DEBUG("[#] Received TCP packet: %s/%u -> ", | ||
|  |         addr_to_str(pk->src, pk->ip_ver), pk->sport); | ||
|  | 
 | ||
|  |   DEBUG("%s/%u (type 0x%02x, pay_len = %u)\n", | ||
|  |         addr_to_str(pk->dst, pk->ip_ver), pk->dport, pk->tcp_type, | ||
|  |         pk->pay_len); | ||
|  |      | ||
|  |   f = lookup_flow(pk, &to_srv); | ||
|  | 
 | ||
|  |   switch (pk->tcp_type) { | ||
|  | 
 | ||
|  |     case TCP_SYN: | ||
|  | 
 | ||
|  |       if (f) { | ||
|  | 
 | ||
|  |         /* Perhaps just a simple dupe? */ | ||
|  |         if (to_srv && f->next_cli_seq - 1 == pk->seq) return; | ||
|  | 
 | ||
|  |         DEBUG("[#] New SYN for an existing flow, resetting.\n"); | ||
|  |         destroy_flow(f); | ||
|  | 
 | ||
|  |       } | ||
|  | 
 | ||
|  |       f = create_flow_from_syn(pk); | ||
|  | 
 | ||
|  |       tsig = fingerprint_tcp(1, pk, f); | ||
|  | 
 | ||
|  |       /* We don't want to do any further processing on generic non-OS
 | ||
|  |          signatures (e.g. NMap). The easiest way to guarantee that is to  | ||
|  |          kill the flow. */ | ||
|  | 
 | ||
|  |       if (!tsig && !f->sendsyn) { | ||
|  | 
 | ||
|  |         destroy_flow(f); | ||
|  |         return; | ||
|  | 
 | ||
|  |       } | ||
|  | 
 | ||
|  |       fingerprint_mtu(1, pk, f); | ||
|  |       check_ts_tcp(1, pk, f); | ||
|  | 
 | ||
|  |       if (tsig) { | ||
|  | 
 | ||
|  |         /* This can't be done in fingerprint_tcp because check_ts_tcp()
 | ||
|  |            depends on having original SYN / SYN+ACK data. */ | ||
|  |   | ||
|  |         ck_free(f->client->last_syn); | ||
|  |         f->client->last_syn = tsig; | ||
|  | 
 | ||
|  |       } | ||
|  | 
 | ||
|  |       break; | ||
|  | 
 | ||
|  |     case TCP_SYN | TCP_ACK: | ||
|  | 
 | ||
|  |       if (!f) { | ||
|  | 
 | ||
|  |         DEBUG("[#] Stray SYN+ACK with no flow.\n"); | ||
|  |         return; | ||
|  | 
 | ||
|  |       } | ||
|  | 
 | ||
|  |       /* This is about as far as we want to go with p0f-sendsyn. */ | ||
|  | 
 | ||
|  |       if (f->sendsyn) { | ||
|  | 
 | ||
|  |         fingerprint_tcp(0, pk, f); | ||
|  |         destroy_flow(f); | ||
|  |         return; | ||
|  | 
 | ||
|  |       } | ||
|  | 
 | ||
|  | 
 | ||
|  |       if (to_srv) { | ||
|  | 
 | ||
|  |         DEBUG("[#] SYN+ACK from client to server, trippy.\n"); | ||
|  |         return; | ||
|  | 
 | ||
|  |       } | ||
|  | 
 | ||
|  |       if (f->acked) { | ||
|  | 
 | ||
|  |         if (f->next_srv_seq - 1 != pk->seq) | ||
|  |           DEBUG("[#] Repeated but non-identical SYN+ACK (0x%08x != 0x%08x).\n", | ||
|  |                 f->next_srv_seq - 1, pk->seq); | ||
|  | 
 | ||
|  |         return; | ||
|  | 
 | ||
|  |       } | ||
|  | 
 | ||
|  |       f->acked = 1; | ||
|  | 
 | ||
|  |       tsig = fingerprint_tcp(0, pk, f); | ||
|  | 
 | ||
|  |       /* SYN from real OS, SYN+ACK from a client stack. Weird, but whatever. */ | ||
|  | 
 | ||
|  |       if (!tsig) { | ||
|  | 
 | ||
|  |         destroy_flow(f); | ||
|  |         return; | ||
|  | 
 | ||
|  |       } | ||
|  | 
 | ||
|  |       fingerprint_mtu(0, pk, f); | ||
|  |       check_ts_tcp(0, pk, f); | ||
|  | 
 | ||
|  |       ck_free(f->server->last_synack); | ||
|  |       f->server->last_synack = tsig; | ||
|  | 
 | ||
|  |       f->next_srv_seq = pk->seq + 1; | ||
|  | 
 | ||
|  |       break; | ||
|  | 
 | ||
|  |     case TCP_RST | TCP_ACK: | ||
|  |     case TCP_RST: | ||
|  |     case TCP_FIN | TCP_ACK: | ||
|  |     case TCP_FIN: | ||
|  | 
 | ||
|  |        if (f) { | ||
|  | 
 | ||
|  |          check_ts_tcp(to_srv, pk, f); | ||
|  |          destroy_flow(f); | ||
|  | 
 | ||
|  |        } | ||
|  | 
 | ||
|  |        break; | ||
|  | 
 | ||
|  |     case TCP_ACK: | ||
|  | 
 | ||
|  |       if (!f) return; | ||
|  | 
 | ||
|  |       /* Stop there, you criminal scum! */ | ||
|  | 
 | ||
|  |       if (f->sendsyn) { | ||
|  |         destroy_flow(f); | ||
|  |         return; | ||
|  |       } | ||
|  | 
 | ||
|  |       if (!f->acked) { | ||
|  | 
 | ||
|  |         DEBUG("[#] Never received SYN+ACK to complete handshake, huh.\n"); | ||
|  |         destroy_flow(f); | ||
|  |         return; | ||
|  | 
 | ||
|  |       } | ||
|  | 
 | ||
|  |       if (to_srv) { | ||
|  | 
 | ||
|  |         /* We don't do stream reassembly, so if something arrives out of order,
 | ||
|  |            we won't catch it. Oh well. */ | ||
|  | 
 | ||
|  |         if (f->next_cli_seq != pk->seq) { | ||
|  | 
 | ||
|  |           /* Not a simple dupe? */ | ||
|  | 
 | ||
|  |           if (f->next_cli_seq - pk->pay_len != pk->seq) | ||
|  |             DEBUG("[#] Expected client seq 0x%08x, got 0x%08x.\n", f->next_cli_seq, pk->seq); | ||
|  |   | ||
|  |           return; | ||
|  |         } | ||
|  | 
 | ||
|  |         /* Append data */ | ||
|  | 
 | ||
|  |         if (f->req_len < MAX_FLOW_DATA && pk->pay_len) { | ||
|  | 
 | ||
|  |           u32 read_amt = MIN(pk->pay_len, MAX_FLOW_DATA - f->req_len); | ||
|  | 
 | ||
|  |           f->request = ck_realloc_kb(f->request, f->req_len + read_amt + 1); | ||
|  |           memcpy(f->request + f->req_len, pk->payload, read_amt); | ||
|  |           f->req_len += read_amt; | ||
|  | 
 | ||
|  |         } | ||
|  | 
 | ||
|  |         check_ts_tcp(1, pk, f); | ||
|  | 
 | ||
|  |         f->next_cli_seq += pk->pay_len; | ||
|  | 
 | ||
|  |       } else { | ||
|  | 
 | ||
|  |         if (f->next_srv_seq != pk->seq) { | ||
|  | 
 | ||
|  |           /* Not a simple dupe? */ | ||
|  | 
 | ||
|  |           if (f->next_srv_seq - pk->pay_len != pk->seq) | ||
|  |             DEBUG("[#] Expected server seq 0x%08x, got 0x%08x.\n", | ||
|  |                   f->next_cli_seq, pk->seq); | ||
|  |   | ||
|  |           return; | ||
|  | 
 | ||
|  |         } | ||
|  | 
 | ||
|  |         /* Append data */ | ||
|  | 
 | ||
|  |         if (f->resp_len < MAX_FLOW_DATA && pk->pay_len) { | ||
|  | 
 | ||
|  |           u32 read_amt = MIN(pk->pay_len, MAX_FLOW_DATA - f->resp_len); | ||
|  | 
 | ||
|  |           f->response = ck_realloc_kb(f->response, f->resp_len + read_amt + 1); | ||
|  |           memcpy(f->response + f->resp_len, pk->payload, read_amt); | ||
|  |           f->resp_len += read_amt; | ||
|  | 
 | ||
|  |         } | ||
|  | 
 | ||
|  |         check_ts_tcp(0, pk, f); | ||
|  | 
 | ||
|  |         f->next_srv_seq += pk->pay_len; | ||
|  | 
 | ||
|  |       } | ||
|  | 
 | ||
|  |       if (!pk->pay_len) return; | ||
|  | 
 | ||
|  |       need_more |= process_http(to_srv, f); | ||
|  | 
 | ||
|  |       if (!need_more) { | ||
|  | 
 | ||
|  |         DEBUG("[#] All modules done, no need to keep tracking flow.\n"); | ||
|  |         destroy_flow(f); | ||
|  | 
 | ||
|  |       } else if (f->req_len >= MAX_FLOW_DATA && f->resp_len >= MAX_FLOW_DATA) { | ||
|  | 
 | ||
|  |         DEBUG("[#] Per-flow capture size limit exceeded.\n"); | ||
|  |         destroy_flow(f); | ||
|  | 
 | ||
|  |       } | ||
|  | 
 | ||
|  |       break; | ||
|  | 
 | ||
|  |     default: | ||
|  | 
 | ||
|  |       WARN("Huh. Unexpected packet type 0x%02x in flow_dispatch().", pk->tcp_type); | ||
|  | 
 | ||
|  |   } | ||
|  | 
 | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | /* Add NAT score, check if alarm due. */ | ||
|  | 
 | ||
|  | void add_nat_score(u8 to_srv, struct packet_flow* f, u16 reason, u8 score) { | ||
|  | 
 | ||
|  |   static u8 rea[1024]; | ||
|  | 
 | ||
|  |   struct host_data* hd; | ||
|  |   u8 *scores, *rptr = rea; | ||
|  |   u32 i; | ||
|  |   u8  over_5 = 0, over_2 = 0, over_1 = 0, over_0 = 0; | ||
|  | 
 | ||
|  |   if (to_srv) { | ||
|  | 
 | ||
|  |     hd = f->client; | ||
|  |     scores = hd->cli_scores; | ||
|  | 
 | ||
|  |   } else { | ||
|  | 
 | ||
|  |     hd = f->server; | ||
|  |     scores = hd->srv_scores; | ||
|  | 
 | ||
|  |   } | ||
|  | 
 | ||
|  |   memmove(scores, scores + 1, NAT_SCORES - 1); | ||
|  |   scores[NAT_SCORES - 1] = score; | ||
|  |   hd->nat_reasons |= reason; | ||
|  | 
 | ||
|  |   if (!score) return; | ||
|  | 
 | ||
|  |   for (i = 0; i < NAT_SCORES; i++) switch (scores[i]) { | ||
|  |     case 6 ... 255: over_5++; | ||
|  |     case 3 ... 5:   over_2++; | ||
|  |     case 2:         over_1++; | ||
|  |     case 1:         over_0++; | ||
|  |   } | ||
|  | 
 | ||
|  |   if (over_5 > 2 || over_2 > 4 || over_1 > 6 || over_0 > 8) { | ||
|  | 
 | ||
|  |     start_observation("ip sharing", 2, to_srv, f); | ||
|  | 
 | ||
|  |     reason = hd->nat_reasons; | ||
|  | 
 | ||
|  |     hd->last_nat = get_unix_time(); | ||
|  | 
 | ||
|  |     memset(scores, 0, NAT_SCORES); | ||
|  |     hd->nat_reasons = 0; | ||
|  | 
 | ||
|  |   } else { | ||
|  | 
 | ||
|  |     /* Wait for something more substantial. */ | ||
|  |     if (score == 1) return; | ||
|  | 
 | ||
|  |     start_observation("host change", 2, to_srv, f); | ||
|  | 
 | ||
|  |     hd->last_chg = get_unix_time(); | ||
|  | 
 | ||
|  |   } | ||
|  | 
 | ||
|  |   *rptr = 0; | ||
|  | 
 | ||
|  | #define REAF(_par...) do { \
 | ||
|  |     rptr += sprintf((char*)rptr, _par); \ | ||
|  |   } while (0)  | ||
|  | 
 | ||
|  |   if (reason & NAT_APP_SIG)  REAF(" app_vs_os"); | ||
|  |   if (reason & NAT_OS_SIG)   REAF(" os_diff"); | ||
|  |   if (reason & NAT_UNK_DIFF) REAF(" sig_diff"); | ||
|  |   if (reason & NAT_TO_UNK)   REAF(" x_known"); | ||
|  |   if (reason & NAT_TS)       REAF(" tstamp"); | ||
|  |   if (reason & NAT_TTL)      REAF(" ttl"); | ||
|  |   if (reason & NAT_PORT)     REAF(" port"); | ||
|  |   if (reason & NAT_MSS)      REAF(" mtu"); | ||
|  |   if (reason & NAT_FUZZY)    REAF(" fuzzy"); | ||
|  | 
 | ||
|  |   if (reason & NAT_APP_VIA)  REAF(" via"); | ||
|  |   if (reason & NAT_APP_DATE) REAF(" date"); | ||
|  |   if (reason & NAT_APP_LB)   REAF(" srv_sig_lb"); | ||
|  |   if (reason & NAT_APP_UA)   REAF(" ua_vs_os"); | ||
|  | 
 | ||
|  | #undef REAF
 | ||
|  | 
 | ||
|  |   add_observation_field("reason", rea[0] ? (rea + 1) : NULL); | ||
|  | 
 | ||
|  |   OBSERVF("raw_hits", "%u,%u,%u,%u", over_5, over_2, over_1, over_0); | ||
|  | 
 | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | /* Verify if tool class (called from modules). */ | ||
|  | 
 | ||
|  | void verify_tool_class(u8 to_srv, struct packet_flow* f, u32* sys, u32 sys_cnt) { | ||
|  | 
 | ||
|  |   struct host_data* hd; | ||
|  |   u32 i; | ||
|  | 
 | ||
|  |   if (to_srv) hd = f->client; else hd = f->server; | ||
|  | 
 | ||
|  |   CP(sys); | ||
|  | 
 | ||
|  |   /* No existing data; although there is perhaps some value in detecting
 | ||
|  |      app-only conflicts in absence of other info, it's probably OK to just | ||
|  |      wait until more data becomes available. */ | ||
|  | 
 | ||
|  |   if (hd->last_class_id == -1) return; | ||
|  | 
 | ||
|  |   for (i = 0; i < sys_cnt; i++) | ||
|  | 
 | ||
|  |     if ((sys[i] & SYS_CLASS_FLAG)) { | ||
|  | 
 | ||
|  |       if (SYS_NF(sys[i]) == hd->last_class_id) break; | ||
|  | 
 | ||
|  |     } else { | ||
|  | 
 | ||
|  |       if (SYS_NF(sys[i]) == hd->last_name_id) break; | ||
|  | 
 | ||
|  |     } | ||
|  | 
 | ||
|  |   /* Oops, a mismatch. */ | ||
|  | 
 | ||
|  |   if (i == sys_cnt) { | ||
|  | 
 | ||
|  |     DEBUG("[#] Detected app not supposed to run on host OS.\n"); | ||
|  |     add_nat_score(to_srv, f, NAT_APP_SIG, 4); | ||
|  | 
 | ||
|  |   } else { | ||
|  | 
 | ||
|  |     DEBUG("[#] Detected app supported on host OS.\n"); | ||
|  |     add_nat_score(to_srv, f, 0, 0); | ||
|  | 
 | ||
|  |   } | ||
|  | 
 | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | /* Clean up everything. */ | ||
|  | 
 | ||
|  | void destroy_all_hosts(void) { | ||
|  | 
 | ||
|  |   while (flow_by_age) destroy_flow(flow_by_age); | ||
|  |   while (host_by_age) destroy_host(host_by_age); | ||
|  | 
 | ||
|  | } |