/*
 * Copyright (c) 2024 Apple Inc. All rights reserved.
 *
 * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
 *
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. The rights granted to you under the License
 * may not be used to create, or enable the creation or redistribution of,
 * unlawful or unlicensed copies of an Apple operating system, or to
 * circumvent, violate, or enable the circumvention or violation of, any
 * terms of an Apple operating system software license agreement.
 *
 * Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this file.
 *
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 *
 * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
 */

/*-
 * SPDX-License-Identifier: BSD-2-Clause
 *
 * Copyright (c) 2001 McAfee, Inc.
 * Copyright (c) 2006,2013 Andre Oppermann, Internet Business Solutions AG
 * All rights reserved.
 *
 * This software was developed for the FreeBSD Project by Jonathan Lemon
 * and McAfee Research, the Security Research Division of McAfee, Inc. under
 * DARPA/SPAWAR contract N66001-01-C-8035 ("CBOSS"), as part of the
 * DARPA CHATS research program. [2001 McAfee, Inc.]
 *
 * 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.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
 */

#include "tcp_includes.h"

#include <corecrypto/cchmac.h>
#include <corecrypto/ccsha2.h>
#include <net/if_var_private.h>
#include <netinet/in_tclass.h>
#include <netinet/ip.h>
#include <netinet/ip6.h>
#include <netinet/tcpip.h>
#include <netinet/tcp_syncookie.h>
#include <netinet6/nd6.h>
#include <net/siphash.h>
#include <os/ptrtools.h>
#include <sys/random.h>

extern int path_mtu_discovery;
int tcp_syncookie_hmac_sha256 = 0;

SYSCTL_INT(_net_inet_tcp, OID_AUTO, syncookie_hmac_sha256,
    CTLFLAG_RW | CTLFLAG_LOCKED, &tcp_syncookie_hmac_sha256, 0,
    "0: disable, 1: Use HMAC with SHA-256 for generating SYN cookie");

static bool
syncookie_respond(struct socket *so, struct tcpcb *tp, struct tcp_inp *tpi, uint16_t flags,
    struct sockaddr *local, struct sockaddr *remote);
static uint32_t syncookie_siphash(struct tcp_inp *tpi, uint8_t flags, uint8_t key[SYNCOOKIE_SECRET_SIZE]);
static uint32_t syncookie_hmac_sha256(struct tcp_inp *tpi, uint8_t flags, uint8_t key[CCSHA256_OUTPUT_SIZE]);
static uint32_t syncookie_mac(struct tcp_inp *tpi, uint8_t flags, uint8_t secbit);
static tcp_seq syncookie_generate(struct tcp_inp *tpi, bool has_ecn);
static bool syncookie_lookup(struct tcp_inp *tpi);
static void syncookie_reseed(void);

static struct syncookie_secret tcp_syncookie_secret;

/*
 * This function gets called when we receive an ACK for a
 * socket in the LISTEN state.  We create the connection
 * and set its state based on information from SYN cookies
 * and options/flags received in last ACK. The returned
 * tcpcb is in the SYN-RECEIVED state.
 *
 * Return true on success and false on failure.
 */
bool
tcp_syncookie_ack(struct tcp_inp *tpi, struct socket **so2, int* dropsocket)
{
#define TCP_LOG_HDR (isipv6 ? (void *)ip6 : (void *)ip)

	ASSERT((tcp_get_flags(tpi->th) & (TH_RST | TH_ACK | TH_SYN)) == TH_ACK);
	/*
	 * We don't support syncache, so see if this ACK is
	 * a returning syncookie. To do this,  check that the
	 * syncookie is valid.
	 */
	bool ret = syncookie_lookup(tpi);

	if (ret == false) {
		TCP_LOG(*tpi->tp, "Segment failed SYNCOOKIE authentication, "
		    "segment rejected (probably spoofed)");
		goto failed;
	}

	ret = tcp_create_server_socket(tpi, so2, NULL, dropsocket);

	if (ret == false) {
		goto failed;
	}

	ret = tcp_setup_server_socket(tpi, *so2, true);

	/* Set snd state for newly created tcpcb */
	(*tpi->tp)->snd_nxt = (*tpi->tp)->snd_max = tpi->th->th_ack;

	if (ret == false) {
		/*
		 * We failed to setup the server socket, return failure
		 * so that tcp_input can cleanup the socket and the
		 * incoming segment
		 */
		goto failed;
	}
	*dropsocket = 0;         /* committed to socket */

	if (__improbable(*so2 == NULL)) {
		tcpstat.tcps_sc_aborted++;
	} else {
		tcpstat.tcps_sc_completed++;
	}

	return true;

failed:
	return false;
}

