/*
  Copyright (c) 1999 Rafal Wojtczuk <nergal@avet.com.pl>. All rights reserved.
  See the file COPYING for license details.
 */

#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifndef WIN32
#include <unistd.h>
#include <netinet/in.h>
#else
#include <winsock.h>
#endif
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <netinet/ip_icmp.h>

#include "checksum.h"
#include "scan.h"
#include "tcp.h"
#include "util.h"
#include "nids.h"

#ifndef __linux__
enum {
  TCP_ESTABLISHED = 1,
  TCP_SYN_SENT,
  TCP_SYN_RECV,
  TCP_FIN_WAIT1,
  TCP_FIN_WAIT2,
  TCP_TIME_WAIT,
  TCP_CLOSE,
  TCP_CLOSE_WAIT,
  TCP_LAST_ACK,
  TCP_LISTEN,
  TCP_CLOSING			/* now a valid state */
};

#define ICMP_DEST_UNREACH ICMP_UNREACH
#define ICMP_PROT_UNREACH ICMP_UNREACH_PROTOCOL
#define ICMP_PORT_UNREACH ICMP_UNREACH_PORT
#define NR_ICMP_UNREACH   ICMP_MAXTYPE

#endif				/* !linux */

#define FIN_SENT 120
#define FIN_CONFIRMED 121
#define COLLECT_cc 1
#define COLLECT_sc 2
#define COLLECT_ccu 4
#define COLLECT_scu 8

#define EXP_SEQ (snd->first_data_seq + rcv->count + rcv->urg_count)

extern struct proc_node *tcp_procs;

static struct tcp_stream **tcp_stream_table;
static struct tcp_stream *streams_pool;
static int tcp_num = 0;
static int tcp_stream_table_size;
static int hash_deg;
static int max_stream;
static struct tcp_stream *tcp_latest = 0, *tcp_oldest = 0;
static struct tcp_stream *free_streams;
static struct ip *ugly_iphdr;

static void
free_tcp(struct tcp_stream * a_tcp)
{
  int hash_index = a_tcp->hash_index;
  struct lurker_node *i, *j;
  
  if (a_tcp->next_node)
    a_tcp->next_node->prev_node = a_tcp->prev_node;
  if (a_tcp->prev_node)
    a_tcp->prev_node->next_node = a_tcp->next_node;
  else
    tcp_stream_table[hash_index] = a_tcp->next_node;
  if (a_tcp->client.data)
    free(a_tcp->client.data);
  if (a_tcp->server.data)
    free(a_tcp->server.data);
  if (a_tcp->next_time)
    a_tcp->next_time->prev_time = a_tcp->prev_time;
  if (a_tcp->prev_time)
    a_tcp->prev_time->next_time = a_tcp->next_time;
  if (a_tcp == tcp_oldest)
    tcp_oldest = a_tcp->prev_time;
  if (a_tcp == tcp_latest)
    tcp_latest = a_tcp->next_time;
  
  i = a_tcp->listeners;
  
  while (i) {
    j = i->next;
    free(i);
    i = j;
  }
  a_tcp->next_free = free_streams;
  free_streams = a_tcp;
  tcp_num--;
}

static int
mk_hash_index(struct tuple4 addr)
{
  u_int sth = (addr.dest << 16) + addr.source;
  u_int hash = 0;
  
  sth ^= addr.saddr;
  sth += addr.daddr;
  do {
    // taken from scanlogd.c
    hash ^= sth;
  }
  while ((sth >>= hash_deg));
  hash %= tcp_stream_table_size;

  return hash;
}

