www.pudn.com > src.rar > tcp.c


/*
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>
#include <unistd.h>
#include <netinet/in.h>
#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"
#include "hash.h"

#if ! HAVE_TCP_STATES
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 */
};

#endif

#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 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 purge_queue(struct half_stream * h)
{
struct skbuff *tmp, *p = h->list;

while (p) {
free(p->data);
tmp = p->next;
free(p);
p = tmp;
}
h->list = h->listtail = 0;
h->rmem_alloc = 0;
}

static void
free_tcp(struct tcp_stream * a_tcp)
{
int hash_index = a_tcp->hash_index;
struct lurker_node *i, *j;

purge_queue(&amt;a_tcp->server);
purge_queue(&amt;a_tcp->client);

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)
{
int hash=mkhash(addr.saddr, addr.source, addr.daddr, addr.dest);
return hash > tcp_stream_table_size;
}

static int get_ts(struct tcphdr * this_tcphdr, unsigned int * ts)
{
int len = 4 * this_tcphdr->th_off;
unsigned int tmp_ts;
unsigned char * options = (char*)(this_tcphdr + 1);
int ind = 0, ret = 0;
while (ind <= len - (int)sizeof (struct tcphdr) - 10 )
switch (options[ind]) {
case 0: /* TCPOPT_EOL */
return ret;
case 1: /* TCPOPT_NOP */
ind++;
continue;
case 8: /* TCPOPT_TIMESTAMP */
memcpy((char*)&amt;tmp_ts, options + ind + 2, 4);
*ts=ntohl(tmp_ts);
ret = 1;
/* no break, intentionally */
default:
if (options[ind+1] < 2 ) /* "silly option" */
return ret;
ind += options[ind+1];
}

return ret;
}



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, &amt;i->data);
free_tcp(tcp_oldest);
nids_params.syslog(NIDS_WARN_TCP, NIDS_WARN_TCP_TOOMUCH, ugly_iphdr, this_tcphdr);
}
a_tcp = free_streams;
if (!a_tcp) {
fprintf(stderr, "gdb me ...\n");
#ifdef WIN32 /* Exit here, I do not know of a function similar to pause() in WIN32 -- Mike */
exit(-1);
#else
pause();
#endif
}
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->client.ts_on = get_ts(this_tcphdr, &amt;a_tcp->client.curr_ts);
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)
{
int toalloc;

if (datalen + rcv->count - rcv->offset > rcv->bufsize) {
if (!rcv->data) {
if (datalen < 2048)
toalloc = 4096;
else
toalloc = datalen * 2;
rcv->data = malloc(toalloc);
rcv->bufsize = toalloc;
}
else {
if (datalen < rcv->bufsize)
toalloc = 2 * rcv->bufsize;
else
toalloc = rcv->bufsize + 2*datalen;
rcv->data = realloc(rcv->data, toalloc);
rcv->bufsize = toalloc;
}
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 &amt; 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, &amt;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 &amt;= ~COLLECT_cc;
if (ccu > a_tcp->client.collect_urg)
i->whatto &amt;= ~COLLECT_ccu;
if (sc > a_tcp->server.collect)
i->whatto &amt;= ~COLLECT_sc;
if (scu > a_tcp->server.collect_urg)
i->whatto &amt;= ~COLLECT_scu;
}
}

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

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