static uint8_t
syncookie_process_accecn_syn(struct tcpcb *tp, uint32_t ace_flags,
    uint8_t ip_ecn)
{
	uint8_t setup_flags = 0;
	switch (ace_flags) {
	case (0 | 0 | 0):
		/* No ECN */
		break;
	case (0 | TH_CWR | TH_ECE):
		/* Legacy ECN-setup */
		setup_flags |= SC_ECN_SETUP;
		break;
	case (TH_ACE):
		/* Accurate ECN */
		if (tp->l4s_enabled) {
			switch (ip_ecn) {
			case IPTOS_ECN_NOTECT:
				setup_flags |= SC_ACE_SETUP_NOT_ECT;
				break;
			case IPTOS_ECN_ECT1:
				setup_flags |= SC_ACE_SETUP_ECT1;
				break;
			case IPTOS_ECN_ECT0:
				setup_flags |= SC_ACE_SETUP_ECT0;
				break;
			case IPTOS_ECN_CE:
				setup_flags |= SC_ACE_SETUP_CE;
				break;
			}
		} else {
			/*
			 * If AccECN is not enabled, ignore
			 * the TH_AE bit and do Legacy ECN-setup
			 */
			setup_flags |= SC_ECN_SETUP;
		}
	default:
		/* Forward Compatibility */
		/* Accurate ECN */
		if (tp->l4s_enabled) {
			switch (ip_ecn) {
			case IPTOS_ECN_NOTECT:
				setup_flags |= SC_ACE_SETUP_NOT_ECT;
				break;
			case IPTOS_ECN_ECT1:
				setup_flags |= SC_ACE_SETUP_ECT1;
				break;
			case IPTOS_ECN_ECT0:
				setup_flags |= SC_ACE_SETUP_ECT0;
				break;
			case IPTOS_ECN_CE:
				setup_flags |= SC_ACE_SETUP_CE;
				break;
			}
		}
		break;
	}
	return setup_flags;
}

static uint16_t
syncookie_respond_accecn(uint8_t setup_flags, uint16_t thflags)
{
	switch (setup_flags) {
	case SC_ECN_SETUP:
		thflags |= TH_ECE;
		break;
	case SC_ACE_SETUP_NOT_ECT:
		thflags |= TH_CWR;
		break;
	case SC_ACE_SETUP_ECT1:
		thflags |= (TH_CWR | TH_ECE);
		break;
	case SC_ACE_SETUP_ECT0:
		thflags |= TH_AE;
		break;
	case SC_ACE_SETUP_CE:
		thflags |= (TH_AE | TH_CWR);
		break;
	}

	return thflags;
}

/*
 * Given a LISTEN socket and an inbound SYN request, generate
 * a SYN cookie, and send back a segment:
 *	<SEQ=ISS><ACK=RCV_NXT><CTL=SYN,ACK>
 * to the source.
 */
void
tcp_syncookie_syn(struct tcp_inp *tpi, struct sockaddr *local,
    struct sockaddr *remote)
{
	struct socket *so = tpi->so;
	struct inpcb *inp;
	struct tcpcb *tp;
	uint8_t ip_tos, ip_ecn;
	uint8_t ace_setup_flags = 0;

	/* make sure inp is locked for listen socket */
	socket_lock_assert_owned(so);

	ASSERT((tcp_get_flags(tpi->th) & (TH_RST | TH_ACK | TH_SYN)) == TH_SYN);

