/*
 * Copyright (C) 1982, 1986, 1988, 1990, 1993
 *	The Regents of the University of California.  All rights reserved.
 * Copyright (C) 2006 Pavel Fedin
 * Copyright (C) 2005-2026 The AROS Dev Team
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by the University of
 *	California, Berkeley and its contributors.
 * 4. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 *	@(#)tcp_subr.c	8.1 (Berkeley) 6/10/93
 * $Id$
 */

#include <conf.h>

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/malloc.h>
#include <sys/mbuf.h>
#include <sys/socket.h>
#include <sys/socketvar.h>
#include <sys/protosw.h>
#include <sys/domain.h>
#include <sys/errno.h>
#include <sys/queue.h>

#include <net/route.h>
#include <net/if.h>

#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <netinet/ip6.h>
#include <netinet/in_pcb.h>
#include <netinet/in_var.h>
#include <netinet/ip_var.h>
#include <netinet/ip_icmp.h>
#include <netinet/in_cksum_protos.h>
#include <netinet/tcp.h>
#include <netinet/tcp_fsm.h>
#include <netinet/tcp_seq.h>
#include <netinet/tcp_timer.h>
#include <netinet/tcp_var.h>
#include <netinet/tcpip.h>
#ifdef TCPDEBUG
#include <netinet/tcp_debug.h>
#endif

#include "tcp_compat.h"
#include <netinet/tcp_cc.h>
#include "tcp_syncache.h"

#include <kern/amiga_includes.h>

#include <kern/kern_subr_protos.h>

/*
 * Dual-stack helper: true if the socket bound to this PCB is an AF_INET6
 * (IPv6) socket.  Derived from the socket's protocol domain so it is correct
 * for listen children and syncache-created sockets too.
 */
int
tcp_isipv6(struct inpcb *inp)
{
#if INET6
    if(inp && inp->inp_socket && inp->inp_socket->so_proto &&
            inp->inp_socket->so_proto->pr_domain) {
        if(inp->inp_socket->so_proto->pr_domain->dom_family != AF_INET6)
            return (0);
        /*
         * A dual-stack socket holding an IPv4-mapped peer is carrying an
         * IPv4 connection - it must be framed and routed as IPv4, even
         * though the socket itself is AF_INET6.
         */
        if(IN6_IS_ADDR_V4MAPPED(&inp->inp_faddr6))
            return (0);
        return (1);
    }
#endif
    return (0);
}

#if INET6
/*
 * Dual-stack output helper.  The mbuf m must already start at the TCP header
 * (TCP header + options + data), with m_len / m_pkthdr.len == tlen.  Prepend
 * and fill an IPv6 header, compute the TCP checksum over the IPv6 pseudo-header
 * and hand the datagram to ip6_output().  Shared by tcp_output(), tcp_respond()
 * and the SYN cache, mirroring udp6_output()'s construction.
 */
int
tcp_v6output(struct mbuf *m, struct in6_addr *laddr, struct in6_addr *faddr,
    int hlim, int tlen, struct inpcb *inp)
{
    struct ip6_hdr *ip6;
    struct tcphdr  *th;
    struct ip6_moptions *im6o = inp ? inp->in6p_moptions : NULL;

    M_PREPEND(m, sizeof(struct ip6_hdr), M_DONTWAIT);
    if(m == NULL)
        return (ENOBUFS);
    /*
     * M_PREPEND may have allocated a fresh mbuf for the IPv6 header, leaving
     * the TCP header in the following mbuf.  Pull the IPv6 + TCP headers into
     * one contiguous mbuf so the th_sum store below lands on the real TCP
     * header (and not on stray bytes of a separate header mbuf).
     */
    if(m->m_len < sizeof(struct ip6_hdr) + sizeof(struct tcphdr)) {
        m = m_pullup(m, sizeof(struct ip6_hdr) + sizeof(struct tcphdr));
        if(m == NULL)
            return (ENOBUFS);
    }
    ip6 = mtod(m, struct ip6_hdr *);
    ip6->ip6_flow = 0;
    ip6->ip6_vfc  = IPV6_VERSION;	/* must follow ip6_flow (they overlap) */
    ip6->ip6_plen = htons((u_short)tlen);
    ip6->ip6_nxt  = IPPROTO_TCP;
    ip6->ip6_hlim = (hlim > 0) ? (u_int8_t)hlim : (u_int8_t)ip6_defhlim;
    ip6->ip6_src  = *laddr;
    ip6->ip6_dst  = *faddr;

    th = (struct tcphdr *)(ip6 + 1);
    th->th_sum = 0;
    th->th_sum = in6_cksum(m, IPPROTO_TCP, sizeof(struct ip6_hdr),
                           (u_int32_t)tlen);

    return (ip6_output(m, (struct mbuf *)NULL, (struct route *)NULL, 0,
                       im6o, (struct ifnet **)NULL, inp));
}
#endif /* INET6 */
u_int32_t tcp_secret_key[4];