ride_lurkers(a_tcp, mask);
if (a_tcp->read>total-rcv->count_new)
rcv->count_new=total-a_tcp->read;

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;
}
}while (nids_params.one_loop_less &amt;&amt; a_tcp->read>0 &amt;&amt; rcv->count_new);
// we know that if one_loop_less!=0, we have only one callback to notify
rcv->count_new=0;
}
prune_listeners:
prev_addr = &amt;a_tcp->listeners;
i = a_tcp->listeners;
while (i)
if (!i->whatto) {
*prev_addr = i->next;
free(i);
i = *prev_addr;
}
else {
prev_addr = &amt;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 &amt;&amt; after(urg_ptr, EXP_SEQ - 1) &amt;&amt;
(!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) &amt;&amt;
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);
}
else {
rcv->count += to_copy;
rcv->offset = rcv->count; /* clear the buffer */
}
}
rcv->urgdata = data[rcv->urg_ptr - this_seq];
rcv->count_new_urg = 1;
notify(a_tcp, rcv);
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);
}
else {
rcv->count += to_copy2;
rcv->offset = rcv->count; /* clear the buffer */
}
}
}
else {
if (datalen - lost > 0) {
if (rcv->collect) {
add2buf(rcv, data + lost, datalen - lost);
notify(a_tcp, rcv);
}
else {
rcv->count += datalen - lost;
rcv->offset = rcv->count; /* clear the buffer */
}
}
}
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 &amt; TH_FIN), EXP_SEQ)) {
/* the packet straddles our window end */
get_ts(this_tcphdr, &amt;snd->curr_ts);
add_from_skb(a_tcp, rcv, snd, data, datalen, this_seq,
(this_tcphdr->th_flags &amt; TH_FIN),
(this_tcphdr->th_flags &amt; 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 + pakiet->fin, 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 &amt; TH_FIN);
pakiet->seq = this_seq;
pakiet->urg = (this_tcphdr->th_flags &amt; 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;
if (rcv->list)
rcv->list->prev = pakiet;
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;

nids_params.syslog(NIDS_WARN_TCP, NIDS_WARN_TCP_BIGQUEUE, ugly_iphdr, this_tcphdr);
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 half_stream * snd, 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);

if (flag &amt; 0x40 || flag &amt; 0x80)
nids_params.syslog(NIDS_WARN_TCP, NIDS_WARN_TCP_BADFLAGS, iph, th);
}

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 &amt;&amt; !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 &amt;&amt; !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 clear_stream_buffers()
{
int i;
struct lurker_node *j;
struct tcp_stream *a_tcp;

for (i = 0; i < tcp_stream_table_size; i++) {
for (a_tcp = tcp_stream_table[i];
a_tcp;
a_tcp = a_tcp->next_node) {
for (j = a_tcp->listeners; j; j = j->next) {
a_tcp->nids_state = NIDS_EXITING;
(j->item)(a_tcp, &amt;j->data);
}
}
}
}

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;
unsigned int tmp_ts;
struct tcp_stream *a_tcp;
struct half_stream *snd, *rcv;

ugly_iphdr = this_iphdr;
iplen = ntohs(this_iphdr->ip_len);
if ((unsigned)iplen < 4 * this_iphdr->ip_hl + sizeof(struct tcphdr)) {
nids_params.syslog(NIDS_WARN_TCP, NIDS_WARN_TCP_HDR, this_iphdr,
this_tcphdr);
return;
} // ktos sie bawi

datalen = iplen - 4 * this_iphdr->ip_hl - 4 * this_tcphdr->th_off;

if (datalen < 0) {
nids_params.syslog(NIDS_WARN_TCP, NIDS_WARN_TCP_HDR, this_iphdr,
this_tcphdr);
return;
} // ktos sie bawi

if ((this_iphdr->ip_src.s_addr | this_iphdr->ip_dst.s_addr) == 0) {
nids_params.syslog(NIDS_WARN_TCP, NIDS_WARN_TCP_HDR, this_iphdr,
this_tcphdr);
return;
}
if (!(this_tcphdr->th_flags &amt; TH_ACK))
detect_scan(this_iphdr);
if (!nids_params.n_tcp_streams) return;
if (my_tcp_check(this_tcphdr, iplen - 4 * this_iphdr->ip_hl,
this_iphdr->ip_src.s_addr, this_iphdr->ip_dst.s_addr)) {
nids_params.syslog(NIDS_WARN_TCP, NIDS_WARN_TCP_HDR, this_iphdr,
this_tcphdr);
return;
}

check_flags(this_iphdr, this_tcphdr);
if (!(a_tcp = find_stream(this_tcphdr, this_iphdr, &amt;from_client))) {
if ((this_tcphdr->th_flags &amt; TH_SYN) &amt;&amt;
!(this_tcphdr->th_flags &amt; TH_ACK) &amt;&amt;
!(this_tcphdr->th_flags &amt; TH_RST))
add_new_tcp(this_tcphdr, this_iphdr);
return;
}
if (from_client) {
snd = &amt;a_tcp->client;
rcv = &amt;a_tcp->server;
}
else {
rcv = &amt;a_tcp->client;
snd = &amt;a_tcp->server;
}
if ((this_tcphdr->th_flags &amt; TH_SYN)) {
if (from_client || a_tcp->client.state != TCP_SYN_SENT ||
a_tcp->server.state != TCP_CLOSE || !(this_tcphdr->th_flags &amt; 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);
if (a_tcp->client.ts_on) {
a_tcp->server.ts_on = get_ts(this_tcphdr, &amt;a_tcp->server.curr_ts);
if (!a_tcp->server.ts_on)
a_tcp->client.ts_on = 0;
} else a_tcp->server.ts_on = 0;
return;
}
if (
! ( !datalen &amt;&amt; ntohl(this_tcphdr->th_seq) == rcv->ack_seq )
&amt;&amt;
( !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 &amt; 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, &amt;i->data);
}
free_tcp(a_tcp);
return;
}

/* PAWS check */
if (rcv->ts_on &amt;&amt; get_ts(this_tcphdr, &amt;tmp_ts) &amt;&amt;
before(tmp_ts, snd->curr_ts))
return;

if ((this_tcphdr->th_flags &amt; TH_ACK)) {
if (from_client &amt;&amt; a_tcp->client.state == TCP_SYN_SENT &amt;&amt;
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, &amt;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 (nids_params.one_loop_less) {
if (a_tcp->client.collect >=2) {
a_tcp->client.collect=cc;
whatto&amt;=~COLLECT_cc;
}
if (a_tcp->server.collect >=2 ) {
a_tcp->server.collect=sc;
whatto&amt;=~COLLECT_sc;
}
}
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 &amt; TH_ACK)) {
handle_ack(snd, ntohl(this_tcphdr->th_ack));
if (rcv->state == FIN_SENT)
rcv->state = FIN_CONFIRMED;
if (rcv->state == FIN_CONFIRMED &amt;&amt; 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, &amt;i->data);
free_tcp(a_tcp);
return;
}
}
if (datalen + (this_tcphdr->th_flags &amt; 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 i;
if (!size) return 0;
tcp_stream_table_size = size;
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 *));
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 = &amt;(streams_pool[i + 1]);
streams_pool[max_stream].next_free = 0;
free_streams = streams_pool;
init_hash();
return 0;
}