	ASSERT((so->so_options & SO_ACCEPTCONN) != 0);

	/* Reseed the key if SYNCOOKIE_LIFETIME time has elapsed */
	if (tcp_now > tcp_syncookie_secret.last_updated +
	    SYNCOOKIE_LIFETIME * TCP_RETRANSHZ) {
		syncookie_reseed();
	}
	inp = sotoinpcb(so);
	tp = sototcpcb(so);

	if (tpi->isipv6) {
		if ((inp->in6p_outputopts == NULL) ||
		    (inp->in6p_outputopts->ip6po_tclass == -1)) {
			ip_tos = 0;
		} else {
			ip_tos = (uint8_t)inp->in6p_outputopts->ip6po_tclass;
		}
	} else {
		ip_tos = inp->inp_ip_tos;
	}

	ip_ecn = ip_tos & IPTOS_ECN_MASK;

	/* Is ECN enabled? */
	bool is_ecn = tcp_ecn_enabled(tp->ecn_flags);
	/* ECN Handshake */
	if (is_ecn) {
		int ace_flags = ((tpi->th->th_x2 << 8) | tpi->th->th_flags) & TH_ACE;
		ace_setup_flags = syncookie_process_accecn_syn(tp, ace_flags, ip_ecn);
	}
	bool classic_ecn = !!(ace_setup_flags & SC_ECN_SETUP);

	tpi->iss = syncookie_generate(tpi, classic_ecn);

	uint16_t output_flags = TH_SYN | TH_ACK;
	output_flags = syncookie_respond_accecn(ace_setup_flags, output_flags);
	/*
	 * Do a standard 3-way handshake.
	 */
	if (syncookie_respond(so, tp, tpi, output_flags, local, remote)) {
		tcpstat.tcps_sndacks++;
		tcpstat.tcps_sndtotal++;
	} else {
		tcpstat.tcps_sc_dropped++;
	}
	if (tpi->m != NULL) {
		m_freem(tpi->m);
	}
}

/*
 * Send SYN|ACK to the peer in response to a peer's SYN segment
 */
static bool
syncookie_respond(struct socket *so, struct tcpcb *tp, struct tcp_inp *tpi, uint16_t flags,
    struct sockaddr *local, struct sockaddr *remote)
{
	struct tcptemp *__single t_template;
	struct mbuf *__single m;
	tcp_seq seq;
	uint16_t mss = 0;
	uint32_t win;

	if (flags & TH_SYN) {
		seq = tpi->iss;
	} else {
		seq = tpi->iss + 1;
	}

	t_template = tcp_maketemplate(tp, &m, local, remote);
	if (t_template != NULL) {
		/* Use the properties of listener socket for sending SYN-ACK with cookie */
		struct inpcb *inp = tp->t_inpcb;

		uint16_t min_protoh = tpi->isipv6 ? sizeof(struct ip6_hdr) + sizeof(struct tcphdr)
		    : sizeof(struct tcpiphdr);
		if (tpi->isipv6) {
			mss = (uint16_t)IN6_LINKMTU(tpi->ifp);
		} else {
			mss = (uint16_t)tpi->ifp->if_mtu;
		}
		mss -= min_protoh;

		win = ((so->so_rcv.sb_flags & SB_USRSIZE) != 0) ?
		    so->so_rcv.sb_hiwat : tcp_autorcvbuf_max;
		win = imin(win, TCP_MAXWIN);
		uint8_t rcv_scale = tcp_get_max_rwinscale(tp, so);

		struct tcp_respond_args tra;

		bzero(&tra, sizeof(tra));
		tra.nocell = INP_NO_CELLULAR(inp) ? 1 : 0;
		tra.noexpensive = INP_NO_EXPENSIVE(inp) ? 1 : 0;
		tra.noconstrained = INP_NO_CONSTRAINED(inp) ? 1 : 0;
		tra.awdl_unrestricted = INP_AWDL_UNRESTRICTED(inp) ? 1 : 0;
		tra.intcoproc_allowed = INP_INTCOPROC_ALLOWED(inp) ? 1 : 0;
		tra.management_allowed = INP_MANAGEMENT_ALLOWED(inp) ? 1 : 0;
		tra.keep_alive = 1;
		if (tp->t_inpcb->inp_flags & INP_BOUND_IF) {
			tra.ifscope = tp->t_inpcb->inp_boundifp->if_index;
		} else {
			tra.ifscope = IFSCOPE_NONE;
		}
		tcp_respond((struct tcpcb*) 0, t_template->tt_ipgen, sizeof(t_template->tt_ipgen),
		    &t_template->tt_t, (struct mbuf *)NULL,
		    tpi->th->th_seq + 1, seq, win, flags, tpi->to, mss, rcv_scale, tpi->ts_offset, &tra, true);
		(void) m_free(m);

		tcpstat.tcps_sc_sendcookie++;

		return true;
	} else {
		return false;
	}
}