/* Mix-based hash for ISN generation (Murmur-style) */
u_int32_t
tcp_isn_hash(u_int32_t a, u_int32_t b, u_int32_t c, u_int32_t d,
    u_int32_t *key)
{
	u_int32_t h = key[0];
	h ^= a; h = (h << 13) | (h >> 19); h *= 0x5bd1e995;
	h ^= b; h = (h << 13) | (h >> 19); h *= 0x5bd1e995;
	h ^= c; h = (h << 13) | (h >> 19); h *= 0x5bd1e995;
	h ^= d; h = (h << 13) | (h >> 19); h *= 0x5bd1e995;
	h ^= key[1]; h ^= key[2]; h ^= key[3];
	h ^= h >> 16;
	return h;
}

/*
 * Generate a per-connection ISN per RFC 6528:
 *   ISN = M + F(localip, localport, remoteip, remoteport, secret_key)
 * where M is the monotonic tcp_iss clock component.
 */
tcp_seq
tcp_new_isn(struct inpcb *inp)
{
	u_int32_t hash;

#if INET6
	if(tcp_isipv6(inp)) {
		u_int32_t *la = (u_int32_t *)&inp->inp_laddr6;
		u_int32_t *fa = (u_int32_t *)&inp->inp_faddr6;
		hash = tcp_isn_hash(
		    la[0] ^ la[1] ^ la[2] ^ la[3],
		    (u_int32_t)inp->inp_lport,
		    fa[0] ^ fa[1] ^ fa[2] ^ fa[3],
		    (u_int32_t)inp->inp_fport,
		    tcp_secret_key);
		return ((tcp_seq)tcp_iss + hash);
	}
#endif

	hash = tcp_isn_hash(
	    inp->inp_laddr.s_addr,
	    (u_int32_t)inp->inp_lport,
	    inp->inp_faddr.s_addr,
	    (u_int32_t)inp->inp_fport,
	    tcp_secret_key);

	return ((tcp_seq)tcp_iss + hash);
}

/*
 * SYN cookie support (RFC 4987).
 *
 * Cookie = ISN encoding:
 *   bits 31..8: hash(src_ip, src_port, dst_ip, dst_port, secret, counter)
 *   bits  7..5: MSS index (8 common MSS values)
 *   bits  4..0: counter mod 32 (to validate freshness)
 *
 * The counter is tcp_iss / TCP_ISSINCR (approximately a slow-timer tick count).
 */

/* Common MSS values for encoding (3 bits = 8 entries) */
static u_int16_t tcp_sc_msstab[] = {
	216, 536, 1024, 1220, 1360, 1440, 1460, 8960
};
#define TCP_SC_MSS_ENTRIES (sizeof(tcp_sc_msstab)/sizeof(tcp_sc_msstab[0]))

int tcp_syncookies = 1;		/* SYN cookies enabled by default */

static int
tcp_sc_mss_encode(u_int16_t mss)
{
	int i, best = 0;

	for(i = 0; i < TCP_SC_MSS_ENTRIES; i++) {
		if(tcp_sc_msstab[i] <= mss)
			best = i;
	}
	return best;
}

