ip_state.c revision 53642
153642Sguido/*
253642Sguido * Copyright (C) 1995-1998 by Darren Reed.
353642Sguido *
453642Sguido * Redistribution and use in source and binary forms are permitted
553642Sguido * provided that this notice is preserved and due credit is given
653642Sguido * to the original author and the contributors.
753642Sguido */
853642Sguido#if !defined(lint)
953642Sguidostatic const char sccsid[] = "@(#)ip_state.c	1.8 6/5/96 (C) 1993-1995 Darren Reed";
1053642Sguido/*static const char rcsid[] = "@(#)$Id: ip_state.c,v 2.3.2.9 1999/10/21 14:31:09 darrenr Exp $";*/
1153642Sguidostatic const char rcsid[] = "@(#)$FreeBSD: head/sys/contrib/ipfilter/netinet/ip_state.c 53642 1999-11-23 21:44:59Z guido $";
1253642Sguido#endif
1353642Sguido
1453642Sguido#include <sys/errno.h>
1553642Sguido#include <sys/types.h>
1653642Sguido#include <sys/param.h>
1753642Sguido#include <sys/file.h>
1853642Sguido#if defined(__NetBSD__) && (NetBSD >= 199905) && !defined(IPFILTER_LKM) && \
1953642Sguido    defined(_KERNEL)
2053642Sguido# include "opt_ipfilter_log.h"
2153642Sguido#endif
2253642Sguido#if !defined(_KERNEL) && !defined(KERNEL) && !defined(__KERNEL__)
2353642Sguido# include <stdio.h>
2453642Sguido# include <stdlib.h>
2553642Sguido# include <string.h>
2653642Sguido#else
2753642Sguido# ifdef linux
2853642Sguido#  include <linux/kernel.h>
2953642Sguido#  include <linux/module.h>
3053642Sguido# endif
3153642Sguido#endif
3253642Sguido#if defined(KERNEL) && (__FreeBSD_version >= 220000)
3353642Sguido# include <sys/filio.h>
3453642Sguido# include <sys/fcntl.h>
3553642Sguido# if (__FreeBSD_version >= 300000) && !defined(IPFILTER_LKM)
3653642Sguido#  include "opt_ipfilter.h"
3753642Sguido# endif
3853642Sguido#else
3953642Sguido# include <sys/ioctl.h>
4053642Sguido#endif
4153642Sguido#include <sys/time.h>
4253642Sguido#include <sys/uio.h>
4353642Sguido#ifndef linux
4453642Sguido# include <sys/protosw.h>
4553642Sguido#endif
4653642Sguido#include <sys/socket.h>
4753642Sguido#if defined(_KERNEL) && !defined(linux)
4853642Sguido# include <sys/systm.h>
4953642Sguido#endif
5053642Sguido#if !defined(__SVR4) && !defined(__svr4__)
5153642Sguido# ifndef linux
5253642Sguido#  include <sys/mbuf.h>
5353642Sguido# endif
5453642Sguido#else
5553642Sguido# include <sys/filio.h>
5653642Sguido# include <sys/byteorder.h>
5753642Sguido# ifdef _KERNEL
5853642Sguido#  include <sys/dditypes.h>
5953642Sguido# endif
6053642Sguido# include <sys/stream.h>
6153642Sguido# include <sys/kmem.h>
6253642Sguido#endif
6353642Sguido
6453642Sguido#include <net/if.h>
6553642Sguido#ifdef sun
6653642Sguido# include <net/af.h>
6753642Sguido#endif
6853642Sguido#include <net/route.h>
6953642Sguido#include <netinet/in.h>
7053642Sguido#include <netinet/in_systm.h>
7153642Sguido#include <netinet/ip.h>
7253642Sguido#include <netinet/tcp.h>
7353642Sguido#ifndef linux
7453642Sguido# include <netinet/ip_var.h>
7553642Sguido# include <netinet/tcp_fsm.h>
7653642Sguido#endif
7753642Sguido#include <netinet/udp.h>
7853642Sguido#include <netinet/ip_icmp.h>
7953642Sguido#include "netinet/ip_compat.h"
8053642Sguido#include <netinet/tcpip.h>
8153642Sguido#include "netinet/ip_fil.h"
8253642Sguido#include "netinet/ip_nat.h"
8353642Sguido#include "netinet/ip_frag.h"
8453642Sguido#include "netinet/ip_proxy.h"
8553642Sguido#include "netinet/ip_state.h"
8653642Sguido#if (__FreeBSD_version >= 300000)
8753642Sguido# include <sys/malloc.h>
8853642Sguido# if (defined(_KERNEL) || defined(KERNEL)) && !defined(IPFILTER_LKM)
8953642Sguido#  include <sys/libkern.h>
9053642Sguido#  include <sys/systm.h>
9153642Sguido# endif
9253642Sguido#endif
9353642Sguido
9453642Sguido#ifndef	MIN
9553642Sguido# define	MIN(a,b)	(((a)<(b))?(a):(b))
9653642Sguido#endif
9753642Sguido
9853642Sguido#define	TCP_CLOSE	(TH_FIN|TH_RST)
9953642Sguido
10053642Sguidostatic ipstate_t **ips_table = NULL;
10153642Sguidostatic int	ips_num = 0;
10253642Sguidostatic ips_stat_t ips_stats;
10353642Sguido#if	(SOLARIS || defined(__sgi)) && defined(_KERNEL)
10453642Sguidoextern	KRWLOCK_T	ipf_state, ipf_mutex;
10553642Sguidoextern	kmutex_t	ipf_rw;
10653642Sguido#endif
10753642Sguido
10853642Sguidostatic int fr_matchsrcdst __P((ipstate_t *, struct in_addr, struct in_addr,
10953642Sguido			       fr_info_t *, tcphdr_t *));
11053642Sguidostatic frentry_t *fr_checkicmpmatchingstate __P((ip_t *, fr_info_t *));
11153642Sguidostatic int fr_state_flush __P((int));
11253642Sguidostatic ips_stat_t *fr_statetstats __P((void));
11353642Sguidostatic void fr_delstate __P((ipstate_t *));
11453642Sguido
11553642Sguido
11653642Sguido#define	FIVE_DAYS	(2 * 5 * 86400)	/* 5 days: half closed session */
11753642Sguido
11853642Sguido#define	TCP_MSL	240			/* 2 minutes */
11953642Sguidou_long	fr_tcpidletimeout = FIVE_DAYS,
12053642Sguido	fr_tcpclosewait = 2 * TCP_MSL,
12153642Sguido	fr_tcplastack = 2 * TCP_MSL,
12253642Sguido	fr_tcptimeout = 2 * TCP_MSL,
12353642Sguido	fr_tcpclosed = 1,
12453642Sguido	fr_udptimeout = 240,
12553642Sguido	fr_icmptimeout = 120;
12653642Sguidoint	fr_statemax = IPSTATE_MAX,
12753642Sguido	fr_statesize = IPSTATE_SIZE;
12853642Sguidoint	fr_state_doflush = 0;
12953642Sguido
13053642Sguido
13153642Sguidoint fr_stateinit()
13253642Sguido{
13353642Sguido	KMALLOCS(ips_table, ipstate_t **, fr_statesize * sizeof(ipstate_t *));
13453642Sguido	if (ips_table != NULL)
13553642Sguido		bzero((char *)ips_table, fr_statesize * sizeof(ipstate_t *));
13653642Sguido	else
13753642Sguido		return -1;
13853642Sguido	return 0;
13953642Sguido}
14053642Sguido
14153642Sguido
14253642Sguidostatic ips_stat_t *fr_statetstats()
14353642Sguido{
14453642Sguido	ips_stats.iss_active = ips_num;
14553642Sguido	ips_stats.iss_table = ips_table;
14653642Sguido	return &ips_stats;
14753642Sguido}
14853642Sguido
14953642Sguido
15053642Sguido/*
15153642Sguido * flush state tables.  two actions currently defined:
15253642Sguido * which == 0 : flush all state table entries
15353642Sguido * which == 1 : flush TCP connections which have started to close but are
15453642Sguido *              stuck for some reason.
15553642Sguido */
15653642Sguidostatic int fr_state_flush(which)
15753642Sguidoint which;
15853642Sguido{
15953642Sguido	register int i;
16053642Sguido	register ipstate_t *is, **isp;
16153642Sguido#if defined(_KERNEL) && !SOLARIS
16253642Sguido	int s;
16353642Sguido#endif
16453642Sguido	int delete, removed = 0;
16553642Sguido
16653642Sguido	SPL_NET(s);
16753642Sguido	WRITE_ENTER(&ipf_state);
16853642Sguido	for (i = fr_statesize - 1; i >= 0; i--)
16953642Sguido		for (isp = &ips_table[i]; (is = *isp); ) {
17053642Sguido			delete = 0;
17153642Sguido
17253642Sguido			switch (which)
17353642Sguido			{
17453642Sguido			case 0 :
17553642Sguido				delete = 1;
17653642Sguido				break;
17753642Sguido			case 1 :
17853642Sguido				if ((is->is_p == IPPROTO_TCP) &&
17953642Sguido				    (((is->is_state[0] <= TCPS_ESTABLISHED) &&
18053642Sguido				      (is->is_state[1] > TCPS_ESTABLISHED)) ||
18153642Sguido				     ((is->is_state[1] <= TCPS_ESTABLISHED) &&
18253642Sguido				      (is->is_state[0] > TCPS_ESTABLISHED))))
18353642Sguido					delete = 1;
18453642Sguido				break;
18553642Sguido			}
18653642Sguido
18753642Sguido			if (delete) {
18853642Sguido				*isp = is->is_next;
18953642Sguido				if (is->is_p == IPPROTO_TCP)
19053642Sguido					ips_stats.iss_fin++;
19153642Sguido				else
19253642Sguido					ips_stats.iss_expire++;
19353642Sguido				if (ips_table[i] == NULL)
19453642Sguido					ips_stats.iss_inuse--;
19553642Sguido#ifdef	IPFILTER_LOG
19653642Sguido				ipstate_log(is, ISL_FLUSH);
19753642Sguido#endif
19853642Sguido				fr_delstate(is);
19953642Sguido				ips_num--;
20053642Sguido				removed++;
20153642Sguido			} else
20253642Sguido				isp = &is->is_next;
20353642Sguido		}
20453642Sguido	if (fr_state_doflush) {
20553642Sguido		(void) fr_state_flush(1);
20653642Sguido		fr_state_doflush = 0;
20753642Sguido	}
20853642Sguido	RWLOCK_EXIT(&ipf_state);
20953642Sguido	SPL_X(s);
21053642Sguido	return removed;
21153642Sguido}
21253642Sguido
21353642Sguido
21453642Sguidoint fr_state_ioctl(data, cmd, mode)
21553642Sguidocaddr_t data;
21653642Sguido#if defined(__NetBSD__) || defined(__OpenBSD__)
21753642Sguidou_long cmd;
21853642Sguido#else
21953642Sguidoint cmd;
22053642Sguido#endif
22153642Sguidoint mode;
22253642Sguido{
22353642Sguido	int	arg, ret, error = 0;
22453642Sguido
22553642Sguido	switch (cmd)
22653642Sguido	{
22753642Sguido	case SIOCIPFFL :
22853642Sguido		IRCOPY(data, (caddr_t)&arg, sizeof(arg));
22953642Sguido		if (arg == 0 || arg == 1) {
23053642Sguido			ret = fr_state_flush(arg);
23153642Sguido			IWCOPY((caddr_t)&ret, data, sizeof(ret));
23253642Sguido		} else
23353642Sguido			error = EINVAL;
23453642Sguido		break;
23553642Sguido	case SIOCGIPST :
23653642Sguido		IWCOPY((caddr_t)fr_statetstats(), data, sizeof(ips_stat_t));
23753642Sguido		break;
23853642Sguido	case FIONREAD :
23953642Sguido#ifdef	IPFILTER_LOG
24053642Sguido		IWCOPY((caddr_t)&iplused[IPL_LOGSTATE], (caddr_t)data,
24153642Sguido		       sizeof(iplused[IPL_LOGSTATE]));
24253642Sguido#endif
24353642Sguido		break;
24453642Sguido	default :
24553642Sguido		error = EINVAL;
24653642Sguido		break;
24753642Sguido	}
24853642Sguido	return error;
24953642Sguido}
25053642Sguido
25153642Sguido
25253642Sguido/*
25353642Sguido * Create a new ipstate structure and hang it off the hash table.
25453642Sguido */
25553642Sguidoipstate_t *fr_addstate(ip, fin, flags)
25653642Sguidoip_t *ip;
25753642Sguidofr_info_t *fin;
25853642Sguidou_int flags;
25953642Sguido{
26053642Sguido	register ipstate_t *is;
26153642Sguido	register u_int hv;
26253642Sguido	ipstate_t ips;
26353642Sguido	u_int pass;
26453642Sguido
26553642Sguido	if ((ip->ip_off & IP_OFFMASK) || (fin->fin_fi.fi_fl & FI_SHORT))
26653642Sguido		return NULL;
26753642Sguido	if (ips_num == fr_statemax) {
26853642Sguido		ips_stats.iss_max++;
26953642Sguido		fr_state_doflush = 1;
27053642Sguido		return NULL;
27153642Sguido	}
27253642Sguido	is = &ips;
27353642Sguido	bzero((char *)is, sizeof(*is));
27453642Sguido	ips.is_age = 1;
27553642Sguido	ips.is_state[0] = 0;
27653642Sguido	ips.is_state[1] = 0;
27753642Sguido	/*
27853642Sguido	 * Copy and calculate...
27953642Sguido	 */
28053642Sguido	hv = (is->is_p = ip->ip_p);
28153642Sguido	hv += (is->is_src.s_addr = ip->ip_src.s_addr);
28253642Sguido	hv += (is->is_dst.s_addr = ip->ip_dst.s_addr);
28353642Sguido
28453642Sguido	switch (ip->ip_p)
28553642Sguido	{
28653642Sguido	case IPPROTO_ICMP :
28753642Sguido	    {
28853642Sguido		struct icmp *ic = (struct icmp *)fin->fin_dp;
28953642Sguido
29053642Sguido		switch (ic->icmp_type)
29153642Sguido		{
29253642Sguido		case ICMP_ECHO :
29353642Sguido			is->is_icmp.ics_type = ICMP_ECHOREPLY;	/* XXX */
29453642Sguido			hv += (is->is_icmp.ics_id = ic->icmp_id);
29553642Sguido			hv += (is->is_icmp.ics_seq = ic->icmp_seq);
29653642Sguido			break;
29753642Sguido		case ICMP_TSTAMP :
29853642Sguido		case ICMP_IREQ :
29953642Sguido		case ICMP_MASKREQ :
30053642Sguido			is->is_icmp.ics_type = ic->icmp_type + 1;
30153642Sguido			break;
30253642Sguido		default :
30353642Sguido			return NULL;
30453642Sguido		}
30553642Sguido		ATOMIC_INC(ips_stats.iss_icmp);
30653642Sguido		is->is_age = fr_icmptimeout;
30753642Sguido		break;
30853642Sguido	    }
30953642Sguido	case IPPROTO_TCP :
31053642Sguido	    {
31153642Sguido		register tcphdr_t *tcp = (tcphdr_t *)fin->fin_dp;
31253642Sguido
31353642Sguido		/*
31453642Sguido		 * The endian of the ports doesn't matter, but the ack and
31553642Sguido		 * sequence numbers do as we do mathematics on them later.
31653642Sguido		 */
31753642Sguido		is->is_dport = tcp->th_dport;
31853642Sguido		is->is_sport = tcp->th_sport;
31953642Sguido		if ((flags & (FI_W_DPORT|FI_W_SPORT)) == 0) {
32053642Sguido			hv += tcp->th_dport;
32153642Sguido			hv += tcp->th_sport;
32253642Sguido		}
32353642Sguido		if (tcp->th_seq != 0) {
32453642Sguido			is->is_send = ntohl(tcp->th_seq) + ip->ip_len -
32553642Sguido				      fin->fin_hlen - (tcp->th_off << 2) +
32653642Sguido				      ((tcp->th_flags & TH_SYN) ? 1 : 0) +
32753642Sguido				      ((tcp->th_flags & TH_FIN) ? 1 : 0);
32853642Sguido			is->is_maxsend = is->is_send + 1;
32953642Sguido		}
33053642Sguido		is->is_dend = 0;
33153642Sguido		is->is_maxswin = ntohs(tcp->th_win);
33253642Sguido		if (is->is_maxswin == 0)
33353642Sguido			is->is_maxswin = 1;
33453642Sguido		/*
33553642Sguido		 * If we're creating state for a starting connection, start the
33653642Sguido		 * timer on it as we'll never see an error if it fails to
33753642Sguido		 * connect.
33853642Sguido		 */
33953642Sguido		MUTEX_ENTER(&ipf_rw);
34053642Sguido		ips_stats.iss_tcp++;
34153642Sguido		fr_tcp_age(&is->is_age, is->is_state, ip, fin,
34253642Sguido			   tcp->th_sport == is->is_sport);
34353642Sguido		MUTEX_EXIT(&ipf_rw);
34453642Sguido		break;
34553642Sguido	    }
34653642Sguido	case IPPROTO_UDP :
34753642Sguido	    {
34853642Sguido		register tcphdr_t *tcp = (tcphdr_t *)fin->fin_dp;
34953642Sguido
35053642Sguido		if ((flags & (FI_W_DPORT|FI_W_SPORT)) == 0) {
35153642Sguido			hv += (is->is_dport = tcp->th_dport);
35253642Sguido			hv += (is->is_sport = tcp->th_sport);
35353642Sguido		}
35453642Sguido		ATOMIC_INC(ips_stats.iss_udp);
35553642Sguido		is->is_age = fr_udptimeout;
35653642Sguido		break;
35753642Sguido	    }
35853642Sguido	default :
35953642Sguido		return NULL;
36053642Sguido	}
36153642Sguido
36253642Sguido	KMALLOC(is, ipstate_t *);
36353642Sguido	if (is == NULL) {
36453642Sguido		ATOMIC_INC(ips_stats.iss_nomem);
36553642Sguido		return NULL;
36653642Sguido	}
36753642Sguido	bcopy((char *)&ips, (char *)is, sizeof(*is));
36853642Sguido	hv %= fr_statesize;
36953642Sguido	RW_UPGRADE(&ipf_mutex);
37053642Sguido	is->is_rule = fin->fin_fr;
37153642Sguido	if (is->is_rule != NULL) {
37253642Sguido		is->is_rule->fr_ref++;
37353642Sguido		pass = is->is_rule->fr_flags;
37453642Sguido	} else
37553642Sguido		pass = fr_flags;
37653642Sguido	MUTEX_DOWNGRADE(&ipf_mutex);
37753642Sguido	WRITE_ENTER(&ipf_state);
37853642Sguido
37953642Sguido	is->is_rout = pass & FR_OUTQUE ? 1 : 0;
38053642Sguido	is->is_pass = pass;
38153642Sguido	is->is_pkts = 1;
38253642Sguido	is->is_bytes = ip->ip_len;
38353642Sguido	/*
38453642Sguido	 * We want to check everything that is a property of this packet,
38553642Sguido	 * but we don't (automatically) care about it's fragment status as
38653642Sguido	 * this may change.
38753642Sguido	 */
38853642Sguido	is->is_opt = fin->fin_fi.fi_optmsk;
38953642Sguido	is->is_optmsk = 0xffffffff;
39053642Sguido	is->is_sec = fin->fin_fi.fi_secmsk;
39153642Sguido	is->is_secmsk = 0xffff;
39253642Sguido	is->is_auth = fin->fin_fi.fi_auth;
39353642Sguido	is->is_authmsk = 0xffff;
39453642Sguido	is->is_flags = fin->fin_fi.fi_fl & FI_CMP;
39553642Sguido	is->is_flags |= FI_CMP << 4;
39653642Sguido	is->is_flags |= flags & (FI_W_DPORT|FI_W_SPORT);
39753642Sguido	/*
39853642Sguido	 * add into table.
39953642Sguido	 */
40053642Sguido	is->is_next = ips_table[hv];
40153642Sguido	ips_table[hv] = is;
40253642Sguido	if (is->is_next == NULL)
40353642Sguido		ips_stats.iss_inuse++;
40453642Sguido	if (fin->fin_out) {
40553642Sguido		is->is_ifpin = NULL;
40653642Sguido		is->is_ifpout = fin->fin_ifp;
40753642Sguido	} else {
40853642Sguido		is->is_ifpin = fin->fin_ifp;
40953642Sguido		is->is_ifpout = NULL;
41053642Sguido	}
41153642Sguido	if (pass & FR_LOGFIRST)
41253642Sguido		is->is_pass &= ~(FR_LOGFIRST|FR_LOG);
41353642Sguido	ATOMIC_INC(ips_num);
41453642Sguido#ifdef	IPFILTER_LOG
41553642Sguido	ipstate_log(is, ISL_NEW);
41653642Sguido#endif
41753642Sguido	RWLOCK_EXIT(&ipf_state);
41853642Sguido	fin->fin_rev = (is->is_dst.s_addr != ip->ip_dst.s_addr);
41953642Sguido	if (fin->fin_fi.fi_fl & FI_FRAG)
42053642Sguido		ipfr_newfrag(ip, fin, pass ^ FR_KEEPSTATE);
42153642Sguido	return is;
42253642Sguido}
42353642Sguido
42453642Sguido
42553642Sguido
42653642Sguido/*
42753642Sguido * check to see if a packet with TCP headers fits within the TCP window.
42853642Sguido * change timeout depending on whether new packet is a SYN-ACK returning for a
42953642Sguido * SYN or a RST or FIN which indicate time to close up shop.
43053642Sguido */
43153642Sguidoint fr_tcpstate(is, fin, ip, tcp)
43253642Sguidoregister ipstate_t *is;
43353642Sguidofr_info_t *fin;
43453642Sguidoip_t *ip;
43553642Sguidotcphdr_t *tcp;
43653642Sguido{
43753642Sguido	register tcp_seq seq, ack, end;
43853642Sguido	register int ackskew;
43953642Sguido	tcpdata_t  *fdata, *tdata;
44053642Sguido	u_short	win, maxwin;
44153642Sguido	int ret = 0;
44253642Sguido	int source;
44353642Sguido
44453642Sguido	/*
44553642Sguido	 * Find difference between last checked packet and this packet.
44653642Sguido	 */
44753642Sguido	source = (ip->ip_src.s_addr == is->is_src.s_addr);
44853642Sguido	fdata = &is->is_tcp.ts_data[!source];
44953642Sguido	tdata = &is->is_tcp.ts_data[source];
45053642Sguido	seq = ntohl(tcp->th_seq);
45153642Sguido	ack = ntohl(tcp->th_ack);
45253642Sguido	win = ntohs(tcp->th_win);
45353642Sguido	end = seq + ip->ip_len - fin->fin_hlen - (tcp->th_off << 2) +
45453642Sguido	       ((tcp->th_flags & TH_SYN) ? 1 : 0) +
45553642Sguido	       ((tcp->th_flags & TH_FIN) ? 1 : 0);
45653642Sguido
45753642Sguido	if (fdata->td_end == 0) {
45853642Sguido		/*
45953642Sguido		 * Must be a (outgoing) SYN-ACK in reply to a SYN.
46053642Sguido		 */
46153642Sguido		fdata->td_end = end;
46253642Sguido		fdata->td_maxwin = 1;
46353642Sguido		fdata->td_maxend = end + 1;
46453642Sguido	}
46553642Sguido
46653642Sguido	if (!(tcp->th_flags & TH_ACK)) {  /* Pretend an ack was sent */
46753642Sguido		ack = tdata->td_end;
46853642Sguido		win = 1;
46953642Sguido	} else if (((tcp->th_flags & (TH_ACK|TH_RST)) == (TH_ACK|TH_RST)) &&
47053642Sguido		   (ack == 0)) {
47153642Sguido		/* gross hack to get around certain broken tcp stacks */
47253642Sguido		ack = tdata->td_end;
47353642Sguido	}
47453642Sguido
47553642Sguido	if (seq == end)
47653642Sguido		seq = end = fdata->td_end;
47753642Sguido
47853642Sguido	maxwin = tdata->td_maxwin;
47953642Sguido	ackskew = tdata->td_end - ack;
48053642Sguido
48153642Sguido#define	SEQ_GE(a,b)	((int)((a) - (b)) >= 0)
48253642Sguido#define	SEQ_GT(a,b)	((int)((a) - (b)) > 0)
48353642Sguido	if ((SEQ_GE(fdata->td_maxend, end)) &&
48453642Sguido	    (SEQ_GE(seq + maxwin, fdata->td_end - maxwin)) &&
48553642Sguido/* XXX what about big packets */
48653642Sguido#define MAXACKWINDOW 66000
48753642Sguido	    (ackskew >= -MAXACKWINDOW) &&
48853642Sguido	    (ackskew <= MAXACKWINDOW)) {
48953642Sguido		/* if ackskew < 0 then this should be due to fragented
49053642Sguido		 * packets. There is no way to know the length of the
49153642Sguido		 * total packet in advance.
49253642Sguido		 * We do know the total length from the fragment cache though.
49353642Sguido		 * Note however that there might be more sessions with
49453642Sguido		 * exactly the same source and destination paramters in the
49553642Sguido		 * state cache (and source and destination is the only stuff
49653642Sguido		 * that is saved in the fragment cache). Note further that
49753642Sguido		 * some TCP connections in the state cache are hashed with
49853642Sguido		 * sport and dport as well which makes it not worthwhile to
49953642Sguido		 * look for them.
50053642Sguido		 * Thus, when ackskew is negative but still seems to belong
50153642Sguido		 * to this session, we bump up the destinations end value.
50253642Sguido		 */
50353642Sguido		if (ackskew < 0)
50453642Sguido			tdata->td_end = ack;
50553642Sguido
50653642Sguido		/* update max window seen */
50753642Sguido		if (fdata->td_maxwin < win)
50853642Sguido			fdata->td_maxwin = win;
50953642Sguido		if (SEQ_GT(end, fdata->td_end))
51053642Sguido			fdata->td_end = end;
51153642Sguido		if (SEQ_GE(ack + win, tdata->td_maxend)) {
51253642Sguido			tdata->td_maxend = ack + win;
51353642Sguido			if (win == 0)
51453642Sguido				tdata->td_maxend++;
51553642Sguido		}
51653642Sguido
51753642Sguido		ATOMIC_INC(ips_stats.iss_hits);
51853642Sguido		is->is_pkts++;
51953642Sguido		is->is_bytes += ip->ip_len;
52053642Sguido		/*
52153642Sguido		 * Nearing end of connection, start timeout.
52253642Sguido		 */
52353642Sguido		MUTEX_ENTER(&ipf_rw);
52453642Sguido		fr_tcp_age(&is->is_age, is->is_state, ip, fin, source);
52553642Sguido		MUTEX_EXIT(&ipf_rw);
52653642Sguido		ret = 1;
52753642Sguido	}
52853642Sguido	return ret;
52953642Sguido}
53053642Sguido
53153642Sguido
53253642Sguidostatic int fr_matchsrcdst(is, src, dst, fin, tcp)
53353642Sguidoipstate_t *is;
53453642Sguidostruct in_addr src, dst;
53553642Sguidofr_info_t *fin;
53653642Sguidotcphdr_t *tcp;
53753642Sguido{
53853642Sguido	int ret = 0, rev, out, flags;
53953642Sguido	u_short sp, dp;
54053642Sguido	void *ifp;
54153642Sguido
54253642Sguido	rev = fin->fin_rev = (is->is_dst.s_addr != dst.s_addr);
54353642Sguido	ifp = fin->fin_ifp;
54453642Sguido	out = fin->fin_out;
54553642Sguido
54653642Sguido	if (tcp != NULL) {
54753642Sguido		flags = is->is_flags;
54853642Sguido		sp = tcp->th_sport;
54953642Sguido		dp = tcp->th_dport;
55053642Sguido	} else {
55153642Sguido		flags = 0;
55253642Sguido		sp = 0;
55353642Sguido		dp = 0;
55453642Sguido	}
55553642Sguido
55653642Sguido	if (rev == 0) {
55753642Sguido		if (!out) {
55853642Sguido			if (is->is_ifpin == ifp)
55953642Sguido				ret = 1;
56053642Sguido		} else {
56153642Sguido			if (is->is_ifpout == NULL || is->is_ifpout == ifp)
56253642Sguido				ret = 1;
56353642Sguido		}
56453642Sguido	} else {
56553642Sguido		if (out) {
56653642Sguido			if (is->is_ifpin == ifp)
56753642Sguido				ret = 1;
56853642Sguido		} else {
56953642Sguido			if (is->is_ifpout == NULL || is->is_ifpout == ifp)
57053642Sguido				ret = 1;
57153642Sguido		}
57253642Sguido	}
57353642Sguido	if (ret == 0)
57453642Sguido		return 0;
57553642Sguido	ret = 0;
57653642Sguido
57753642Sguido	if (rev == 0) {
57853642Sguido		if ((is->is_dst.s_addr == dst.s_addr) &&
57953642Sguido		    (is->is_src.s_addr == src.s_addr) &&
58053642Sguido		    (!tcp || ((sp == is->is_sport || flags & FI_W_SPORT) &&
58153642Sguido		     (dp == is->is_dport || flags & FI_W_DPORT)))) {
58253642Sguido			ret = 1;
58353642Sguido		}
58453642Sguido	} else {
58553642Sguido		if ((is->is_dst.s_addr == src.s_addr) &&
58653642Sguido		    (is->is_src.s_addr == dst.s_addr) &&
58753642Sguido		    (!tcp || ((sp == is->is_dport || flags & FI_W_DPORT) &&
58853642Sguido		     (dp == is->is_sport || flags & FI_W_SPORT)))) {
58953642Sguido			ret = 1;
59053642Sguido		}
59153642Sguido	}
59253642Sguido	if (ret == 0)
59353642Sguido		return 0;
59453642Sguido
59553642Sguido	/*
59653642Sguido	 * Whether or not this should be here, is questionable, but the aim
59753642Sguido	 * is to get this out of the main line.
59853642Sguido	 */
59953642Sguido	if (tcp == NULL)
60053642Sguido		flags = is->is_flags & (FI_CMP|(FI_CMP<<4));
60153642Sguido
60253642Sguido	if (((fin->fin_fi.fi_fl & (flags >> 4)) != (flags & FI_CMP)) ||
60353642Sguido	    ((fin->fin_fi.fi_optmsk & is->is_optmsk) != is->is_opt) ||
60453642Sguido	    ((fin->fin_fi.fi_secmsk & is->is_secmsk) != is->is_sec) ||
60553642Sguido	    ((fin->fin_fi.fi_auth & is->is_authmsk) != is->is_auth))
60653642Sguido		return 0;
60753642Sguido
60853642Sguido	if ((flags & (FI_W_SPORT|FI_W_DPORT))) {
60953642Sguido		if ((flags & FI_W_SPORT) != 0) {
61053642Sguido			if (rev == 0) {
61153642Sguido				is->is_sport = sp;
61253642Sguido				is->is_send = htonl(tcp->th_seq);
61353642Sguido			} else {
61453642Sguido				is->is_sport = dp;
61553642Sguido				is->is_send = htonl(tcp->th_ack);
61653642Sguido			}
61753642Sguido			is->is_maxsend = is->is_send + 1;
61853642Sguido		} else if ((flags & FI_W_DPORT) != 0) {
61953642Sguido			if (rev == 0) {
62053642Sguido				is->is_dport = dp;
62153642Sguido				is->is_dend = htonl(tcp->th_ack);
62253642Sguido			} else {
62353642Sguido				is->is_dport = sp;
62453642Sguido				is->is_dend = htonl(tcp->th_seq);
62553642Sguido			}
62653642Sguido			is->is_maxdend = is->is_dend + 1;
62753642Sguido		}
62853642Sguido		is->is_flags &= ~(FI_W_SPORT|FI_W_DPORT);
62953642Sguido	}
63053642Sguido
63153642Sguido	if (!rev) {
63253642Sguido		if (out && (out == is->is_rout)) {
63353642Sguido			if (!is->is_ifpout)
63453642Sguido				is->is_ifpout = ifp;
63553642Sguido		} else {
63653642Sguido			if (!is->is_ifpin)
63753642Sguido				is->is_ifpin = ifp;
63853642Sguido		}
63953642Sguido	} else {
64053642Sguido		if (!out && (out != is->is_rout)) {
64153642Sguido			if (!is->is_ifpin)
64253642Sguido				is->is_ifpin = ifp;
64353642Sguido		} else {
64453642Sguido			if (!is->is_ifpout)
64553642Sguido				is->is_ifpout = ifp;
64653642Sguido		}
64753642Sguido	}
64853642Sguido	return 1;
64953642Sguido}
65053642Sguido
65153642Sguidofrentry_t *fr_checkicmpmatchingstate(ip, fin)
65253642Sguidoip_t *ip;
65353642Sguidofr_info_t *fin;
65453642Sguido{
65553642Sguido	register struct in_addr	dst, src;
65653642Sguido	register ipstate_t *is, **isp;
65753642Sguido	register u_short sport, dport;
65853642Sguido	register u_char	pr;
65953642Sguido	struct icmp *ic;
66053642Sguido	fr_info_t ofin;
66153642Sguido	u_int hv, dest;
66253642Sguido	tcphdr_t *tcp;
66353642Sguido	frentry_t *fr;
66453642Sguido	ip_t *oip;
66553642Sguido	int type;
66653642Sguido
66753642Sguido	/*
66853642Sguido	 * Does it at least have the return (basic) IP header ?
66953642Sguido	 * Only a basic IP header (no options) should be with
67053642Sguido	 * an ICMP error header.
67153642Sguido	 */
67253642Sguido	if ((ip->ip_hl != 5) || (ip->ip_len < ICMPERR_MINPKTLEN))
67353642Sguido		return NULL;
67453642Sguido	ic = (struct icmp *)((char *)ip + fin->fin_hlen);
67553642Sguido	type = ic->icmp_type;
67653642Sguido	/*
67753642Sguido	 * If it's not an error type, then return
67853642Sguido	 */
67953642Sguido	if ((type != ICMP_UNREACH) && (type != ICMP_SOURCEQUENCH) &&
68053642Sguido    	    (type != ICMP_REDIRECT) && (type != ICMP_TIMXCEED) &&
68153642Sguido    	    (type != ICMP_PARAMPROB))
68253642Sguido		return NULL;
68353642Sguido
68453642Sguido	oip = (ip_t *)((char *)fin->fin_dp + ICMPERR_ICMPHLEN);
68553642Sguido	if (ip->ip_len < ICMPERR_MAXPKTLEN + ((oip->ip_hl - 5) << 2))
68653642Sguido		return NULL;
68753642Sguido	if ((oip->ip_p != IPPROTO_TCP) && (oip->ip_p != IPPROTO_UDP))
68853642Sguido		return NULL;
68953642Sguido
69053642Sguido	tcp = (tcphdr_t *)((char *)oip + (oip->ip_hl << 2));
69153642Sguido	dport = tcp->th_dport;
69253642Sguido	sport = tcp->th_sport;
69353642Sguido
69453642Sguido	hv = (pr = oip->ip_p);
69553642Sguido	hv += (src.s_addr = oip->ip_src.s_addr);
69653642Sguido	hv += (dst.s_addr = oip->ip_dst.s_addr);
69753642Sguido	hv += dport;
69853642Sguido	hv += sport;
69953642Sguido	hv %= fr_statesize;
70053642Sguido	/*
70153642Sguido	 * we make an fin entry to be able to feed it to
70253642Sguido	 * matchsrcdst note that not all fields are encessary
70353642Sguido	 * but this is the cleanest way. Note further we fill
70453642Sguido	 * in fin_mp such that if someone uses it we'll get
70553642Sguido	 * a kernel panic. fr_matchsrcdst does not use this.
70653642Sguido	 *
70753642Sguido	 * watch out here, as ip is in host order and oip in network
70853642Sguido	 * order. Any change we make must be undone afterwards.
70953642Sguido	 */
71053642Sguido	oip->ip_len = ntohs(oip->ip_len);
71153642Sguido	fr_makefrip(oip->ip_hl << 2, oip, &ofin);
71253642Sguido	oip->ip_len = htons(oip->ip_len);
71353642Sguido	ofin.fin_ifp = fin->fin_ifp;
71453642Sguido	ofin.fin_out = !fin->fin_out;
71553642Sguido	ofin.fin_mp = NULL; /* if dereferenced, panic XXX */
71653642Sguido	READ_ENTER(&ipf_state);
71753642Sguido	for (isp = &ips_table[hv]; (is = *isp); isp = &is->is_next) {
71853642Sguido		/*
71953642Sguido		 * Only allow this icmp though if the
72053642Sguido		 * encapsulated packet was allowed through the
72153642Sguido		 * other way around. Note that the minimal amount
72253642Sguido		 * of info present does not allow for checking against
72353642Sguido		 * tcp internals such as seq and ack numbers.
72453642Sguido		 */
72553642Sguido		if ((is->is_p == pr) &&
72653642Sguido		    fr_matchsrcdst(is, src, dst, &ofin, tcp)) {
72753642Sguido			fr = is->is_rule;
72853642Sguido			ips_stats.iss_hits++;
72953642Sguido			/*
73053642Sguido			 * we must swap src and dst here because the icmp
73153642Sguido			 * comes the other way around
73253642Sguido			 */
73353642Sguido			dest = (is->is_dst.s_addr != src.s_addr);
73453642Sguido			is->is_pkts++;
73553642Sguido			is->is_bytes += ip->ip_len;
73653642Sguido			/*
73753642Sguido			 * we deliberately do not touch the timeouts
73853642Sguido			 * for the accompanying state table entry.
73953642Sguido			 * It remains to be seen if that is correct. XXX
74053642Sguido			 */
74153642Sguido			RWLOCK_EXIT(&ipf_state);
74253642Sguido			return fr;
74353642Sguido		}
74453642Sguido	}
74553642Sguido	RWLOCK_EXIT(&ipf_state);
74653642Sguido	return NULL;
74753642Sguido}
74853642Sguido
74953642Sguido/*
75053642Sguido * Check if a packet has a registered state.
75153642Sguido */
75253642Sguidofrentry_t *fr_checkstate(ip, fin)
75353642Sguidoip_t *ip;
75453642Sguidofr_info_t *fin;
75553642Sguido{
75653642Sguido	register struct in_addr dst, src;
75753642Sguido	register ipstate_t *is, **isp;
75853642Sguido	register u_char pr;
75953642Sguido	u_int hv, hvm, hlen, tryagain, pass;
76053642Sguido	struct icmp *ic;
76153642Sguido	frentry_t *fr;
76253642Sguido	tcphdr_t *tcp;
76353642Sguido
76453642Sguido	if ((ip->ip_off & IP_OFFMASK) || (fin->fin_fi.fi_fl & FI_SHORT))
76553642Sguido		return NULL;
76653642Sguido
76753642Sguido	is = NULL;
76853642Sguido	hlen = fin->fin_hlen;
76953642Sguido	tcp = (tcphdr_t *)((char *)ip + hlen);
77053642Sguido	ic = (struct icmp *)tcp;
77153642Sguido	hv = (pr = ip->ip_p);
77253642Sguido	hv += (src.s_addr = ip->ip_src.s_addr);
77353642Sguido	hv += (dst.s_addr = ip->ip_dst.s_addr);
77453642Sguido
77553642Sguido	/*
77653642Sguido	 * Search the hash table for matching packet header info.
77753642Sguido	 */
77853642Sguido	switch (ip->ip_p)
77953642Sguido	{
78053642Sguido	case IPPROTO_ICMP :
78153642Sguido		hv += ic->icmp_id;
78253642Sguido		hv += ic->icmp_seq;
78353642Sguido		hv %= fr_statesize;
78453642Sguido		READ_ENTER(&ipf_state);
78553642Sguido		for (isp = &ips_table[hv]; (is = *isp); isp = &is->is_next)
78653642Sguido			if ((is->is_p == pr) &&
78753642Sguido			    (ic->icmp_id == is->is_icmp.ics_id) &&
78853642Sguido			    (ic->icmp_seq == is->is_icmp.ics_seq) &&
78953642Sguido			    fr_matchsrcdst(is, src, dst, fin, NULL)) {
79053642Sguido				if ((is->is_type == ICMP_ECHOREPLY) &&
79153642Sguido				    (ic->icmp_type == ICMP_ECHO))
79253642Sguido					;
79353642Sguido				else if (is->is_type != ic->icmp_type)
79453642Sguido					continue;
79553642Sguido				is->is_age = fr_icmptimeout;
79653642Sguido				break;
79753642Sguido			}
79853642Sguido		if (is != NULL)
79953642Sguido			break;
80053642Sguido		RWLOCK_EXIT(&ipf_state);
80153642Sguido		/*
80253642Sguido		 * No matching icmp state entry. Perhaps this is a
80353642Sguido		 * response to another state entry.
80453642Sguido		 */
80553642Sguido		fr = fr_checkicmpmatchingstate(ip, fin);
80653642Sguido		if (fr)
80753642Sguido			return fr;
80853642Sguido		break;
80953642Sguido	case IPPROTO_TCP :
81053642Sguido	    {
81153642Sguido		register u_short dport = tcp->th_dport, sport = tcp->th_sport;
81253642Sguido
81353642Sguido		tryagain = 0;
81453642Sguidoretry_tcp:
81553642Sguido		hvm = hv % fr_statesize;
81653642Sguido		WRITE_ENTER(&ipf_state);
81753642Sguido		for (isp = &ips_table[hvm]; (is = *isp);
81853642Sguido		     isp = &is->is_next)
81953642Sguido			if ((is->is_p == pr) &&
82053642Sguido			    fr_matchsrcdst(is, src, dst, fin, tcp)) {
82153642Sguido				if (fr_tcpstate(is, fin, ip, tcp)) {
82253642Sguido					break;
82353642Sguido#ifndef	_KERNEL
82453642Sguido					if (tcp->th_flags & TCP_CLOSE) {
82553642Sguido						*isp = is->is_next;
82653642Sguido						isp = &ips_table[hvm];
82753642Sguido						if (ips_table[hvm] == NULL)
82853642Sguido							ips_stats.iss_inuse--;
82953642Sguido						fr_delstate(is);
83053642Sguido						ips_num--;
83153642Sguido					}
83253642Sguido#endif
83353642Sguido					break;
83453642Sguido				}
83553642Sguido				is = NULL;
83653642Sguido				break;
83753642Sguido			}
83853642Sguido		if (is != NULL)
83953642Sguido			break;
84053642Sguido		RWLOCK_EXIT(&ipf_state);
84153642Sguido		hv += dport;
84253642Sguido		hv += sport;
84353642Sguido		if (tryagain == 0) {
84453642Sguido			tryagain = 1;
84553642Sguido			goto retry_tcp;
84653642Sguido		}
84753642Sguido		break;
84853642Sguido	    }
84953642Sguido	case IPPROTO_UDP :
85053642Sguido	    {
85153642Sguido		register u_short dport = tcp->th_dport, sport = tcp->th_sport;
85253642Sguido
85353642Sguido		tryagain = 0;
85453642Sguidoretry_udp:
85553642Sguido		hvm = hv % fr_statesize;
85653642Sguido		/*
85753642Sguido		 * Nothing else to match on but ports. and IP#'s
85853642Sguido		 */
85953642Sguido		READ_ENTER(&ipf_state);
86053642Sguido		for (is = ips_table[hvm]; is; is = is->is_next)
86153642Sguido			if ((is->is_p == pr) &&
86253642Sguido			    fr_matchsrcdst(is, src, dst, fin, tcp)) {
86353642Sguido				is->is_age = fr_udptimeout;
86453642Sguido				break;
86553642Sguido			}
86653642Sguido		if (is != NULL)
86753642Sguido			break;
86853642Sguido		RWLOCK_EXIT(&ipf_state);
86953642Sguido		hv += dport;
87053642Sguido		hv += sport;
87153642Sguido		if (tryagain == 0) {
87253642Sguido			tryagain = 1;
87353642Sguido			goto retry_udp;
87453642Sguido		}
87553642Sguido		break;
87653642Sguido	    }
87753642Sguido	default :
87853642Sguido		break;
87953642Sguido	}
88053642Sguido	if (is == NULL) {
88153642Sguido		ATOMIC_INC(ips_stats.iss_miss);
88253642Sguido		return NULL;
88353642Sguido	}
88453642Sguido	MUTEX_ENTER(&ipf_rw);
88553642Sguido	is->is_bytes += ip->ip_len;
88653642Sguido	ips_stats.iss_hits++;
88753642Sguido	is->is_pkts++;
88853642Sguido	MUTEX_EXIT(&ipf_rw);
88953642Sguido	fr = is->is_rule;
89053642Sguido	fin->fin_fr = fr;
89153642Sguido	pass = is->is_pass;
89253642Sguido	RWLOCK_EXIT(&ipf_state);
89353642Sguido	if (fin->fin_fi.fi_fl & FI_FRAG)
89453642Sguido		ipfr_newfrag(ip, fin, pass ^ FR_KEEPSTATE);
89553642Sguido	return fr;
89653642Sguido}
89753642Sguido
89853642Sguido
89953642Sguidostatic void fr_delstate(is)
90053642Sguidoipstate_t *is;
90153642Sguido{
90253642Sguido	frentry_t *fr;
90353642Sguido
90453642Sguido	fr = is->is_rule;
90553642Sguido	if (fr != NULL) {
90653642Sguido		ATOMIC_DEC(fr->fr_ref);
90753642Sguido		if (fr->fr_ref == 0)
90853642Sguido			KFREE(fr);
90953642Sguido	}
91053642Sguido	KFREE(is);
91153642Sguido}
91253642Sguido
91353642Sguido
91453642Sguido/*
91553642Sguido * Free memory in use by all state info. kept.
91653642Sguido */
91753642Sguidovoid fr_stateunload()
91853642Sguido{
91953642Sguido	register int i;
92053642Sguido	register ipstate_t *is, **isp;
92153642Sguido
92253642Sguido	WRITE_ENTER(&ipf_state);
92353642Sguido	for (i = fr_statesize - 1; i >= 0; i--)
92453642Sguido		for (isp = &ips_table[i]; (is = *isp); ) {
92553642Sguido			*isp = is->is_next;
92653642Sguido			fr_delstate(is);
92753642Sguido			ips_num--;
92853642Sguido		}
92953642Sguido	ips_stats.iss_inuse = 0;
93053642Sguido	ips_num = 0;
93153642Sguido	RWLOCK_EXIT(&ipf_state);
93253642Sguido	KFREES(ips_table, fr_statesize * sizeof(ipstate_t *));
93353642Sguido	ips_table = NULL;
93453642Sguido}
93553642Sguido
93653642Sguido
93753642Sguido/*
93853642Sguido * Slowly expire held state for thingslike UDP and ICMP.  Timeouts are set
93953642Sguido * in expectation of this being called twice per second.
94053642Sguido */
94153642Sguidovoid fr_timeoutstate()
94253642Sguido{
94353642Sguido	register int i;
94453642Sguido	register ipstate_t *is, **isp;
94553642Sguido#if defined(_KERNEL) && !SOLARIS
94653642Sguido	int s;
94753642Sguido#endif
94853642Sguido
94953642Sguido	SPL_NET(s);
95053642Sguido	WRITE_ENTER(&ipf_state);
95153642Sguido	for (i = fr_statesize - 1; i >= 0; i--)
95253642Sguido		for (isp = &ips_table[i]; (is = *isp); )
95353642Sguido			if (is->is_age && !--is->is_age) {
95453642Sguido				*isp = is->is_next;
95553642Sguido				if (is->is_p == IPPROTO_TCP)
95653642Sguido					ips_stats.iss_fin++;
95753642Sguido				else
95853642Sguido					ips_stats.iss_expire++;
95953642Sguido				if (ips_table[i] == NULL)
96053642Sguido					ips_stats.iss_inuse--;
96153642Sguido#ifdef	IPFILTER_LOG
96253642Sguido				ipstate_log(is, ISL_EXPIRE);
96353642Sguido#endif
96453642Sguido				fr_delstate(is);
96553642Sguido				ips_num--;
96653642Sguido			} else
96753642Sguido				isp = &is->is_next;
96853642Sguido	RWLOCK_EXIT(&ipf_state);
96953642Sguido	SPL_X(s);
97053642Sguido}
97153642Sguido
97253642Sguido
97353642Sguido/*
97453642Sguido * Original idea freom Pradeep Krishnan for use primarily with NAT code.
97553642Sguido * (pkrishna@netcom.com)
97653642Sguido */
97753642Sguidovoid fr_tcp_age(age, state, ip, fin, dir)
97853642Sguidou_long *age;
97953642Sguidou_char *state;
98053642Sguidoip_t *ip;
98153642Sguidofr_info_t *fin;
98253642Sguidoint dir;
98353642Sguido{
98453642Sguido	tcphdr_t *tcp = (tcphdr_t *)fin->fin_dp;
98553642Sguido	u_char flags = tcp->th_flags;
98653642Sguido	int dlen, ostate;
98753642Sguido
98853642Sguido	ostate = state[1 - dir];
98953642Sguido
99053642Sguido	dlen = ip->ip_len - fin->fin_hlen - (tcp->th_off << 2);
99153642Sguido
99253642Sguido	if (flags & TH_RST) {
99353642Sguido		if (!(tcp->th_flags & TH_PUSH) && !dlen) {
99453642Sguido			*age = fr_tcpclosed;
99553642Sguido			state[dir] = TCPS_CLOSED;
99653642Sguido		} else {
99753642Sguido			*age = fr_tcpclosewait;
99853642Sguido			state[dir] = TCPS_CLOSE_WAIT;
99953642Sguido		}
100053642Sguido		return;
100153642Sguido	}
100253642Sguido
100353642Sguido	*age = fr_tcptimeout; /* 1 min */
100453642Sguido
100553642Sguido	switch(state[dir])
100653642Sguido	{
100753642Sguido	case TCPS_CLOSED:
100853642Sguido		if ((flags & (TH_FIN|TH_SYN|TH_RST|TH_ACK)) == TH_ACK) {
100953642Sguido			state[dir] = TCPS_ESTABLISHED;
101053642Sguido			*age = fr_tcpidletimeout;
101153642Sguido		}
101253642Sguido	case TCPS_FIN_WAIT_2:
101353642Sguido		if ((flags & TH_OPENING) == TH_OPENING)
101453642Sguido			state[dir] = TCPS_SYN_RECEIVED;
101553642Sguido		else if (flags & TH_SYN)
101653642Sguido			state[dir] = TCPS_SYN_SENT;
101753642Sguido		break;
101853642Sguido	case TCPS_SYN_RECEIVED:
101953642Sguido	case TCPS_SYN_SENT:
102053642Sguido		if ((flags & (TH_FIN|TH_ACK)) == TH_ACK) {
102153642Sguido			state[dir] = TCPS_ESTABLISHED;
102253642Sguido			*age = fr_tcpidletimeout;
102353642Sguido		} else if ((flags & (TH_FIN|TH_ACK)) == (TH_FIN|TH_ACK)) {
102453642Sguido			state[dir] = TCPS_CLOSE_WAIT;
102553642Sguido			if (!(flags & TH_PUSH) && !dlen &&
102653642Sguido			    ostate > TCPS_ESTABLISHED)
102753642Sguido				*age  = fr_tcplastack;
102853642Sguido			else
102953642Sguido				*age  = fr_tcpclosewait;
103053642Sguido		}
103153642Sguido		break;
103253642Sguido	case TCPS_ESTABLISHED:
103353642Sguido		if (flags & TH_FIN) {
103453642Sguido			state[dir] = TCPS_CLOSE_WAIT;
103553642Sguido			if (!(flags & TH_PUSH) && !dlen &&
103653642Sguido			    ostate > TCPS_ESTABLISHED)
103753642Sguido				*age  = fr_tcplastack;
103853642Sguido			else
103953642Sguido				*age  = fr_tcpclosewait;
104053642Sguido		} else {
104153642Sguido			if (ostate < TCPS_CLOSE_WAIT)
104253642Sguido				*age = fr_tcpidletimeout;
104353642Sguido		}
104453642Sguido		break;
104553642Sguido	case TCPS_CLOSE_WAIT:
104653642Sguido		if ((flags & TH_FIN) && !(flags & TH_PUSH) && !dlen &&
104753642Sguido		    ostate > TCPS_ESTABLISHED) {
104853642Sguido			*age  = fr_tcplastack;
104953642Sguido			state[dir] = TCPS_LAST_ACK;
105053642Sguido		} else
105153642Sguido			*age  = fr_tcpclosewait;
105253642Sguido		break;
105353642Sguido	case TCPS_LAST_ACK:
105453642Sguido		if (flags & TH_ACK) {
105553642Sguido			state[dir] = TCPS_FIN_WAIT_2;
105653642Sguido			if (!(flags & TH_PUSH) && !dlen &&
105753642Sguido			    ostate > TCPS_ESTABLISHED)
105853642Sguido				*age  = fr_tcplastack;
105953642Sguido			else {
106053642Sguido				*age  = fr_tcpclosewait;
106153642Sguido				state[dir] = TCPS_CLOSE_WAIT;
106253642Sguido			}
106353642Sguido		}
106453642Sguido		break;
106553642Sguido	}
106653642Sguido}
106753642Sguido
106853642Sguido
106953642Sguido#ifdef	IPFILTER_LOG
107053642Sguidovoid ipstate_log(is, type)
107153642Sguidostruct ipstate *is;
107253642Sguidou_int type;
107353642Sguido{
107453642Sguido	struct	ipslog	ipsl;
107553642Sguido	void *items[1];
107653642Sguido	size_t sizes[1];
107753642Sguido	int types[1];
107853642Sguido
107953642Sguido	ipsl.isl_type = type;
108053642Sguido	ipsl.isl_pkts = is->is_pkts;
108153642Sguido	ipsl.isl_bytes = is->is_bytes;
108253642Sguido	ipsl.isl_src = is->is_src;
108353642Sguido	ipsl.isl_dst = is->is_dst;
108453642Sguido	ipsl.isl_p = is->is_p;
108553642Sguido	ipsl.isl_flags = is->is_flags;
108653642Sguido	if (ipsl.isl_p == IPPROTO_TCP || ipsl.isl_p == IPPROTO_UDP) {
108753642Sguido		ipsl.isl_sport = is->is_sport;
108853642Sguido		ipsl.isl_dport = is->is_dport;
108953642Sguido		if (ipsl.isl_p == IPPROTO_TCP) {
109053642Sguido			ipsl.isl_state[0] = is->is_state[0];
109153642Sguido			ipsl.isl_state[1] = is->is_state[1];
109253642Sguido		}
109353642Sguido	} else if (ipsl.isl_p == IPPROTO_ICMP)
109453642Sguido		ipsl.isl_itype = is->is_icmp.ics_type;
109553642Sguido	else {
109653642Sguido		ipsl.isl_ps.isl_filler[0] = 0;
109753642Sguido		ipsl.isl_ps.isl_filler[1] = 0;
109853642Sguido	}
109953642Sguido	items[0] = &ipsl;
110053642Sguido	sizes[0] = sizeof(ipsl);
110153642Sguido	types[0] = 0;
110253642Sguido
110353642Sguido	(void) ipllog(IPL_LOGSTATE, NULL, items, sizes, types, 1);
110453642Sguido}
110553642Sguido#endif
1106