/*
 * The purpose of syncookies is to handle spoofed SYN flooding DoS attacks
 * that exceed the capacity of the listen queue by avoiding the storage of any
 * of the SYNs we receive.  Syncookies defend against blind SYN flooding
 * attacks where the attacker does not have access to our responses.
 *
 * Syncookies encode and include all necessary information about the
 * connection setup within the SYN|ACK that we send back.  That way we
 * can avoid keeping any local state until the ACK to our SYN|ACK returns
 * (if ever).
 *
 * The only reliable information persisting the 3WHS is our initial sequence
 * number ISS of 32 bits.  Syncookies embed a cryptographically sufficient
 * strong hash (MAC) value and a few bits of TCP SYN options in the ISS
 * of our SYN|ACK.  The MAC can be recomputed when the ACK to our SYN|ACK
 * returns and signifies a legitimate connection if it matches the ACK.
 *
 * The available space of 32 bits to store the hash and to encode the SYN
 * option information is very tight and we should have at least 24 bits for
 * the MAC to keep the number of guesses by blind spoofing reasonably high.
 *
 * SYN option information we have to encode to fully restore a connection:
 * MSS: is imporant to chose an optimal segment size to avoid IP level
 *   fragmentation along the path.  The common MSS values can be encoded
 *   in a 3-bit table.  Uncommon values are captured by the next lower value
 *   in the table leading to a slight increase in packetization overhead.
 * WSCALE: is necessary to allow large windows to be used for high delay-
 *   bandwidth product links.  Not scaling the window when it was initially
 *   negotiated is bad for performance as lack of scaling further decreases
 *   the apparent available send window.  We only need to encode the WSCALE
 *   we received from the remote end.  Our end can be recalculated at any
 *   time.  The common WSCALE values can be encoded in a 3-bit table.
 *   Uncommon values are captured by the next lower value in the table
 *   making us under-estimate the available window size halving our
 *   theoretically possible maximum throughput for that connection.
 * SACK: Greatly assists in packet loss recovery and requires 1 bit.
 * TIMESTAMP is not encoded because it is a permanent option
 *   that is included in all segments on a connection.  We enable it when
 *   the ACK has it.
 * Accurate ECN is not encoded because the last ACK has enough state to
 *   determine the state negotiated during SYN/ACK.
 *
 * Security of syncookies and attack vectors:
 *
 * The MAC is computed over (faddr||laddr||fport||lport||irs||flags)
 * together with the global secret to make it unique per connection attempt.
 * Thus any change of any of those parameters results in a different MAC output
 * in an unpredictable way unless a collision is encountered.  24 bits of the
 * MAC are embedded into the ISS.
 *
 * To prevent replay attacks two rotating global secrets are updated with a
 * new random value every 15 seconds.  The life-time of a syncookie is thus
 * 15-30 seconds.
 *
 * Vector 1: Attacking the secret.  This requires finding a weakness in the
 * MAC itself or the way it is used here.  The attacker can do a chosen plain
 * text attack by varying and testing the all parameters under his control.
 * The strength depends on the size and randomness of the secret, and the
 * cryptographic security of the MAC function.  Due to the constant updating
 * of the secret the attacker has at most 29.999 seconds to find the secret
 * and launch spoofed connections.  After that he has to start all over again.
 *
 * Vector 2: Collision attack on the MAC of a single ACK.  With a 24 bit MAC
 * size an average of 4,823 attempts are required for a 50% chance of success
 * to spoof a single syncookie (birthday collision paradox).  However the
 * attacker is blind and doesn't know if one of his attempts succeeded unless
 * he has a side channel to interfere success from.  A single connection setup
 * success average of 90% requires 8,790 packets, 99.99% requires 17,578 packets.
 * This many attempts are required for each one blind spoofed connection.  For
 * every additional spoofed connection he has to launch another N attempts.
 * Thus for a sustained rate 100 spoofed connections per second approximately
 * 1,800,000 packets per second would have to be sent.
 *
 * NB: The MAC function should be fast so that it doesn't become a CPU
 * exhaustion attack vector itself.
 *
 * References:
 *  RFC4987 TCP SYN Flooding Attacks and Common Mitigations
 *  SYN cookies were first proposed by cryptographer Dan J. Bernstein in 1996
 *   http://cr.yp.to/syncookies.html    (overview)
 *   http://cr.yp.to/syncookies/archive (details)
 *
 *
 * Schematic construction of a syncookie enabled Initial Sequence Number:
 *  0        1         2         3
 *  12345678901234567890123456789012
 * |xxxxxxxxxxxxxxxxxxxxxxxxWWWMMMSP|
 *
 *  x 24 MAC (truncated)
 *  W  3 Send Window Scale index
 *  M  2 MSS index
 *  E  1 Classic ECN permitted
 *  S  1 SACK permitted
 *  P  1 Odd/even secret
 */