static void
add_new_tcp(struct tcphdr * this_tcphdr, struct ip * this_iphdr)
{
  struct tcp_stream *tolink;
  struct tcp_stream *a_tcp;
  int hash_index;
  struct tuple4 addr;
  
  addr.source = ntohs(this_tcphdr->th_sport);
  addr.dest = ntohs(this_tcphdr->th_dport);
  addr.saddr = this_iphdr->ip_src.s_addr;
  addr.daddr = this_iphdr->ip_dst.s_addr;
  hash_index = mk_hash_index(addr);
  
  if (tcp_num > max_stream) {
    struct lurker_node *i;
    
    tcp_oldest->nids_state = NIDS_TIMED_OUT;
    for (i = tcp_oldest->listeners; i; i = i->next)
      (i->item) (tcp_oldest, &i->data);
    free_tcp(tcp_oldest);
#ifndef WIN32
    nids_params.syslog(NIDS_WARN_TCP, NIDS_WARN_TCP_TOOMUCH, ugly_iphdr, this_tcphdr);
#endif
  }
  a_tcp = free_streams;
  if (!a_tcp) {
    fprintf(stderr, "gdb me ...\n");
    exit(-1);
  }
  free_streams = a_tcp->next_free;
  
  tcp_num++;
  tolink = tcp_stream_table[hash_index];
  memset(a_tcp, 0, sizeof(struct tcp_stream));
  a_tcp->hash_index = hash_index;
  a_tcp->addr = addr;
  a_tcp->client.state = TCP_SYN_SENT;
  a_tcp->client.seq = ntohl(this_tcphdr->th_seq) + 1;
  a_tcp->client.first_data_seq = a_tcp->client.seq;
  a_tcp->client.window = ntohs(this_tcphdr->th_win);
  a_tcp->server.state = TCP_CLOSE;
  a_tcp->next_node = tolink;
  a_tcp->prev_node = 0;
  if (tolink)
    tolink->prev_node = a_tcp;
  tcp_stream_table[hash_index] = a_tcp;
  a_tcp->next_time = tcp_latest;
  a_tcp->prev_time = 0;
  if (!tcp_oldest)
    tcp_oldest = a_tcp;
  if (tcp_latest)
    tcp_latest->prev_time = a_tcp;
  tcp_latest = a_tcp;
}

static void
add2buf(struct half_stream * rcv, char *data, int datalen)
{
  if (datalen + rcv->count - rcv->offset > rcv->bufsize) {
    if (!rcv->data) {
      int toalloc;
      
      if (datalen < 2048)
	toalloc = 4096;
      else
	toalloc = datalen * 2;
      rcv->data = malloc(toalloc);
      rcv->bufsize = toalloc;
    }
    else {
      rcv->data = realloc(rcv->data, 2 * rcv->bufsize);
      rcv->bufsize *= 2;
    }
    if (!rcv->data)
      nids_params.no_mem("add2buf");
  }
  memcpy(rcv->data + rcv->count - rcv->offset, data, datalen);
  rcv->count_new = datalen;
  rcv->count += datalen;
}

static void
ride_lurkers(struct tcp_stream * a_tcp, char mask)
{
  struct lurker_node *i;
  char cc, sc, ccu, scu;
  
  for (i = a_tcp->listeners; i; i = i->next)
    if (i->whatto & mask) {
      cc = a_tcp->client.collect;
      sc = a_tcp->server.collect;
      ccu = a_tcp->client.collect_urg;
      scu = a_tcp->server.collect_urg;
      
      (i->item) (a_tcp, &i->data);
      if (cc < a_tcp->client.collect)
	i->whatto |= COLLECT_cc;
      if (ccu < a_tcp->client.collect_urg)
	i->whatto |= COLLECT_ccu;
      if (sc < a_tcp->server.collect)
	i->whatto |= COLLECT_sc;
      if (scu < a_tcp->server.collect_urg)
	i->whatto |= COLLECT_scu;
      if (cc > a_tcp->client.collect)
	i->whatto &= ~COLLECT_cc;
      if (ccu > a_tcp->client.collect_urg)
	i->whatto &= ~COLLECT_ccu;
      if (sc > a_tcp->server.collect)
	i->whatto &= ~COLLECT_sc;
      if (scu > a_tcp->server.collect_urg)
	i->whatto &= ~COLLECT_scu;
    }
}