tcp_seq
tcp_syncookie_generate(struct in_addr src, struct in_addr dst,
    u_int16_t sport, u_int16_t dport, u_int16_t mss)
{
	u_int32_t counter;
	u_int32_t hash;
	int mss_idx;

	counter = (u_int32_t)(tcp_iss / TCP_ISSINCR);
	mss_idx = tcp_sc_mss_encode(mss);

	hash = tcp_isn_hash(src.s_addr, dst.s_addr,
	    ((u_int32_t)sport << 16) | dport,
	    counter, tcp_secret_key);

	return ((hash & 0xFFFFFF00) |
	    ((mss_idx & 0x7) << 5) | (counter & 0x1F));
}

u_int16_t
tcp_syncookie_validate(struct in_addr src, struct in_addr dst,
    u_int16_t sport, u_int16_t dport, tcp_seq ack_seq)
{
	tcp_seq cookie = ack_seq - 1;
	u_int32_t counter_now;
	u_int32_t counter_then;
	u_int32_t hash;
	int mss_idx;
	int diff;
	u_int32_t try_counter;
	tcp_seq expected;

	counter_now  = (u_int32_t)(tcp_iss / TCP_ISSINCR);
	counter_then = cookie & 0x1F;
	mss_idx      = (cookie >> 5) & 0x7;

	diff = (counter_now & 0x1F) - counter_then;
	if(diff < 0) diff += 32;
	if(diff > 6) /* ~3 seconds window */
		return 0;

	/* Try current counter epoch */
	try_counter = (counter_now & ~0x1F) | counter_then;
	hash = tcp_isn_hash(src.s_addr, dst.s_addr,
	    ((u_int32_t)sport << 16) | dport,
	    try_counter, tcp_secret_key);
	expected = (hash & 0xFFFFFF00) |
	    ((mss_idx & 0x7) << 5) | (counter_then & 0x1F);
	if(expected == cookie)
		return tcp_sc_msstab[mss_idx];

	/* Try previous epoch in case counter wrapped */
	if(try_counter >= 32) {
		try_counter -= 32;
		hash = tcp_isn_hash(src.s_addr, dst.s_addr,
		    ((u_int32_t)sport << 16) | dport,
		    try_counter, tcp_secret_key);
		expected = (hash & 0xFFFFFF00) |
		    ((mss_idx & 0x7) << 5) | (counter_then & 0x1F);
		if(expected == cookie)
			return tcp_sc_msstab[mss_idx];
	}

	return 0;  /* invalid cookie */
}

/* patchable/settable parameters for tcp */
int	ip_defttl = 60;				  /* default time to live for TCP segs */
int 	tcp_mssdflt = 1460;			  /* Ethernet MSS (1500 - 40) */
int 	tcp_rttdflt = TCPTV_SRTTDFLT / PR_SLOWHZ;
int	tcp_do_rfc1323 = 1;
int	tcp_do_rfc1644 = 1;
int	tcp_do_sack = 1;	/* enable SACK (RFC 2018) */
int	tcp_do_newreno = 1;	/* enable NewReno (RFC 3782) */
int	tcp_do_ecn = 0;		/* enable ECN (RFC 3168) - off by default */
int	tcp_do_prr = 0;		/* enable PRR (RFC 6937) - off by default */
static	void tcp_cleartaocache(void);

extern u_char inetctlerrmap[];
extern struct in_addr zeroin_addr;

/*
 * Target size of TCP PCB hash table. Will be rounded down to a prime
 * number.
 */
#ifndef TCBHASHSIZE
#define TCBHASHSIZE	128
#endif

/*
 * Tcp initialization
 */