/*
 * Distribution and probability of certain MSS values.  Those in between are
 * rounded down to the next lower one.
 */
static uint16_t tcp_sc_msstab_v4[] = { 536, 1300, 1460, 4036 };

static uint16_t tcp_sc_msstab_v6[] = { 1220, 1420, 1440, 4016 };

/*
 * Distribution and probability of certain WSCALE values.  We have to map the
 * (send) window scale (shift) option with a range of 0-14 from 4 bits into 3
 * bits based on prevalence of certain values.  Where we don't have an exact
 * match for are rounded down to the next lower one letting us under-estimate
 * the true available window.  At the moment this would happen only for the
 * very uncommon values 2, 5 and those above 9 (more than 32MB socket buffer
 * and window size).  The absence of the WSCALE option (no scaling in either
 * direction) is encoded with index zero.
 */
static uint8_t tcp_sc_wstab[] = { 0, 1, 3, 4, 6, 7, 8, 9 };

#define nitems(_x_) (sizeof(_x_) / sizeof(*_x_))

/*
 * Compute the MAC for the SYN cookie.  SIPHASH-2-4 is chosen for its speed
 * and good cryptographic properties.
 */
static uint32_t
syncookie_siphash(struct tcp_inp *tpi, uint8_t flags, uint8_t key[SYNCOOKIE_SECRET_SIZE])
{
	SIPHASH_CTX ctx;
	uint32_t siphash[2];

	SipHash24_Init(&ctx);
	SipHash_SetKey(&ctx, key);
	if (tpi->isipv6) {
		SipHash_Update(&ctx, &tpi->ip6->ip6_src.s6_addr, sizeof(tpi->ip6->ip6_src.s6_addr));
		SipHash_Update(&ctx, &tpi->ip6->ip6_dst.s6_addr, sizeof(tpi->ip6->ip6_dst.s6_addr));
	} else {
		SipHash_Update(&ctx, &tpi->ip->ip_src.s_addr, sizeof(tpi->ip->ip_src.s_addr));
		SipHash_Update(&ctx, &tpi->ip->ip_dst.s_addr, sizeof(tpi->ip->ip_dst.s_addr));
	}

	SipHash_Update(&ctx, &tpi->th->th_sport, sizeof(tpi->th->th_sport));
	SipHash_Update(&ctx, &tpi->th->th_dport, sizeof(tpi->th->th_dport));
	SipHash_Update(&ctx, &tpi->irs, sizeof(tpi->irs));
	SipHash_Update(&ctx, &flags, sizeof(flags));
	SipHash_Final((u_int8_t *)&siphash, &ctx);

	tpi->ts_offset = siphash[1];

	return siphash[0] ^ siphash[1];
}