static void
notify(struct tcp_stream * a_tcp, struct half_stream * rcv,
       struct half_stream * snd)
{
  struct lurker_node *i, **prev_addr;
  char mask;

  if (rcv->count_new_urg) {
    if (!rcv->collect_urg)
      return;
    if (rcv == &a_tcp->client)
      mask = COLLECT_ccu;
    else
      mask = COLLECT_scu;
    ride_lurkers(a_tcp, mask);
    goto prune_listeners;
  }
  a_tcp->read = rcv->count - rcv->offset;
  if (rcv->collect) {
    if (rcv == &a_tcp->client)
      mask = COLLECT_cc;
    else
      mask = COLLECT_sc;

    ride_lurkers(a_tcp, mask);

    rcv->count_new = 0;
    if (a_tcp->read > 0) {
      memmove(rcv->data, rcv->data + a_tcp->read, rcv->count - rcv->offset - a_tcp->read);
      rcv->offset += a_tcp->read;
    }
  }
 prune_listeners:
  prev_addr = &a_tcp->listeners;
  i = a_tcp->listeners;
  while (i)
    if (!i->whatto) {
      *prev_addr = i->next;
      free(i);
      i = *prev_addr;
    }
    else {
      prev_addr = &i->next;
      i = i->next;
    }
}

static void
add_from_skb(struct tcp_stream * a_tcp, struct half_stream * rcv,
	     struct half_stream * snd,
	     u_char *data, int datalen,
	     u_int this_seq, char fin, char urg, u_int urg_ptr)
{
  u_int lost = EXP_SEQ - this_seq;
  int to_copy, to_copy2;
  
  if (urg && after(urg_ptr, EXP_SEQ - 1) &&
      (!rcv->urg_seen || after(urg_ptr, rcv->urg_ptr))) {
    rcv->urg_ptr = urg_ptr;
    rcv->urg_seen = 1;
  }
  if (after(rcv->urg_ptr + 1, this_seq + lost) &&
      before(rcv->urg_ptr, this_seq + datalen)) {
    to_copy = rcv->urg_ptr - (this_seq + lost);
    if (to_copy > 0) {
      if (rcv->collect) {
	add2buf(rcv, data + lost, to_copy);
	notify(a_tcp, rcv, snd);
      }
      else {
	rcv->count += to_copy;
      }
    }
    rcv->urgdata = data[rcv->urg_ptr - this_seq];
    rcv->count_new_urg = 1;
    notify(a_tcp, rcv, snd);
    rcv->count_new_urg = 0;
    rcv->urg_count++;
    to_copy2 = this_seq + datalen - rcv->urg_ptr - 1;
    if (to_copy2 > 0) {
      if (rcv->collect) {
	add2buf(rcv, data + lost + to_copy + 1, to_copy2);
	notify(a_tcp, rcv, snd);
      }
      else {
	rcv->count += to_copy2;
      }
    }
  }
  else {
    if (datalen > 0) {
      if (rcv->collect) {
	add2buf(rcv, data + lost, datalen - lost);
	notify(a_tcp, rcv, snd);
      }
      else {
	rcv->count += datalen - lost;
      }
    }
  }
  if (fin)
    snd->state = FIN_SENT;
}