#if HAVE_ICMPHDR
#define STRUCT_ICMP struct icmphdr
#define ICMP_CODE code
#define ICMP_TYPE type
#else
#define STRUCT_ICMP struct icmp
#define ICMP_CODE icmp_code
#define ICMP_TYPE icmp_type
#endif

#ifndef ICMP_DEST_UNREACH
#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


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;
/* we will use unsigned, to suppress warning; we must be careful with
possible wrap when substracting
the following is ok, as the ip header has already been sanitized */
unsigned 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;
/* ok due to check 7 lines above */
len -= sizeof(STRUCT_ICMP);
// sizeof(struct icmp) is not what we want here

if (len < sizeof(struct ip))
return;

orig_ip = (struct ip *) (((char *) pkt) + 8);
if (len < (unsigned)(orig_ip->ip_hl << 2) + 8)
return;
/* subtraction ok due to the check above */
len -= orig_ip->ip_hl << 2;
if ((pkt->ICMP_CODE &amt; 15) == ICMP_PROT_UNREACH ||
(pkt->ICMP_CODE &amt; 15) == ICMP_PORT_UNREACH)
match_addr = 1;
else
match_addr = 0;
if (pkt->ICMP_CODE > NR_ICMP_UNREACH)
return;
if (match_addr &amt;&amt; (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, &amt;from_client)))
return;
if (a_tcp->addr.dest == iph->ip_dst.s_addr)
hlf = &amt;a_tcp->server;
else
hlf = &amt;a_tcp->client;
if (hlf->state != TCP_SYN_SENT &amt;&amt; 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, &amt;i->data);
free_tcp(a_tcp);
}