/*
 * HMAC with SHA-256 is only used for comparison with Siphash
 */
static uint32_t
syncookie_hmac_sha256(struct tcp_inp *tpi, uint8_t flags, uint8_t key[CCSHA256_OUTPUT_SIZE])
{
	/* SHA256 mac is 32 bytes */
	uint32_t mac[8] = {};
	const struct ccdigest_info *di = ccsha256_di();

	cchmac_ctx_decl(di->state_size, di->block_size, ctx);
	cchmac_init(di, ctx, CCSHA256_OUTPUT_SIZE, key);
	if (tpi->isipv6) {
		cchmac_update(di, ctx, sizeof(tpi->ip6->ip6_src.s6_addr), &tpi->ip6->ip6_src.s6_addr);
		cchmac_update(di, ctx, sizeof(tpi->ip6->ip6_dst.s6_addr), &tpi->ip6->ip6_dst.s6_addr);
	} else {
		cchmac_update(di, ctx, sizeof(tpi->ip->ip_src.s_addr), &tpi->ip->ip_src.s_addr);
		cchmac_update(di, ctx, sizeof(tpi->ip->ip_dst.s_addr), &tpi->ip->ip_dst.s_addr);
	}
	cchmac_update(di, ctx, sizeof(tpi->th->th_sport), &tpi->th->th_sport);
	cchmac_update(di, ctx, sizeof(tpi->th->th_dport), &tpi->th->th_dport);
	cchmac_update(di, ctx, sizeof(tpi->irs), &tpi->irs);
	cchmac_update(di, ctx, sizeof(flags), &flags);
	cchmac_final(di, ctx, (uint8_t *)mac);

	tpi->ts_offset = mac[1];

	return mac[0] ^ mac[1] ^ mac[2] ^ mac[3] ^ mac[4] ^ mac[5] ^ mac[6] ^ mac[7];
}

static uint32_t
syncookie_mac(struct tcp_inp *tpi, uint8_t flags, uint8_t secbit)
{
	if (tcp_syncookie_hmac_sha256) {
		/* key size is 32 bytes */
		return syncookie_hmac_sha256(tpi, flags, (uint8_t *) tcp_syncookie_secret.key);
	} else {
		/* key size is 16 bytes */
		return syncookie_siphash(tpi, flags, tcp_syncookie_secret.key[secbit]);
	}
}