static void
tcp_queue(struct tcp_stream * a_tcp, struct tcphdr * this_tcphdr,
	  struct half_stream * snd, struct half_stream * rcv,
	  char *data, int datalen, int skblen
	  )
{
  u_int this_seq = ntohl(this_tcphdr->th_seq);
  struct skbuff *pakiet;
  
  /*
   * Did we get anything new to ack?
   */
  
  if (!after(this_seq, EXP_SEQ)) {
    if (after(this_seq + datalen + (this_tcphdr->th_flags & TH_FIN), EXP_SEQ)) {
      /* the packet straddles our window end */
      add_from_skb(a_tcp, rcv, snd, data, datalen, this_seq,
		   (this_tcphdr->th_flags & TH_FIN),
		   (this_tcphdr->th_flags & TH_URG),
		   ntohs(this_tcphdr->th_urp) + this_seq - 1);
      /*
       * Do we have any old packets to ack that the above
       * made visible? (Go forward from skb)
       */
      pakiet = rcv->list;
      while (pakiet) {
	if (after(pakiet->seq, EXP_SEQ))
	  break;
	if (after(pakiet->seq + pakiet->len, EXP_SEQ)) {
	  struct skbuff *tmp;
	  
	  add_from_skb(a_tcp, rcv, snd, pakiet->data,
		       pakiet->len, pakiet->seq, pakiet->fin, pakiet->urg,
		       pakiet->urg_ptr + pakiet->seq - 1);
	  rcv->rmem_alloc -= pakiet->truesize;
	  if (pakiet->prev)
	    pakiet->prev->next = pakiet->next;
	  else
	    rcv->list = pakiet->next;
	  if (pakiet->next)
	    pakiet->next->prev = pakiet->prev;
	  else
	    rcv->listtail = pakiet->prev;
	  tmp = pakiet->next;
	  free(pakiet->data);
	  free(pakiet);
	  pakiet = tmp;
	}
	else
	  pakiet = pakiet->next;
      }
    }
    else
      return;
  }
  else {
    struct skbuff *p = rcv->listtail;

    pakiet = mknew(struct skbuff);
    pakiet->truesize = skblen;
    rcv->rmem_alloc += pakiet->truesize;
    pakiet->len = datalen;
    pakiet->data = malloc(datalen);
    if (!pakiet->data)
      nids_params.no_mem("tcp_queue");
    memcpy(pakiet->data, data, datalen);
    pakiet->fin = (this_tcphdr->th_flags & TH_FIN);
    pakiet->seq = this_seq;
    pakiet->urg = (this_tcphdr->th_flags & TH_URG);
    pakiet->urg_ptr = ntohs(this_tcphdr->th_urp);
    for (;;) {
      if (!p || !after(p->seq, this_seq))
	break;
      p = p->prev;
    }
    if (!p) {
      pakiet->prev = 0;
      pakiet->next = rcv->list;
      rcv->list = pakiet;
      if (!rcv->listtail)
	rcv->listtail = pakiet;
    }
    else {
      pakiet->next = p->next;
      p->next = pakiet;
      pakiet->prev = p;
      if (pakiet->next)
	pakiet->next->prev = pakiet;
      else
	rcv->listtail = pakiet;
    }
  }
}

static void
prune_queue(struct half_stream * rcv, struct tcphdr * this_tcphdr)
{
  struct skbuff *tmp, *p = rcv->list;

#ifndef WIN32
  nids_params.syslog(NIDS_WARN_TCP, NIDS_WARN_TCP_BIGQUEUE, ugly_iphdr, this_tcphdr);
#endif
  while (p) {
    free(p->data);
    tmp = p->next;
    free(p);
    p = tmp;
  }
  rcv->list = rcv->listtail = 0;
  rcv->rmem_alloc = 0;
}

static void
handle_ack(struct tcp_stream * a_tcp, struct half_stream * snd,
	   struct half_stream * rcv, u_int acknum)
{
  int ackdiff;

  ackdiff = acknum - snd->ack_seq;
  if (ackdiff > 0) {
    snd->ack_seq = acknum;
  }
}

static void
check_flags(struct ip * iph, struct tcphdr * th)
{
  u_char flag = *(((u_char *) th) + 13);

#ifndef WIN32  
  if (flag & 0x40 || flag & 0x80)
    nids_params.syslog(NIDS_WARN_TCP, NIDS_WARN_TCP_BADFLAGS, iph, th);
#endif
}