void
tcp_init()
{
    /*
     * Seed the ISN secret key and initial sequence number from
     * available entropy (system time, task pointer).
     */
    {
        struct timeval tv;
        APTR task;
        u_int32_t seed;

        GetSysTime(&tv);
        task = FindTask(NULL);
        seed = (u_int32_t)tv.tv_secs ^ ((u_int32_t)tv.tv_micro << 16)
             ^ (u_int32_t)(IPTR)task;

        /* Fill secret key with mixed entropy */
        tcp_secret_key[0] = seed * 0x5bd1e995;
        seed ^= seed >> 16;
        tcp_secret_key[1] = seed * 0x85ebca6b;
        seed ^= seed >> 13;
        tcp_secret_key[2] = seed * 0xc2b2ae35;
        seed ^= seed >> 16;
        tcp_secret_key[3] = seed * 0xcc9e2d51;

        /* Start tcp_iss at an unpredictable value */
        tcp_iss = (int)(tcp_secret_key[0] ^ tcp_secret_key[2]);
    }
    tcp_ccgen = 1;
    tcp_cc_init();
    tcp_cleartaocache();
    LIST_INIT(&tcb);
    tcbinfo.listhead = &tcb;
    tcbinfo.hashbase = phashinit(TCBHASHSIZE, M_PCB, &tcbinfo.hashsize);
    syncache_init();
    if(max_protohdr < sizeof(struct tcpiphdr))
        max_protohdr = sizeof(struct tcpiphdr);
    if(max_linkhdr + sizeof(struct tcpiphdr) > MHLEN)
        panic("tcp_init");
}

/*
 * Create template to be used to send tcp packets on a connection.
 * Call after host entry created, allocates an mbuf and fills
 * in a skeletal tcp/ip header, minimizing the amount of work
 * necessary when the connection is used.
 */
struct tcpiphdr *
tcp_template(tp)
struct tcpcb *tp;
{
    register struct inpcb *inp = tp->t_inpcb;
    register struct tcpiphdr *n;

    if((n = tp->t_template) == 0) {
        register struct mbuf *m;
        m = m_get(M_DONTWAIT, MT_HEADER);
        if(m == NULL)
            return (0);
        m->m_len = sizeof(struct tcpiphdr);
        n = mtod(m, struct tcpiphdr *);
    }
    bzero(n->ti_x1, sizeof(n->ti_x1));
    n->ti_pr = IPPROTO_TCP;
    n->ti_len = htons(sizeof(struct tcpiphdr) - sizeof(struct ip));
    n->ti_src = inp->inp_laddr;
    n->ti_dst = inp->inp_faddr;
    n->ti_sport = inp->inp_lport;
    n->ti_dport = inp->inp_fport;
    n->ti_seq = 0;
    n->ti_ack = 0;
    n->ti_x2 = 0;
    n->ti_off = 5;
    n->ti_flags = 0;
    n->ti_win = 0;
    n->ti_sum = 0;
    n->ti_urp = 0;
    return (n);
}

/*
 * Send a single message to the TCP at address specified by
 * the given TCP/IP header.  If m == 0, then we make a copy
 * of the tcpiphdr at ti and send directly to the addressed host.
 * This is used to force keep alive messages out using the TCP
 * template for a connection tp->t_template.  If flags are given
 * then we send a message back to the TCP which originated the
 * segment ti, and discard the mbuf containing it and any other
 * attached mbufs.
 *
 * In any case the ack and sequence number of the transmitted
 * segment are as specified by the parameters.
 */
void
tcp_respond(tp, ti, m, ack, seq, flags, isipv6, laddr6, faddr6)
struct tcpcb *tp;
register struct tcpiphdr *ti;
register struct mbuf *m;
tcp_seq ack, seq;
int flags;
int isipv6;
struct in6_addr *laddr6, *faddr6;
{
    register int tlen;
    int win = 0;
    struct route *ro = 0;

#if INET6
    /*
     * Auto-detect IPv6 from the connection when the caller did not pass an
     * explicit family (e.g. the keep-alive timer).  The send-orientation
     * addresses then come from the PCB.  Callers without a PCB (a RST for a
     * stray segment) pass isipv6 and laddr6/faddr6 themselves.
     */
    if(!isipv6 && tp && tcp_isipv6(tp->t_inpcb)) {
        isipv6 = 1;
        laddr6 = &tp->t_inpcb->inp_laddr6;
        faddr6 = &tp->t_inpcb->inp_faddr6;
    }
#endif