static tcp_seq
syncookie_generate(struct tcp_inp *tpi, bool has_ecn)
{
	uint8_t i, secbit, peer_wscale = 0;
	uint32_t iss, hash;
	syncookie cookie;
	uint16_t peer_mss = 0;

	cookie.cookie = 0;

	struct tcpopt *to = tpi->to;

	if (to->to_flags & TOF_MSS) {
		peer_mss = to->to_mss;  /* peer mss may be zero */
	}
	if (to->to_flags & TOF_SCALE) {
		peer_wscale = to->to_wscale;
	}

	/* Map our computed MSS into the 2-bit index. */
	if (tpi->isipv6) {
		for (i = nitems(tcp_sc_msstab_v6) - 1;
		    tcp_sc_msstab_v6[i] > peer_mss && i > 0;
		    i--) {
			;
		}
	} else {
		for (i = nitems(tcp_sc_msstab_v4) - 1;
		    tcp_sc_msstab_v4[i] > peer_mss && i > 0;
		    i--) {
			;
		}
	}
	cookie.flags.mss_idx = i;
	/*
	 * Map the send window scale into the 3-bit index but only if
	 * the wscale option was received.
	 */
	if (peer_wscale > 0) {
		for (i = nitems(tcp_sc_wstab) - 1;
		    tcp_sc_wstab[i] > peer_wscale && i > 0;
		    i--) {
			;
		}
		cookie.flags.wscale_idx = i;
	}
	/* Can we do SACK? */
	if (to->to_flags & TOF_SACKPERM) {
		cookie.flags.sack_ok = 1;
	}

	/* Should we do classic ECN? */
	if (has_ecn) {
		cookie.flags.ecn_ok = 1;
	}

	/* Which of the two secrets to use. */
	secbit = tcp_syncookie_secret.oddeven & 0x1;
	cookie.flags.odd_even = secbit;
	tpi->irs = tpi->th->th_seq;
	hash = syncookie_mac(tpi, cookie.cookie, secbit);
	/*
	 * Put the flags into the hash and XOR them to get better ISS number
	 * variance.  This doesn't enhance the cryptographic strength and is
	 * done to prevent the 8 cookie bits from showing up directly on the
	 * wire.
	 */
	iss = hash & ~0xff;
	iss |= cookie.cookie ^ (hash >> 24);

	tcpstat.tcps_sc_sendcookie++;

	return iss;
}

/*
 * Validate received SYN cookie in th_ack. Returns true on success
 * and a false on failure
 */
static bool
syncookie_lookup(struct tcp_inp *tpi)
{
	syncookie cookie;
	uint32_t hash;
	tcp_seq ack;
	/*
	 * Pull information out of SYN-ACK/ACK and revert sequence number
	 * advances.
	 */
	ack = tpi->th->th_ack - 1;
	tpi->irs = tpi->th->th_seq - 1;

	/*
	 * Unpack the flags containing enough information to restore the
	 * connection.
	 */
	cookie.cookie = (ack & 0xff) ^ (ack >> 24);
	hash = syncookie_mac(tpi, cookie.cookie, cookie.flags.odd_even);

	/* The recomputed hash failed to match the ACK */
	if ((ack & ~0xff) != (hash & ~0xff)) {
		return false;
	}
	if (tpi->isipv6) {
		tpi->peer_mss = tcp_sc_msstab_v6[cookie.flags.mss_idx];
	} else {
		tpi->peer_mss = tcp_sc_msstab_v4[cookie.flags.mss_idx];
	}

	/* Only use wscale if it was enabled in the orignal SYN. */
	if (cookie.flags.wscale_idx > 0) {
		tpi->peer_wscale = tcp_sc_wstab[cookie.flags.wscale_idx];
	}
	if (cookie.flags.sack_ok) {
		tpi->sackok = true;
	}

	if (cookie.flags.ecn_ok) {
		tpi->ecnok = true;
	}

	tcpstat.tcps_sc_recvcookie++;
	return true;
}

/*
 * We reseed when we receive a new connection request if
 * last update was done SYNCOOKIE_LIFETIME ago
 */
static void
syncookie_reseed(void)
{
	struct syncookie_secret *secret = &tcp_syncookie_secret;
	uint8_t *secbits;
	int secbit;

	/*
	 * Reseeding the secret doesn't have to be protected by a lock.
	 * It only must be ensured that the new random values are visible
	 * to all CPUs in a SMP environment.  The atomic with release
	 * semantics ensures that.
	 */
	secbit = (secret->oddeven & 0x1) ? 0 : 1;
	secbits = secret->key[secbit];
	read_frandom(secbits, SYNCOOKIE_SECRET_SIZE);
	os_atomic_add(&secret->oddeven, 1, relaxed);

	tcp_syncookie_secret.last_updated = tcp_now;
}

void
tcp_syncookie_init()
{
	/* Init syncookie secret */
	read_frandom(tcp_syncookie_secret.key[0], SYNCOOKIE_SECRET_SIZE);
	read_frandom(tcp_syncookie_secret.key[1], SYNCOOKIE_SECRET_SIZE);
	tcp_syncookie_secret.last_updated = tcp_now;
}