struct tcp_stream *
find_stream(struct tcphdr * this_tcphdr, struct ip * this_iphdr,
	    int *from_client)
{
  struct tuple4 this_addr, reversed;
  int hash_index;
  struct tcp_stream *a_tcp;

  this_addr.source = ntohs(this_tcphdr->th_sport);
  this_addr.dest = ntohs(this_tcphdr->th_dport);
  this_addr.saddr = this_iphdr->ip_src.s_addr;
  this_addr.daddr = this_iphdr->ip_dst.s_addr;
  hash_index = mk_hash_index(this_addr);
  for (a_tcp = tcp_stream_table[hash_index];
       a_tcp && !b_comp(a_tcp->addr, this_addr);
       a_tcp = a_tcp->next_node);
  if (a_tcp) {
    *from_client = 1;
    return a_tcp;
  }
  reversed.source = ntohs(this_tcphdr->th_dport);
  reversed.dest = ntohs(this_tcphdr->th_sport);
  reversed.saddr = this_iphdr->ip_dst.s_addr;
  reversed.daddr = this_iphdr->ip_src.s_addr;
  hash_index = mk_hash_index(reversed);
  for (a_tcp = tcp_stream_table[hash_index];
       a_tcp && !b_comp(a_tcp->addr, reversed);
       a_tcp = a_tcp->next_node);
  if (a_tcp) {
    *from_client = 0;
    return a_tcp;
  }
  else
    return 0;
}