    if(tp) {
        win = sbspace(&tp->t_inpcb->inp_socket->so_rcv);
        ro = &tp->t_inpcb->inp_route;
    }
    if(m == 0) {
        m = m_gethdr(M_DONTWAIT, MT_HEADER);
        if(m == NULL)
            return;
#ifdef TCP_COMPAT_42
        tlen = 1;
#else
        tlen = 0;
#endif
        m->m_data += max_linkhdr;
        *mtod(m, struct tcpiphdr *) = *ti;
        ti = mtod(m, struct tcpiphdr *);
        flags = TH_ACK;
    } else {
        m_freem(m->m_next);
        m->m_next = 0;
        m->m_data = (caddr_t)ti;
        m->m_len = sizeof(struct tcpiphdr);
        tlen = 0;
#define xchg(a,b,type) { type t; t=a; a=b; b=t; }
        /*
         * Swap the ports to bounce the segment back to its origin.  For IPv4
         * the addresses are swapped in the overlay too; for IPv6 the source
         * and destination are taken from laddr6/faddr6 (already in our send
         * orientation) by tcp_v6output() below.
         */
#if INET6
        if(!isipv6)
#endif
            xchg(ti->ti_dst.s_addr, ti->ti_src.s_addr, u_long);
        xchg(ti->ti_dport, ti->ti_sport, u_short);
#undef xchg
    }
    ti->ti_len = htons((u_short)(sizeof(struct tcphdr) + tlen));
    tlen += sizeof(struct tcpiphdr);
    m->m_len = tlen;
    m->m_pkthdr.len = tlen;
    m->m_pkthdr.rcvif = (struct ifnet *) 0;
    bzero(ti->ti_x1, sizeof(ti->ti_x1));
    ti->ti_seq = htonl(seq);
    ti->ti_ack = htonl(ack);
    ti->ti_x2 = 0;
    ti->ti_off = sizeof(struct tcphdr) >> 2;
    ti->ti_flags = flags;
    /*
     * Clamp the advertised window to the 16-bit field.  The window is only
     * scaled on an established connection (rcv_scale != 0); on a listen
     * socket rcv_scale is 0, and with large socket buffers the raw value
     * would otherwise overflow the u_short field (e.g. 128KB -> 0).
     */
    if(tp)
        win >>= tp->rcv_scale;
    if(win > TCP_MAXWIN)
        win = TCP_MAXWIN;
    else if(win < 0)
        win = 0;
    ti->ti_win = htons((u_short)win);
    ti->ti_urp = 0;
#if INET6
    if(isipv6) {
        /*
         * Slide off the IPv4 ipovly overlay so the mbuf starts at the TCP
         * header, then build and transmit the IPv6 datagram.  tlen still
         * counts the overlay; the IPv6 TCP length excludes it.  The hop limit
         * comes from the connection (in6p_hops) when there is a PCB, else 0
         * (tcp_v6output() substitutes the default).
         */
        int t6 = tlen - sizeof(struct ipovly);
        int hlim = tp ? tp->t_inpcb->in6p_hops : 0;

        m->m_data       += sizeof(struct ipovly);
        m->m_len         = t6;
        m->m_pkthdr.len  = t6;
        (void) tcp_v6output(m, laddr6, faddr6, hlim, t6,
                            tp ? tp->t_inpcb : NULL);
        return;
    }
#endif
    ti->ti_sum = 0;
    ti->ti_sum = in_cksum(m, tlen);
    ((struct ip *)ti)->ip_len = tlen;
    ((struct ip *)ti)->ip_ttl = ip_defttl;
#ifdef TCPDEBUG
    if(tp == NULL || (tp->t_inpcb->inp_socket->so_options & SO_DEBUG))
        tcp_trace(TA_OUTPUT, 0, tp, ti, 0);
#endif
#ifdef ENABLE_MULTICAST
    (void) ip_output(m, (struct mbuf *)NULL, ro, 0, (struct ip_moptions *)NULL);
#else
    (void) ip_output(m, (struct mbuf *)NULL, ro, 0);
#endif
}

