mirror of
				https://github.com/telekom-security/tpotce.git
				synced 2025-11-03 22:12:53 +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);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								}
							 |