void
process_tcp(u_char * data, int skblen)
{
  struct ip *this_iphdr = (struct ip *)data;
  struct tcphdr *this_tcphdr = (struct tcphdr *)(data + 4 * this_iphdr->ip_hl);
  int datalen, iplen;
  int from_client = 1;
  struct tcp_stream *a_tcp;
  struct half_stream *snd, *rcv;

  ugly_iphdr = this_iphdr;
  iplen = ntohs(this_iphdr->ip_len);
  if (iplen - 4 * this_iphdr->ip_hl <sizeof(struct tcphdr)) {
#ifndef WIN32
    nids_params.syslog(NIDS_WARN_TCP, NIDS_WARN_TCP_HDR, this_iphdr,
		       this_tcphdr);
#endif
    return;
  } // ktos sie bawi
  
  datalen = iplen - 4 * this_iphdr->ip_hl - 4 * this_tcphdr->th_off;
  
  if (datalen < 0) {
#ifndef WIN32
    nids_params.syslog(NIDS_WARN_TCP, NIDS_WARN_TCP_HDR, this_iphdr,
		       this_tcphdr);
#endif
    return;
  } // ktos sie bawi

  if (my_tcp_check(this_tcphdr, iplen - 4 * this_iphdr->ip_hl,
		   this_iphdr->ip_src.s_addr, this_iphdr->ip_dst.s_addr)) {
#ifndef WIN32
    nids_params.syslog(NIDS_WARN_TCP, NIDS_WARN_TCP_HDR, this_iphdr,
		       this_tcphdr);
#endif
    return;
  }
  if ((this_iphdr->ip_src.s_addr | this_iphdr->ip_dst.s_addr) == 0) {
#ifndef WIN32
    nids_params.syslog(NIDS_WARN_TCP, NIDS_WARN_TCP_HDR, this_iphdr,
		       this_tcphdr);
#endif
    return;
  }
  if (!(this_tcphdr->th_flags & TH_ACK))
    detect_scan(this_iphdr);
  check_flags(this_iphdr, this_tcphdr);
  if (!(a_tcp = find_stream(this_tcphdr, this_iphdr, &from_client))) {
    if ((this_tcphdr->th_flags & TH_SYN) &&
	!(this_tcphdr->th_flags & TH_ACK) &&
	!(this_tcphdr->th_flags & TH_RST))
      add_new_tcp(this_tcphdr, this_iphdr);
    return;
  }
  if (from_client) {
    snd = &a_tcp->client;
    rcv = &a_tcp->server;
  }
  else {
    rcv = &a_tcp->client;
    snd = &a_tcp->server;
  }
  if ((this_tcphdr->th_flags & TH_SYN)) {
    if (from_client || a_tcp->client.state != TCP_SYN_SENT ||
      a_tcp->server.state != TCP_CLOSE || !(this_tcphdr->th_flags & TH_ACK))
      return;
    if (a_tcp->client.seq != ntohl(this_tcphdr->th_ack))
      return;
    a_tcp->server.state = TCP_SYN_RECV;
    a_tcp->server.seq = ntohl(this_tcphdr->th_seq) + 1;
    a_tcp->server.first_data_seq = a_tcp->server.seq;
    a_tcp->server.ack_seq = ntohl(this_tcphdr->th_ack);
    a_tcp->server.window = ntohs(this_tcphdr->th_win);
    return;
  }
  if (!before(ntohl(this_tcphdr->th_seq), rcv->ack_seq + rcv->window) ||
      before(ntohl(this_tcphdr->th_seq) + datalen, rcv->ack_seq))
    return;

  if ((this_tcphdr->th_flags & TH_RST)) {
    if (a_tcp->nids_state == NIDS_DATA) {
      struct lurker_node *i;
      
      a_tcp->nids_state = NIDS_RESET;
      for (i = a_tcp->listeners; i; i = i->next)
	(i->item) (a_tcp, &i->data);
    }
    free_tcp(a_tcp);
    return;
  }
  if ((this_tcphdr->th_flags & TH_ACK)) {
    if (from_client && a_tcp->client.state == TCP_SYN_SENT &&
	a_tcp->server.state == TCP_SYN_RECV) {
      if (ntohl(this_tcphdr->th_ack) == a_tcp->server.seq) {
	a_tcp->client.state = TCP_ESTABLISHED;
	a_tcp->client.ack_seq = ntohl(this_tcphdr->th_ack);
	{
	  struct proc_node *i;
	  struct lurker_node *j;
	  void *data;
	  
	  a_tcp->server.state = TCP_ESTABLISHED;
	  a_tcp->nids_state = NIDS_JUST_EST;
	  for (i = tcp_procs; i; i = i->next) {
	    char whatto = 0;
	    char cc = a_tcp->client.collect;
	    char sc = a_tcp->server.collect;
	    char ccu = a_tcp->client.collect_urg;
	    char scu = a_tcp->server.collect_urg;
	    
	    (i->item) (a_tcp, &data);
	    if (cc < a_tcp->client.collect)
	      whatto |= COLLECT_cc;
	    if (ccu < a_tcp->client.collect_urg)
	      whatto |= COLLECT_ccu;
	    if (sc < a_tcp->server.collect)
	      whatto |= COLLECT_sc;
	    if (scu < a_tcp->server.collect_urg)
	      whatto |= COLLECT_scu;
	    if (whatto) {
	      j = mknew(struct lurker_node);
	      j->item = i->item;
	      j->data = data;
	      j->whatto = whatto;
	      j->next = a_tcp->listeners;
	      a_tcp->listeners = j;
	    }
	  }
	  if (!a_tcp->listeners) {
	    free_tcp(a_tcp);
	    return;
	  }
	  a_tcp->nids_state = NIDS_DATA;
	}
      }
      // return;
    }
  }
  if ((this_tcphdr->th_flags & TH_ACK)) {
    handle_ack(a_tcp, snd, rcv, ntohl(this_tcphdr->th_ack));
    if (rcv->state == FIN_SENT)
      rcv->state = FIN_CONFIRMED;
    if (rcv->state == FIN_CONFIRMED && snd->state == FIN_CONFIRMED) {
      struct lurker_node *i;
      
      a_tcp->nids_state = NIDS_CLOSE;
      for (i = a_tcp->listeners; i; i = i->next)
	(i->item) (a_tcp, &i->data);
      free_tcp(a_tcp);
      return;
    }
  }
  if (datalen + (this_tcphdr->th_flags & TH_FIN) > 0)
    tcp_queue(a_tcp, this_tcphdr, snd, rcv,
	      (char *) (this_tcphdr) + 4 * this_tcphdr->th_off,
	      datalen, skblen);
  snd->window = ntohs(this_tcphdr->th_win);
  if (rcv->rmem_alloc > 65535)
    prune_queue(rcv, this_tcphdr);
  if (!a_tcp->listeners)
    free_tcp(a_tcp);
}