/*
 * Create a new TCP control block, making an
 * empty reassembly queue and hooking it to the argument
 * protocol control block.
 */
struct tcpcb *
tcp_newtcpcb(inp)
struct inpcb *inp;
{
    register struct tcpcb *tp;

    tp = bsd_malloc(sizeof(*tp), M_PCB, M_NOWAIT);
    if(tp == NULL)
        return ((struct tcpcb *)0);
    bzero((char *) tp, sizeof(struct tcpcb));
    tp->t_segq = NULL;
    tp->t_maxseg = tp->t_maxopd = tcp_mssdflt;

    if(tcp_do_rfc1323)
        tp->t_flags = (TF_REQ_SCALE | TF_REQ_TSTMP);
    if(tcp_do_rfc1644)
        tp->t_flags |= TF_REQ_CC;
    tp->t_flagsext = 0;
    if(tcp_do_newreno)
        tp->t_flagsext |= TF_NEWRENO;
    if(tcp_do_sack)
        tp->t_flags |= TF_SACK_PERMIT;	/* request SACK in SYN */
    tp->t_num_sack_blks = 0;
    tp->t_num_dsack_blks = 0;
    tp->snd_fack = 0;
    tp->snd_awnd = 0;
    /* PRR / SACK-recovery state (RFC 6937 / RFC 6675); bzero'd above. */
    tp->snd_sacked = 0;
    tp->snd_prr_out = 0;
    tp->snd_prr_delivered = 0;
    tp->snd_prr_recover_fs = 0;
    tp->snd_prr_hiack = 0;
    tp->snd_ecn_recover = 0;
    tp->t_inpcb = inp;
    /*
     * Init srtt to TCPTV_SRTTBASE (0), so we can tell that we have no
     * rtt estimate.  Set rttvar so that srtt + 2 * rttvar gives
     * reasonable initial retransmit time.
     */
    tp->t_srtt = TCPTV_SRTTBASE;
    tp->t_rttvar = tcp_rttdflt * PR_SLOWHZ << 2;
    tp->t_rttmin = TCPTV_MIN;
    TCPT_RANGESET(tp->t_rxtcur,
                  ((TCPTV_SRTTBASE >> 2) + (TCPTV_SRTTDFLT << 2)) >> 1,
                  TCPTV_MIN, TCPTV_REXMTMAX);
    tp->snd_cwnd = TCP_MAXWIN << TCP_MAX_WINSHIFT;
    tp->snd_ssthresh = TCP_MAXWIN << TCP_MAX_WINSHIFT;
    /* Initialize congestion control algorithm */
    tp->t_cc_algo = tcp_cc_default;
    CC_INIT(tp);
    inp->inp_ip.ip_ttl = ip_defttl;
    inp->inp_ppcb = (caddr_t)tp;
    return (tp);
}

/*
 * Drop a TCP connection, reporting
 * the specified error.  If connection is synchronized,
 * then send a RST to peer.
 */
struct tcpcb *
tcp_drop(tp, _errno)
register struct tcpcb *tp;
int _errno;
{
    struct socket *so = tp->t_inpcb->inp_socket;

    if(TCPS_HAVERCVDSYN(tp->t_state)) {
        tp->t_state = TCPS_CLOSED;
        (void) tcp_output(tp);
        tcpstat.tcps_drops++;
    } else
        tcpstat.tcps_conndrops++;
    if(_errno == ETIMEDOUT && tp->t_softerror)
        _errno = tp->t_softerror;
    so->so_error = _errno;
    return (tcp_close(tp));
}

/*
 * Close a TCP control block:
 *	discard all space held by the tcp
 *	discard internet protocol block
 *	wake up any sleepers
 */