void
nids_discard(struct tcp_stream * a_tcp, int num)
{
  if (num < a_tcp->read)
    a_tcp->read = num;
}

void
nids_register_tcp(void (*x))
{
  struct proc_node *i;

  i = mknew(struct proc_node);
  i->item = x;
  i->next = tcp_procs;
  tcp_procs = i;
}

int
tcp_init(int size)
{
  int tmp, i;

  tcp_stream_table_size = nids_params.n_tcp_streams;
  tcp_stream_table = malloc(tcp_stream_table_size * sizeof(char *));
  if (!tcp_stream_table)
    nids_params.no_mem("tcp_init");
  memset(tcp_stream_table, 0, tcp_stream_table_size * sizeof(char *));
  tmp = tcp_stream_table_size;
  hash_deg = 32;
  // computing ceiling of binary logarithm of tcp_stream_table_size
  while (!(0x80000000 & tmp)) {
    hash_deg--;
    tmp <<= 1;
  }
  max_stream = 3 * tcp_stream_table_size / 4;
  streams_pool = (struct tcp_stream *) malloc((max_stream + 1) * sizeof(struct tcp_stream));
  if (!streams_pool)
    nids_params.no_mem("tcp_init");
  for (i = 0; i < max_stream; i++)
    streams_pool[i].next_free = &(streams_pool[i + 1]);
  streams_pool[max_stream].next_free = 0;
  free_streams = streams_pool;

  return 0;
}

void
process_icmp(u_char * data)
{
  struct ip *iph = (struct ip *) data;
  struct ip *orig_ip;
  struct icmp *pkt;
  struct tcphdr *th;
  struct half_stream *hlf;
  int match_addr;
  struct tcp_stream *a_tcp;
  struct lurker_node *i;

  int from_client;
  int len = ntohs(iph->ip_len) - (iph->ip_hl << 2);
  
  if (len < sizeof(struct icmp))
    return;
  pkt = (struct icmp *) (data + (iph->ip_hl << 2));
  if (ip_compute_csum((char *) pkt, len))
    return;
  if (pkt->icmp_type != ICMP_DEST_UNREACH)
    return;
  len -= 8;
  // sizeof(struct icmp) is not what we want here
  
  if (len < sizeof(struct ip))
    return;

  orig_ip = (struct ip *) (((char *) pkt) + 8);
  len -= orig_ip->ip_hl << 2;
  if (len < 8)
    return;
  if ((pkt->icmp_code & 15) == ICMP_PROT_UNREACH ||
      (pkt->icmp_code & 15) == ICMP_PORT_UNREACH)
    match_addr = 1;
  else
    match_addr = 0;
  if (pkt->icmp_code > NR_ICMP_UNREACH)
    return;
  if (match_addr && (iph->ip_src.s_addr != orig_ip->ip_dst.s_addr))
    return;
  if (orig_ip->ip_p != IPPROTO_TCP)
    return;
  th = (struct tcphdr *) (((char *) orig_ip) + (orig_ip->ip_hl << 2));
  if (!(a_tcp = find_stream(th, orig_ip, &from_client)))
    return;
  if (a_tcp->addr.dest == iph->ip_dst.s_addr)
    hlf = &a_tcp->server;
  else
    hlf = &a_tcp->client;
  if (hlf->state != TCP_SYN_SENT && hlf->state != TCP_SYN_RECV)
    return;
  a_tcp->nids_state = NIDS_RESET;
  for (i = a_tcp->listeners; i; i = i->next)
    (i->item) (a_tcp, &i->data);
  free_tcp(a_tcp);
}