struct tcpcb *
tcp_close(tp)
register struct tcpcb *tp;
{
    struct inpcb *inp = tp->t_inpcb;
    struct socket *so = inp->inp_socket;
    register struct mbuf *q;
    register struct mbuf *nq;
#ifdef RTV_RTT
    register struct rtentry *rt;

    /*
     * If we sent enough data to get some meaningful characteristics,
     * save them in the routing entry.  'Enough' is arbitrarily
     * defined as the sendpipesize (default 4K) * 16.  This would
     * give us 16 rtt samples assuming we only get one sample per
     * window (the usual case on a long haul net).  16 samples is
     * enough for the srtt filter to converge to within 5% of the correct
     * value; fewer samples and we could save a very bogus rtt.
     *
     * Don't update the default route's characteristics and don't
     * update anything that the user "locked".
     */
    if(SEQ_LT(tp->iss + so->so_snd.sb_hiwat * 16, tp->snd_max) &&
            (rt = inp->inp_route.ro_rt) &&
            ((struct sockaddr_in *)rt_key(rt))->sin_addr.s_addr != INADDR_ANY) {
        register u_long i = 0;

        if((rt->rt_rmx.rmx_locks & RTV_RTT) == 0) {
            i = tp->t_srtt *
                (RTM_RTTUNIT / (PR_SLOWHZ * TCP_RTT_SCALE));
            if(rt->rt_rmx.rmx_rtt && i)
                /*
                 * filter this update to half the old & half
                 * the new values, converting scale.
                 * See route.h and tcp_var.h for a
                 * description of the scaling constants.
                 */
                rt->rt_rmx.rmx_rtt =
                    (rt->rt_rmx.rmx_rtt + i) / 2;
            else
                rt->rt_rmx.rmx_rtt = i;
        }
        if((rt->rt_rmx.rmx_locks & RTV_RTTVAR) == 0) {
            i = tp->t_rttvar *
                (RTM_RTTUNIT / (PR_SLOWHZ * TCP_RTTVAR_SCALE));
            if(rt->rt_rmx.rmx_rttvar && i)
                rt->rt_rmx.rmx_rttvar =
                    (rt->rt_rmx.rmx_rttvar + i) / 2;
            else
                rt->rt_rmx.rmx_rttvar = i;
        }
        /*
         * update the pipelimit (ssthresh) if it has been updated
         * already or if a pipesize was specified & the threshhold
         * got below half the pipesize.  I.e., wait for bad news
         * before we start updating, then update on both good
         * and bad news.
         */
        if(((rt->rt_rmx.rmx_locks & RTV_SSTHRESH) == 0 &&
                ((i = tp->snd_ssthresh) != 0) && rt->rt_rmx.rmx_ssthresh) ||
                i < (rt->rt_rmx.rmx_sendpipe / 2)) {
            /*
             * convert the limit from user data bytes to
             * packets then to packet data bytes.
             */
            i = (i + tp->t_maxseg / 2) / tp->t_maxseg;
            if(i < 2)
                i = 2;
            i *= (u_long)(tp->t_maxseg + sizeof(struct tcpiphdr));
            if(rt->rt_rmx.rmx_ssthresh)
                rt->rt_rmx.rmx_ssthresh =
                    (rt->rt_rmx.rmx_ssthresh + i) / 2;
            else
                rt->rt_rmx.rmx_ssthresh = i;
        }
    }
#endif /* RTV_RTT */
    /* Drop any half-open syncache entries for a closing listen socket. */
    if(so->so_options & SO_ACCEPTCONN)
        syncache_purge(so);
    /* free the reassembly queue, if any */
    for(q = tp->t_segq; q; q = nq) {
        nq = q->m_nextpkt;
        tp->t_segq = nq;
        m_freem(q);
    }
    if(tp->t_template)
        (void) m_free(dtom(tp->t_template));
    bsd_free(tp, M_PCB);
    inp->inp_ppcb = 0;
    soisdisconnected(so);
    in_pcbdetach(inp);
    tcpstat.tcps_closed++;
    return ((struct tcpcb *)0);
}

void
tcp_drain()
{

}

/*
 * Notify a tcp user of an asynchronous error;
 * store error as soft error, but wake up user
 * (for now, won't do anything until can select for soft error).
 */
void
tcp_notify(inp, error)
struct inpcb *inp;
int error;
{
    register struct tcpcb *tp = (struct tcpcb *)inp->inp_ppcb;
    register struct socket *so = inp->inp_socket;

    /*
     * Ignore some errors if we are hooked up.
     * If connection hasn't completed, has retransmitted several times,
     * and receives a second error, give up now.  This is better
     * than waiting a long time to establish a connection that
     * can never complete.
     */
    if(tp->t_state == TCPS_ESTABLISHED &&
            (error == EHOSTUNREACH || error == ENETUNREACH ||
             error == EHOSTDOWN)) {
        return;
    } else if(tp->t_state < TCPS_ESTABLISHED && tp->t_rxtshift > 3 &&
              tp->t_softerror)
        so->so_error = error;
    else
        tp->t_softerror = error;
    wakeup((caddr_t) &so->so_timeo);
    sorwakeup(so);
    sowwakeup(so);
}

void
tcp_ctlinput(cmd, sa, arg)
int cmd;
struct sockaddr *sa;
void *arg;
{
    register struct ip *ip = arg;
    void (*notify) __P((struct inpcb *, int)) = tcp_notify;

    if(cmd == PRC_QUENCH)
        notify = tcp_quench;
    else if(!PRC_IS_REDIRECT(cmd) &&
            ((unsigned)cmd > PRC_NCMDS || inetctlerrmap[cmd] == 0))
        return;
    if(ip) {
        register struct tcphdr *th;
        th = (struct tcphdr *)((caddr_t)ip + (ip->ip_hl << 2));
        in_pcbnotify(&tcb, sa, th->th_dport, ip->ip_src, th->th_sport,
                     cmd, notify);
    } else
        in_pcbnotify(&tcb, sa, 0, zeroin_addr, 0, cmd, notify);
}

/*
 * When a source quench is received, close congestion window
 * to one segment.  We will gradually open it again as we proceed.
 */
void
tcp_quench(inp, _errno)
struct inpcb *inp;
int _errno;
{
    struct tcpcb *tp = intotcpcb(inp);

    if(tp) {
        CC_ON_RTO(tp);
    }
}

/*
 * Look-up the routing entry to the peer of this inpcb.  If no route
 * is found and it cannot be allocated the return NULL.  This routine
 * is called by TCP routines that access the rmx structure and by tcp_mss
 * to get the interface MTU.
 */
struct rtentry *
tcp_rtlookup(inp)
struct inpcb *inp;
{
    struct route *ro;
    struct rtentry *rt;

    ro = &inp->inp_route;
    rt = ro->ro_rt;
    if(rt == NULL || !(rt->rt_flags & RTF_UP)) {
        /* No route yet, so try to acquire one */
        if(inp->inp_faddr.s_addr != INADDR_ANY) {
            struct sockaddr_in *rodst_saddr = (struct sockaddr_in *)&ro->ro_dst;
            ro->ro_dst.sa_family = AF_INET;
            ro->ro_dst.sa_len = sizeof(ro->ro_dst);
            rodst_saddr->sin_addr = inp->inp_faddr;
            rtalloc(ro);
            rt = ro->ro_rt;
        }
    }
    return rt;
}

/*
 * Return a pointer to the cached information about the remote host.
 * The cached information is stored in the protocol specific part of
 * the route metrics.
 */
struct rmxp_tao *
tcp_gettaocache(inp)
struct inpcb *inp;
{
    struct rtentry *rt = tcp_rtlookup(inp);

    /* Make sure this is a host route and is up. */
    if(rt == NULL ||
            (rt->rt_flags & (RTF_UP | RTF_HOST)) != (RTF_UP | RTF_HOST))
        return NULL;

    return rmx_taop(rt->rt_rmx);
}

/*
 * Clear all the TAO cache entries, called from tcp_init.
 *
 * XXX
 * This routine is just an empty one, because we assume that the routing
 * routing tables are initialized at the same time when TCP, so there is
 * nothing in the cache left over.
 */
static void
tcp_cleartaocache(void)
{ }
