aboutsummaryrefslogblamecommitdiff
path: root/sys/netinet/tcp_fastopen.c
blob: 9b633452951333795535f269a5c0b00aae744dd2 (plain) (tree)
1
2
   
                                         
























                                                                             

                                                                         


                      
  
                                                     




                                                                            



                                          





















                                                                            






                                                                           










                                                                                


                                         







                                                                            

                               
                                     

                                            



                                                       

                                            












                                                                                
  





















                                                                            





























                                                                           
                     
                       
                     

                       
                     
                       
                     
                       









                                   
                            
                                 
 

                                                  
















                                                                                     





                                                                















                                                                

                             
                                
                                                                 
                                                                 






                             









                                                                                 

                                                                              
 
                                                    




                                                                     
                                                             


                                                                     

                                                              

                                                                             

                                                                                 

                                                                     

                                                     
                                                               





                                                                             
                                                          

                                                                           

                                                                 
                                                         








                                                           



                                                           
                                                           




                                                                   
                                                           




                                                                   
                                                                  





                                                                                 
                                                              


                                                                        

                                                              




                                                                           

                                                               

                                                         

                                                                    

                                                                

                         

                                                                    

                                                                

                                    

                                                                         

                                                                               

                                               
                                                        






                                                                                
                                                                   

                                                       
                                                                          

                                                                      
                                             

                                                          

                                                                                             
                                                                    





                                                                     


                       
                       
 
                                                                 
                                                      


                                                        
                                               
                                                               

























                                                                                     






                                                                           














                                                                                  




                          


                                               
                                                             









                                                                      





































                                                                        












                                                                            



















                                                           
























                                                                                  










































                                                                                 












                                                                             
               






                                       
                                          





                                                                                    



                                




                                                                   






                                                                                

                                                                                
                                 




                                                              
               
 
     

                                            














                                                         
                                                   














                                                                            



























                                                               



                         
                                           

                                                      
                                                           





                                                                               



                                                         
                                                    
                                                                  









                                                                            
                                                         



























                                                        






























                                                                     
 














                                                                                  

















                                                                             


                                                                               














                                                                                    
                         














                                                         
 
                       


















                                                                              
                                                                 





































































































































                                                                                   
 





















                                                                                     
 






                                           
 




























                                                                       

                                                          










                                                        
 








                                                                       
 






                                                                       
                                                   





















                                                                      










































































                                                                                     
/*-
 * Copyright (c) 2015-2017 Patrick Kelsey
 * All rights reserved.
 *
 * 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.
 */

/*
 * This is an implementation of TCP Fast Open (TFO) [RFC7413]. To include
 * this code, add the following line to your kernel config:
 *
 * options TCP_RFC7413
 *
 *
 * The generated TFO cookies are the 64-bit output of
 * SipHash24(key=<16-byte-key>, msg=<client-ip>).  Multiple concurrent valid
 * keys are supported so that time-based rolling cookie invalidation
 * policies can be implemented in the system.  The default number of
 * concurrent keys is 2.  This can be adjusted in the kernel config as
 * follows:
 *
 * options TCP_RFC7413_MAX_KEYS=<num-keys>
 *
 *
 * In addition to the facilities defined in RFC7413, this implementation
 * supports a pre-shared key (PSK) mode of operation in which the TFO server
 * requires the client to be in posession of a shared secret in order for
 * the client to be able to successfully open TFO connections with the
 * server.  This is useful, for example, in environments where TFO servers
 * are exposed to both internal and external clients and only wish to allow
 * TFO connections from internal clients.
 *
 * In the PSK mode of operation, the server generates and sends TFO cookies
 * to requesting clients as usual.  However, when validating cookies
 * received in TFO SYNs from clients, the server requires the
 * client-supplied cookie to equal SipHash24(key=<16-byte-psk>,
 * msg=<cookie-sent-to-client>).
 *
 * Multiple concurrent valid pre-shared keys are supported so that
 * time-based rolling PSK invalidation policies can be implemented in the
 * system.  The default number of concurrent pre-shared keys is 2.  This can
 * be adjusted in the kernel config as follows:
 *
 * options TCP_RFC7413_MAX_PSKS=<num-psks>
 *
 *
 * The following TFO-specific sysctls are defined:
 *
 * net.inet.tcp.fastopen.acceptany (RW, default 0)
 *     When non-zero, all client-supplied TFO cookies will be considered to
 *     be valid.
 *
 * net.inet.tcp.fastopen.autokey (RW, default 120)
 *     When this and net.inet.tcp.fastopen.server_enable are non-zero, a new
 *     key will be automatically generated after this many seconds.
 *
 * net.inet.tcp.fastopen.ccache_bucket_limit
 *                     (RWTUN, default TCP_FASTOPEN_CCACHE_BUCKET_LIMIT_DEFAULT)
 *     The maximum number of entries in a client cookie cache bucket.
 *
 * net.inet.tcp.fastopen.ccache_buckets
 *                          (RDTUN, default TCP_FASTOPEN_CCACHE_BUCKETS_DEFAULT)
 *     The number of client cookie cache buckets.
 *
 * net.inet.tcp.fastopen.ccache_list (RO)
 *     Print the client cookie cache.
 *
 * net.inet.tcp.fastopen.client_enable (RW, default 0)
 *     When zero, no new active (i.e., client) TFO connections can be
 *     created.  On the transition from enabled to disabled, the client
 *     cookie cache is cleared and disabled.  The transition from enabled to
 *     disabled does not affect any active TFO connections in progress; it
 *     only prevents new ones from being made.
 *
 * net.inet.tcp.fastopen.keylen (RD)
 *     The key length in bytes.
 *
 * net.inet.tcp.fastopen.maxkeys (RD)
 *     The maximum number of keys supported.
 *
 * net.inet.tcp.fastopen.maxpsks (RD)
 *     The maximum number of pre-shared keys supported.
 *
 * net.inet.tcp.fastopen.numkeys (RD)
 *     The current number of keys installed.
 *
 * net.inet.tcp.fastopen.numpsks (RD)
 *     The current number of pre-shared keys installed.
 *
 * net.inet.tcp.fastopen.path_disable_time
 *                          (RW, default TCP_FASTOPEN_PATH_DISABLE_TIME_DEFAULT)
 *     When a failure occurs while trying to create a new active (i.e.,
 *     client) TFO connection, new active connections on the same path, as
 *     determined by the tuple {client_ip, server_ip, server_port}, will be
 *     forced to be non-TFO for this many seconds.  Note that the path
 *     disable mechanism relies on state stored in client cookie cache
 *     entries, so it is possible for the disable time for a given path to
 *     be reduced if the corresponding client cookie cache entry is reused
 *     due to resource pressure before the disable period has elapsed.
 *
 * net.inet.tcp.fastopen.psk_enable (RW, default 0)
 *     When non-zero, pre-shared key (PSK) mode is enabled for all TFO
 *     servers.  On the transition from enabled to disabled, all installed
 *     pre-shared keys are removed.
 *
 * net.inet.tcp.fastopen.server_enable (RW, default 0)
 *     When zero, no new passive (i.e., server) TFO connections can be
 *     created.  On the transition from enabled to disabled, all installed
 *     keys and pre-shared keys are removed.  On the transition from
 *     disabled to enabled, if net.inet.tcp.fastopen.autokey is non-zero and
 *     there are no keys installed, a new key will be generated immediately.
 *     The transition from enabled to disabled does not affect any passive
 *     TFO connections in progress; it only prevents new ones from being
 *     made.
 *
 * net.inet.tcp.fastopen.setkey (WR)
 *     Install a new key by writing net.inet.tcp.fastopen.keylen bytes to
 *     this sysctl.
 *
 * net.inet.tcp.fastopen.setpsk (WR)
 *     Install a new pre-shared key by writing net.inet.tcp.fastopen.keylen
 *     bytes to this sysctl.
 *
 * In order for TFO connections to be created via a listen socket, that
 * socket must have the TCP_FASTOPEN socket option set on it.  This option
 * can be set on the socket either before or after the listen() is invoked.
 * Clearing this option on a listen socket after it has been set has no
 * effect on existing TFO connections or TFO connections in progress; it
 * only prevents new TFO connections from being made.
 *
 * For passively-created sockets, the TCP_FASTOPEN socket option can be
 * queried to determine whether the connection was established using TFO.
 * Note that connections that are established via a TFO SYN, but that fall
 * back to using a non-TFO SYN|ACK will have the TCP_FASTOPEN socket option
 * set.
 *
 * Per the RFC, this implementation limits the number of TFO connections
 * that can be in the SYN_RECEIVED state on a per listen-socket basis.
 * Whenever this limit is exceeded, requests for new TFO connections are
 * serviced as non-TFO requests.  Without such a limit, given a valid TFO
 * cookie, an attacker could keep the listen queue in an overflow condition
 * using a TFO SYN flood.  This implementation sets the limit at half the
 * configured listen backlog.
 *
 */

#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");

#include "opt_inet.h"

#include <sys/param.h>
#include <sys/jail.h>
#include <sys/kernel.h>
#include <sys/hash.h>
#include <sys/limits.h>
#include <sys/lock.h>
#include <sys/proc.h>
#include <sys/rmlock.h>
#include <sys/sbuf.h>
#include <sys/socket.h>
#include <sys/socketvar.h>
#include <sys/sysctl.h>
#include <sys/systm.h>

#include <crypto/siphash/siphash.h>

#include <net/vnet.h>

#include <netinet/in.h>
#include <netinet/in_pcb.h>
#include <netinet/tcp_var.h>
#include <netinet/tcp_fastopen.h>

#define	TCP_FASTOPEN_KEY_LEN	SIPHASH_KEY_LENGTH

#if TCP_FASTOPEN_PSK_LEN != TCP_FASTOPEN_KEY_LEN
#error TCP_FASTOPEN_PSK_LEN must be equal to TCP_FASTOPEN_KEY_LEN
#endif

/*
 * Because a PSK-mode setsockopt() uses tcpcb.t_tfo_cookie.client to hold
 * the PSK until the connect occurs.
 */
#if TCP_FASTOPEN_MAX_COOKIE_LEN < TCP_FASTOPEN_PSK_LEN
#error TCP_FASTOPEN_MAX_COOKIE_LEN must be >= TCP_FASTOPEN_PSK_LEN
#endif

#define TCP_FASTOPEN_CCACHE_BUCKET_LIMIT_DEFAULT	16
#define TCP_FASTOPEN_CCACHE_BUCKETS_DEFAULT		2048 /* must be power of 2 */

#define TCP_FASTOPEN_PATH_DISABLE_TIME_DEFAULT		900 /* seconds */

#if !defined(TCP_RFC7413_MAX_KEYS) || (TCP_RFC7413_MAX_KEYS < 1)
#define	TCP_FASTOPEN_MAX_KEYS	2
#else
#define	TCP_FASTOPEN_MAX_KEYS	TCP_RFC7413_MAX_KEYS
#endif

#if TCP_FASTOPEN_MAX_KEYS > 10
#undef TCP_FASTOPEN_MAX_KEYS
#define	TCP_FASTOPEN_MAX_KEYS	10
#endif

#if !defined(TCP_RFC7413_MAX_PSKS) || (TCP_RFC7413_MAX_PSKS < 1)
#define	TCP_FASTOPEN_MAX_PSKS	2
#else
#define	TCP_FASTOPEN_MAX_PSKS	TCP_RFC7413_MAX_PSKS
#endif

#if TCP_FASTOPEN_MAX_PSKS > 10
#undef TCP_FASTOPEN_MAX_PSKS
#define	TCP_FASTOPEN_MAX_PSKS	10
#endif

struct tcp_fastopen_keylist {
	unsigned int newest;
	unsigned int newest_psk;
	uint8_t key[TCP_FASTOPEN_MAX_KEYS][TCP_FASTOPEN_KEY_LEN];
	uint8_t psk[TCP_FASTOPEN_MAX_PSKS][TCP_FASTOPEN_KEY_LEN];
};

struct tcp_fastopen_callout {
	struct callout c;
	struct vnet *v;
};

static struct tcp_fastopen_ccache_entry *tcp_fastopen_ccache_lookup(
    struct in_conninfo *, struct tcp_fastopen_ccache_bucket **);
static struct tcp_fastopen_ccache_entry *tcp_fastopen_ccache_create(
    struct tcp_fastopen_ccache_bucket *, struct in_conninfo *, uint16_t, uint8_t,
    uint8_t *);
static void tcp_fastopen_ccache_bucket_trim(struct tcp_fastopen_ccache_bucket *,
    unsigned int);
static void tcp_fastopen_ccache_entry_drop(struct tcp_fastopen_ccache_entry *,
    struct tcp_fastopen_ccache_bucket *);

SYSCTL_NODE(_net_inet_tcp, OID_AUTO, fastopen, CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
    "TCP Fast Open");

VNET_DEFINE_STATIC(int, tcp_fastopen_acceptany) = 0;
#define	V_tcp_fastopen_acceptany	VNET(tcp_fastopen_acceptany)
SYSCTL_INT(_net_inet_tcp_fastopen, OID_AUTO, acceptany,
    CTLFLAG_VNET | CTLFLAG_RW, &VNET_NAME(tcp_fastopen_acceptany), 0,
    "Accept any non-empty cookie");

VNET_DEFINE_STATIC(unsigned int, tcp_fastopen_autokey) = 120;
#define	V_tcp_fastopen_autokey	VNET(tcp_fastopen_autokey)
static int sysctl_net_inet_tcp_fastopen_autokey(SYSCTL_HANDLER_ARGS);
SYSCTL_PROC(_net_inet_tcp_fastopen, OID_AUTO, autokey,
    CTLFLAG_VNET | CTLTYPE_UINT | CTLFLAG_RW | CTLFLAG_MPSAFE,
    NULL, 0, &sysctl_net_inet_tcp_fastopen_autokey, "IU",
    "Number of seconds between auto-generation of a new key; zero disables");

static int sysctl_net_inet_tcp_fastopen_ccache_bucket_limit(SYSCTL_HANDLER_ARGS);
SYSCTL_PROC(_net_inet_tcp_fastopen, OID_AUTO, ccache_bucket_limit,
    CTLFLAG_VNET | CTLTYPE_UINT | CTLFLAG_RWTUN | CTLFLAG_NEEDGIANT,
    NULL, 0, &sysctl_net_inet_tcp_fastopen_ccache_bucket_limit, "IU",
    "Max entries per bucket in client cookie cache");

VNET_DEFINE_STATIC(unsigned int, tcp_fastopen_ccache_buckets) =
    TCP_FASTOPEN_CCACHE_BUCKETS_DEFAULT;
#define	V_tcp_fastopen_ccache_buckets VNET(tcp_fastopen_ccache_buckets)
SYSCTL_UINT(_net_inet_tcp_fastopen, OID_AUTO, ccache_buckets,
    CTLFLAG_VNET | CTLFLAG_RDTUN, &VNET_NAME(tcp_fastopen_ccache_buckets), 0,
    "Client cookie cache number of buckets (power of 2)");

VNET_DEFINE(unsigned int, tcp_fastopen_client_enable) = 1;
static int sysctl_net_inet_tcp_fastopen_client_enable(SYSCTL_HANDLER_ARGS);
SYSCTL_PROC(_net_inet_tcp_fastopen, OID_AUTO, client_enable,
    CTLFLAG_VNET | CTLTYPE_UINT | CTLFLAG_RW | CTLFLAG_NEEDGIANT,
    NULL, 0, &sysctl_net_inet_tcp_fastopen_client_enable, "IU",
    "Enable/disable TCP Fast Open client functionality");

SYSCTL_INT(_net_inet_tcp_fastopen, OID_AUTO, keylen,
    CTLFLAG_RD, SYSCTL_NULL_INT_PTR, TCP_FASTOPEN_KEY_LEN,
    "Key length in bytes");

SYSCTL_INT(_net_inet_tcp_fastopen, OID_AUTO, maxkeys,
    CTLFLAG_RD, SYSCTL_NULL_INT_PTR, TCP_FASTOPEN_MAX_KEYS,
    "Maximum number of keys supported");

SYSCTL_INT(_net_inet_tcp_fastopen, OID_AUTO, maxpsks,
    CTLFLAG_RD, SYSCTL_NULL_INT_PTR, TCP_FASTOPEN_MAX_PSKS,
    "Maximum number of pre-shared keys supported");

VNET_DEFINE_STATIC(unsigned int, tcp_fastopen_numkeys) = 0;
#define	V_tcp_fastopen_numkeys	VNET(tcp_fastopen_numkeys)
SYSCTL_UINT(_net_inet_tcp_fastopen, OID_AUTO, numkeys,
    CTLFLAG_VNET | CTLFLAG_RD, &VNET_NAME(tcp_fastopen_numkeys), 0,
    "Number of keys installed");

VNET_DEFINE_STATIC(unsigned int, tcp_fastopen_numpsks) = 0;
#define	V_tcp_fastopen_numpsks	VNET(tcp_fastopen_numpsks)
SYSCTL_UINT(_net_inet_tcp_fastopen, OID_AUTO, numpsks,
    CTLFLAG_VNET | CTLFLAG_RD, &VNET_NAME(tcp_fastopen_numpsks), 0,
    "Number of pre-shared keys installed");

VNET_DEFINE_STATIC(unsigned int, tcp_fastopen_path_disable_time) =
    TCP_FASTOPEN_PATH_DISABLE_TIME_DEFAULT;
#define	V_tcp_fastopen_path_disable_time VNET(tcp_fastopen_path_disable_time)
SYSCTL_UINT(_net_inet_tcp_fastopen, OID_AUTO, path_disable_time,
    CTLFLAG_VNET | CTLFLAG_RW, &VNET_NAME(tcp_fastopen_path_disable_time), 0,
    "Seconds a TFO failure disables a {client_ip, server_ip, server_port} path");

VNET_DEFINE_STATIC(unsigned int, tcp_fastopen_psk_enable) = 0;
#define	V_tcp_fastopen_psk_enable	VNET(tcp_fastopen_psk_enable)
static int sysctl_net_inet_tcp_fastopen_psk_enable(SYSCTL_HANDLER_ARGS);
SYSCTL_PROC(_net_inet_tcp_fastopen, OID_AUTO, psk_enable,
    CTLFLAG_VNET | CTLTYPE_UINT | CTLFLAG_RW | CTLFLAG_MPSAFE,
    NULL, 0, &sysctl_net_inet_tcp_fastopen_psk_enable, "IU",
    "Enable/disable TCP Fast Open server pre-shared key mode");

VNET_DEFINE(unsigned int, tcp_fastopen_server_enable) = 0;
static int sysctl_net_inet_tcp_fastopen_server_enable(SYSCTL_HANDLER_ARGS);
SYSCTL_PROC(_net_inet_tcp_fastopen, OID_AUTO, server_enable,
    CTLFLAG_VNET | CTLTYPE_UINT | CTLFLAG_RW | CTLFLAG_MPSAFE,
    NULL, 0, &sysctl_net_inet_tcp_fastopen_server_enable, "IU",
    "Enable/disable TCP Fast Open server functionality");

static int sysctl_net_inet_tcp_fastopen_setkey(SYSCTL_HANDLER_ARGS);
SYSCTL_PROC(_net_inet_tcp_fastopen, OID_AUTO, setkey,
    CTLFLAG_VNET | CTLTYPE_OPAQUE | CTLFLAG_WR | CTLFLAG_MPSAFE,
    NULL, 0, &sysctl_net_inet_tcp_fastopen_setkey, "",
    "Install a new key");

static int sysctl_net_inet_tcp_fastopen_setpsk(SYSCTL_HANDLER_ARGS);
SYSCTL_PROC(_net_inet_tcp_fastopen, OID_AUTO, setpsk,
    CTLFLAG_VNET | CTLTYPE_OPAQUE | CTLFLAG_WR | CTLFLAG_MPSAFE,
    NULL, 0, &sysctl_net_inet_tcp_fastopen_setpsk, "",
    "Install a new pre-shared key");

static int sysctl_net_inet_tcp_fastopen_ccache_list(SYSCTL_HANDLER_ARGS);
SYSCTL_PROC(_net_inet_tcp_fastopen, OID_AUTO, ccache_list,
    CTLFLAG_VNET | CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_SKIP | CTLFLAG_MPSAFE,
    NULL, 0, sysctl_net_inet_tcp_fastopen_ccache_list, "A",
    "List of all client cookie cache entries");

VNET_DEFINE_STATIC(struct rmlock, tcp_fastopen_keylock);
#define	V_tcp_fastopen_keylock	VNET(tcp_fastopen_keylock)

#define TCP_FASTOPEN_KEYS_RLOCK(t)	rm_rlock(&V_tcp_fastopen_keylock, (t))
#define TCP_FASTOPEN_KEYS_RUNLOCK(t)	rm_runlock(&V_tcp_fastopen_keylock, (t))
#define TCP_FASTOPEN_KEYS_WLOCK()	rm_wlock(&V_tcp_fastopen_keylock)
#define TCP_FASTOPEN_KEYS_WUNLOCK()	rm_wunlock(&V_tcp_fastopen_keylock)

VNET_DEFINE_STATIC(struct tcp_fastopen_keylist, tcp_fastopen_keys);
#define V_tcp_fastopen_keys	VNET(tcp_fastopen_keys)

VNET_DEFINE_STATIC(struct tcp_fastopen_callout, tcp_fastopen_autokey_ctx);
#define V_tcp_fastopen_autokey_ctx	VNET(tcp_fastopen_autokey_ctx)

VNET_DEFINE_STATIC(uma_zone_t, counter_zone);
#define	V_counter_zone			VNET(counter_zone)

static MALLOC_DEFINE(M_TCP_FASTOPEN_CCACHE, "tfo_ccache", "TFO client cookie cache buckets");

VNET_DEFINE_STATIC(struct tcp_fastopen_ccache, tcp_fastopen_ccache);
#define V_tcp_fastopen_ccache	VNET(tcp_fastopen_ccache)

#define	CCB_LOCK(ccb)		mtx_lock(&(ccb)->ccb_mtx)
#define	CCB_UNLOCK(ccb)		mtx_unlock(&(ccb)->ccb_mtx)
#define	CCB_LOCK_ASSERT(ccb)	mtx_assert(&(ccb)->ccb_mtx, MA_OWNED)

void
tcp_fastopen_init(void)
{
	unsigned int i;

	V_counter_zone = uma_zcreate("tfo", sizeof(unsigned int),
	    NULL, NULL, NULL, NULL, UMA_ALIGN_PTR, 0);
	rm_init(&V_tcp_fastopen_keylock, "tfo_keylock");
	callout_init_rm(&V_tcp_fastopen_autokey_ctx.c,
	    &V_tcp_fastopen_keylock, 0);
	V_tcp_fastopen_autokey_ctx.v = curvnet;
	V_tcp_fastopen_keys.newest = TCP_FASTOPEN_MAX_KEYS - 1;
	V_tcp_fastopen_keys.newest_psk = TCP_FASTOPEN_MAX_PSKS - 1;

	/* May already be non-zero if kernel tunable was set */
	if (V_tcp_fastopen_ccache.bucket_limit == 0)
		V_tcp_fastopen_ccache.bucket_limit =
		    TCP_FASTOPEN_CCACHE_BUCKET_LIMIT_DEFAULT;

	/* May already be non-zero if kernel tunable was set */
	if ((V_tcp_fastopen_ccache_buckets == 0) ||
	    !powerof2(V_tcp_fastopen_ccache_buckets))
		V_tcp_fastopen_ccache.buckets =
			TCP_FASTOPEN_CCACHE_BUCKETS_DEFAULT;
	else
		V_tcp_fastopen_ccache.buckets = V_tcp_fastopen_ccache_buckets;

	V_tcp_fastopen_ccache.mask = V_tcp_fastopen_ccache.buckets - 1;
	V_tcp_fastopen_ccache.secret = arc4random();

	V_tcp_fastopen_ccache.base = malloc(V_tcp_fastopen_ccache.buckets *
	    sizeof(struct tcp_fastopen_ccache_bucket), M_TCP_FASTOPEN_CCACHE,
	    M_WAITOK | M_ZERO);

	for (i = 0; i < V_tcp_fastopen_ccache.buckets; i++) {
		TAILQ_INIT(&V_tcp_fastopen_ccache.base[i].ccb_entries);
		mtx_init(&V_tcp_fastopen_ccache.base[i].ccb_mtx, "tfo_ccache_bucket",
			 NULL, MTX_DEF);
		if (V_tcp_fastopen_client_enable) {
			/* enable bucket */
			V_tcp_fastopen_ccache.base[i].ccb_num_entries = 0;
		} else {
			/* disable bucket */
			V_tcp_fastopen_ccache.base[i].ccb_num_entries = -1;
		}
		V_tcp_fastopen_ccache.base[i].ccb_ccache = &V_tcp_fastopen_ccache;
	}

	/*
	 * Note that while the total number of entries in the cookie cache
	 * is limited by the table management logic to
	 * V_tcp_fastopen_ccache.buckets *
	 * V_tcp_fastopen_ccache.bucket_limit, the total number of items in
	 * this zone can exceed that amount by the number of CPUs in the
	 * system times the maximum number of unallocated items that can be
	 * present in each UMA per-CPU cache for this zone.
	 */
	V_tcp_fastopen_ccache.zone = uma_zcreate("tfo_ccache_entries",
	    sizeof(struct tcp_fastopen_ccache_entry), NULL, NULL, NULL, NULL,
	    UMA_ALIGN_CACHE, 0);
}

void
tcp_fastopen_destroy(void)
{
	struct tcp_fastopen_ccache_bucket *ccb;
	unsigned int i;

	for (i = 0; i < V_tcp_fastopen_ccache.buckets; i++) {
		ccb = &V_tcp_fastopen_ccache.base[i];
		tcp_fastopen_ccache_bucket_trim(ccb, 0);
		mtx_destroy(&ccb->ccb_mtx);
	}

	KASSERT(uma_zone_get_cur(V_tcp_fastopen_ccache.zone) == 0,
	    ("%s: TFO ccache zone allocation count not 0", __func__));
	uma_zdestroy(V_tcp_fastopen_ccache.zone);
	free(V_tcp_fastopen_ccache.base, M_TCP_FASTOPEN_CCACHE);

	callout_drain(&V_tcp_fastopen_autokey_ctx.c);
	rm_destroy(&V_tcp_fastopen_keylock);
	uma_zdestroy(V_counter_zone);
}

unsigned int *
tcp_fastopen_alloc_counter(void)
{
	unsigned int *counter;
	counter = uma_zalloc(V_counter_zone, M_NOWAIT);
	if (counter)
		*counter = 1;
	return (counter);
}

void
tcp_fastopen_decrement_counter(unsigned int *counter)
{
	if (*counter == 1)
		uma_zfree(V_counter_zone, counter);
	else
		atomic_subtract_int(counter, 1);
}

static void
tcp_fastopen_addkey_locked(uint8_t *key)
{

	V_tcp_fastopen_keys.newest++;
	if (V_tcp_fastopen_keys.newest == TCP_FASTOPEN_MAX_KEYS)
		V_tcp_fastopen_keys.newest = 0;
	memcpy(V_tcp_fastopen_keys.key[V_tcp_fastopen_keys.newest], key,
	    TCP_FASTOPEN_KEY_LEN);
	if (V_tcp_fastopen_numkeys < TCP_FASTOPEN_MAX_KEYS)
		V_tcp_fastopen_numkeys++;
}

static void
tcp_fastopen_addpsk_locked(uint8_t *psk)
{

	V_tcp_fastopen_keys.newest_psk++;
	if (V_tcp_fastopen_keys.newest_psk == TCP_FASTOPEN_MAX_PSKS)
		V_tcp_fastopen_keys.newest_psk = 0;
	memcpy(V_tcp_fastopen_keys.psk[V_tcp_fastopen_keys.newest_psk], psk,
	    TCP_FASTOPEN_KEY_LEN);
	if (V_tcp_fastopen_numpsks < TCP_FASTOPEN_MAX_PSKS)
		V_tcp_fastopen_numpsks++;
}

static void
tcp_fastopen_autokey_locked(void)
{
	uint8_t newkey[TCP_FASTOPEN_KEY_LEN];

	arc4rand(newkey, TCP_FASTOPEN_KEY_LEN, 0);
	tcp_fastopen_addkey_locked(newkey);
}

static void
tcp_fastopen_autokey_callout(void *arg)
{
	struct tcp_fastopen_callout *ctx = arg;

	CURVNET_SET(ctx->v);
	tcp_fastopen_autokey_locked();
	callout_reset(&ctx->c, V_tcp_fastopen_autokey * hz,
		      tcp_fastopen_autokey_callout, ctx);
	CURVNET_RESTORE();
}

static uint64_t
tcp_fastopen_make_cookie(uint8_t key[SIPHASH_KEY_LENGTH], struct in_conninfo *inc)
{
	SIPHASH_CTX ctx;
	uint64_t siphash;

	SipHash24_Init(&ctx);
	SipHash_SetKey(&ctx, key);
	switch (inc->inc_flags & INC_ISIPV6) {
#ifdef INET
	case 0:
		SipHash_Update(&ctx, &inc->inc_faddr, sizeof(inc->inc_faddr));
		break;
#endif
#ifdef INET6
	case INC_ISIPV6:
		SipHash_Update(&ctx, &inc->inc6_faddr, sizeof(inc->inc6_faddr));
		break;
#endif
	}
	SipHash_Final((u_int8_t *)&siphash, &ctx);

	return (siphash);
}

static uint64_t
tcp_fastopen_make_psk_cookie(uint8_t *psk, uint8_t *cookie, uint8_t cookie_len)
{
	SIPHASH_CTX ctx;
	uint64_t psk_cookie;

	SipHash24_Init(&ctx);
	SipHash_SetKey(&ctx, psk);
	SipHash_Update(&ctx, cookie, cookie_len);
	SipHash_Final((u_int8_t *)&psk_cookie, &ctx);

	return (psk_cookie);
}

static int
tcp_fastopen_find_cookie_match_locked(uint8_t *wire_cookie, uint64_t *cur_cookie)
{
	unsigned int i, psk_index;
	uint64_t psk_cookie;

	if (V_tcp_fastopen_psk_enable) {
		psk_index = V_tcp_fastopen_keys.newest_psk;
		for (i = 0; i < V_tcp_fastopen_numpsks; i++) {
			psk_cookie =
			    tcp_fastopen_make_psk_cookie(
				 V_tcp_fastopen_keys.psk[psk_index],
				 (uint8_t *)cur_cookie,
				 TCP_FASTOPEN_COOKIE_LEN);

			if (memcmp(wire_cookie, &psk_cookie,
				   TCP_FASTOPEN_COOKIE_LEN) == 0)
				return (1);

			if (psk_index == 0)
				psk_index = TCP_FASTOPEN_MAX_PSKS - 1;
			else
				psk_index--;
		}
	} else if (memcmp(wire_cookie, cur_cookie, TCP_FASTOPEN_COOKIE_LEN) == 0)
		return (1);

	return (0);
}

/*
 * Return values:
 *	-1	the cookie is invalid and no valid cookie is available
 *	 0	the cookie is invalid and the latest cookie has been returned
 *	 1	the cookie is valid and the latest cookie has been returned
 */
int
tcp_fastopen_check_cookie(struct in_conninfo *inc, uint8_t *cookie,
    unsigned int len, uint64_t *latest_cookie)
{
	struct rm_priotracker tracker;
	unsigned int i, key_index;
	int rv;
	uint64_t cur_cookie;

	if (V_tcp_fastopen_acceptany) {
		*latest_cookie = 0;
		return (1);
	}

	TCP_FASTOPEN_KEYS_RLOCK(&tracker);
	if (len != TCP_FASTOPEN_COOKIE_LEN) {
		if (V_tcp_fastopen_numkeys > 0) {
			*latest_cookie =
			    tcp_fastopen_make_cookie(
				V_tcp_fastopen_keys.key[V_tcp_fastopen_keys.newest],
				inc);
			rv = 0;
		} else
			rv = -1;
		goto out;
	}

	/*
	 * Check against each available key, from newest to oldest.
	 */
	key_index = V_tcp_fastopen_keys.newest;
	for (i = 0; i < V_tcp_fastopen_numkeys; i++) {
		cur_cookie =
		    tcp_fastopen_make_cookie(V_tcp_fastopen_keys.key[key_index],
			inc);
		if (i == 0)
			*latest_cookie = cur_cookie;
		rv = tcp_fastopen_find_cookie_match_locked(cookie, &cur_cookie);
		if (rv)
			goto out;
		if (key_index == 0)
			key_index = TCP_FASTOPEN_MAX_KEYS - 1;
		else
			key_index--;
	}
	rv = 0;

 out:
	TCP_FASTOPEN_KEYS_RUNLOCK(&tracker);
	return (rv);
}

static int
sysctl_net_inet_tcp_fastopen_autokey(SYSCTL_HANDLER_ARGS)
{
	int error;
	unsigned int new;

	new = V_tcp_fastopen_autokey;
	error = sysctl_handle_int(oidp, &new, 0, req);
	if (error == 0 && req->newptr) {
		if (new > (INT_MAX / hz))
			return (EINVAL);

		TCP_FASTOPEN_KEYS_WLOCK();
		if (V_tcp_fastopen_server_enable) {
			if (V_tcp_fastopen_autokey && !new)
				callout_stop(&V_tcp_fastopen_autokey_ctx.c);
			else if (new)
				callout_reset(&V_tcp_fastopen_autokey_ctx.c,
				    new * hz, tcp_fastopen_autokey_callout,
				    &V_tcp_fastopen_autokey_ctx);
		}
		V_tcp_fastopen_autokey = new;
		TCP_FASTOPEN_KEYS_WUNLOCK();
	}

	return (error);
}

static int
sysctl_net_inet_tcp_fastopen_psk_enable(SYSCTL_HANDLER_ARGS)
{
	int error;
	unsigned int new;

	new = V_tcp_fastopen_psk_enable;
	error = sysctl_handle_int(oidp, &new, 0, req);
	if (error == 0 && req->newptr) {
		if (V_tcp_fastopen_psk_enable && !new) {
			/* enabled -> disabled */
			TCP_FASTOPEN_KEYS_WLOCK();
			V_tcp_fastopen_numpsks = 0;
			V_tcp_fastopen_keys.newest_psk =
			    TCP_FASTOPEN_MAX_PSKS - 1;
			V_tcp_fastopen_psk_enable = 0;
			TCP_FASTOPEN_KEYS_WUNLOCK();
		} else if (!V_tcp_fastopen_psk_enable && new) {
			/* disabled -> enabled */
			TCP_FASTOPEN_KEYS_WLOCK();
			V_tcp_fastopen_psk_enable = 1;
			TCP_FASTOPEN_KEYS_WUNLOCK();
		}
	}
	return (error);
}

static int
sysctl_net_inet_tcp_fastopen_server_enable(SYSCTL_HANDLER_ARGS)
{
	int error;
	unsigned int new;

	new = V_tcp_fastopen_server_enable;
	error = sysctl_handle_int(oidp, &new, 0, req);
	if (error == 0 && req->newptr) {
		if (V_tcp_fastopen_server_enable && !new) {
			/* enabled -> disabled */
			TCP_FASTOPEN_KEYS_WLOCK();
			V_tcp_fastopen_numkeys = 0;
			V_tcp_fastopen_keys.newest = TCP_FASTOPEN_MAX_KEYS - 1;
			if (V_tcp_fastopen_autokey)
				callout_stop(&V_tcp_fastopen_autokey_ctx.c);
			V_tcp_fastopen_numpsks = 0;
			V_tcp_fastopen_keys.newest_psk =
			    TCP_FASTOPEN_MAX_PSKS - 1;
			V_tcp_fastopen_server_enable = 0;
			TCP_FASTOPEN_KEYS_WUNLOCK();
		} else if (!V_tcp_fastopen_server_enable && new) {
			/* disabled -> enabled */
			TCP_FASTOPEN_KEYS_WLOCK();
			if (V_tcp_fastopen_autokey &&
			    (V_tcp_fastopen_numkeys == 0)) {
				tcp_fastopen_autokey_locked();
				callout_reset(&V_tcp_fastopen_autokey_ctx.c,
				    V_tcp_fastopen_autokey * hz,
				    tcp_fastopen_autokey_callout,
				    &V_tcp_fastopen_autokey_ctx);
			}
			V_tcp_fastopen_server_enable = 1;
			TCP_FASTOPEN_KEYS_WUNLOCK();
		}
	}
	return (error);
}

static int
sysctl_net_inet_tcp_fastopen_setkey(SYSCTL_HANDLER_ARGS)
{
	int error;
	uint8_t newkey[TCP_FASTOPEN_KEY_LEN];

	if (req->oldptr != NULL || req->oldlen != 0)
		return (EINVAL);
	if (req->newptr == NULL)
		return (EPERM);
	if (req->newlen != sizeof(newkey))
		return (EINVAL);
	error = SYSCTL_IN(req, newkey, sizeof(newkey));
	if (error)
		return (error);

	TCP_FASTOPEN_KEYS_WLOCK();
	tcp_fastopen_addkey_locked(newkey);
	TCP_FASTOPEN_KEYS_WUNLOCK();

	return (0);
}

static int
sysctl_net_inet_tcp_fastopen_setpsk(SYSCTL_HANDLER_ARGS)
{
	int error;
	uint8_t newpsk[TCP_FASTOPEN_KEY_LEN];

	if (req->oldptr != NULL || req->oldlen != 0)
		return (EINVAL);
	if (req->newptr == NULL)
		return (EPERM);
	if (req->newlen != sizeof(newpsk))
		return (EINVAL);
	error = SYSCTL_IN(req, newpsk, sizeof(newpsk));
	if (error)
		return (error);

	TCP_FASTOPEN_KEYS_WLOCK();
	tcp_fastopen_addpsk_locked(newpsk);
	TCP_FASTOPEN_KEYS_WUNLOCK();

	return (0);
}

static int
sysctl_net_inet_tcp_fastopen_ccache_bucket_limit(SYSCTL_HANDLER_ARGS)
{
	struct tcp_fastopen_ccache_bucket *ccb;
	int error;
	unsigned int new;
	unsigned int i;

	new = V_tcp_fastopen_ccache.bucket_limit;
	error = sysctl_handle_int(oidp, &new, 0, req);
	if (error == 0 && req->newptr) {
		if ((new == 0) || (new > INT_MAX))
			error = EINVAL;
		else {
			if (new < V_tcp_fastopen_ccache.bucket_limit) {
				for (i = 0; i < V_tcp_fastopen_ccache.buckets;
				     i++) {
					ccb = &V_tcp_fastopen_ccache.base[i];
					tcp_fastopen_ccache_bucket_trim(ccb, new);
				}
			}
			V_tcp_fastopen_ccache.bucket_limit = new;
		}
	}
	return (error);
}

static int
sysctl_net_inet_tcp_fastopen_client_enable(SYSCTL_HANDLER_ARGS)
{
	struct tcp_fastopen_ccache_bucket *ccb;
	int error;
	unsigned int new, i;

	new = V_tcp_fastopen_client_enable;
	error = sysctl_handle_int(oidp, &new, 0, req);
	if (error == 0 && req->newptr) {
		if (V_tcp_fastopen_client_enable && !new) {
			/* enabled -> disabled */
			for (i = 0; i < V_tcp_fastopen_ccache.buckets; i++) {
				ccb = &V_tcp_fastopen_ccache.base[i];
				KASSERT(ccb->ccb_num_entries > -1,
				    ("%s: ccb->ccb_num_entries %d is negative",
					__func__, ccb->ccb_num_entries));
				tcp_fastopen_ccache_bucket_trim(ccb, 0);
			}
			V_tcp_fastopen_client_enable = 0;
		} else if (!V_tcp_fastopen_client_enable && new) {
			/* disabled -> enabled */
			for (i = 0; i < V_tcp_fastopen_ccache.buckets; i++) {
				ccb = &V_tcp_fastopen_ccache.base[i];
				CCB_LOCK(ccb);
				KASSERT(TAILQ_EMPTY(&ccb->ccb_entries),
				    ("%s: ccb->ccb_entries not empty", __func__));
				KASSERT(ccb->ccb_num_entries == -1,
				    ("%s: ccb->ccb_num_entries %d not -1", __func__,
					ccb->ccb_num_entries));
				ccb->ccb_num_entries = 0; /* enable bucket */
				CCB_UNLOCK(ccb);
			}
			V_tcp_fastopen_client_enable = 1;
		}
	}
	return (error);
}

void
tcp_fastopen_connect(struct tcpcb *tp)
{
	struct inpcb *inp;
	struct tcp_fastopen_ccache_bucket *ccb;
	struct tcp_fastopen_ccache_entry *cce;
	sbintime_t now;
	uint16_t server_mss;
	uint64_t psk_cookie;

	psk_cookie = 0;
	inp = tp->t_inpcb;
	cce = tcp_fastopen_ccache_lookup(&inp->inp_inc, &ccb);
	if (cce) {
		if (cce->disable_time == 0) {
			if ((cce->cookie_len > 0) &&
			    (tp->t_tfo_client_cookie_len ==
			     TCP_FASTOPEN_PSK_LEN)) {
				psk_cookie =
				    tcp_fastopen_make_psk_cookie(
					tp->t_tfo_cookie.client,
					cce->cookie, cce->cookie_len);
			} else {
				tp->t_tfo_client_cookie_len = cce->cookie_len;
				memcpy(tp->t_tfo_cookie.client, cce->cookie,
				    cce->cookie_len);
			}
			server_mss = cce->server_mss;
			CCB_UNLOCK(ccb);
			if (tp->t_tfo_client_cookie_len ==
			    TCP_FASTOPEN_PSK_LEN && psk_cookie) {
				tp->t_tfo_client_cookie_len =
				    TCP_FASTOPEN_COOKIE_LEN;
				memcpy(tp->t_tfo_cookie.client, &psk_cookie,
				    TCP_FASTOPEN_COOKIE_LEN);
			}
			tcp_mss(tp, server_mss ? server_mss : -1);
			tp->snd_wnd = tp->t_maxseg;
		} else {
			/*
			 * The path is disabled.  Check the time and
			 * possibly re-enable.
			 */
			now = getsbinuptime();
			if (now - cce->disable_time >
			    ((sbintime_t)V_tcp_fastopen_path_disable_time << 32)) {
				/*
				 * Re-enable path.  Force a TFO cookie
				 * request.  Forget the old MSS as it may be
				 * bogus now, and we will rediscover it in
				 * the SYN|ACK.
				 */
				cce->disable_time = 0;
				cce->server_mss = 0;
				cce->cookie_len = 0;
				/*
				 * tp->t_tfo... cookie details are already
				 * zero from the tcpcb init.
				 */
			} else {
				/*
				 * Path is disabled, so disable TFO on this
				 * connection.
				 */
				tp->t_flags &= ~TF_FASTOPEN;
			}
			CCB_UNLOCK(ccb);
			tcp_mss(tp, -1);
			/*
			 * snd_wnd is irrelevant since we are either forcing
			 * a TFO cookie request or disabling TFO - either
			 * way, no data with the SYN.
			 */
		}
	} else {
		/*
		 * A new entry for this path will be created when a SYN|ACK
		 * comes back, or the attempt otherwise fails.
		 */
		CCB_UNLOCK(ccb);
		tcp_mss(tp, -1);
		/*
		 * snd_wnd is irrelevant since we are forcing a TFO cookie
		 * request.
		 */
	}
}

void
tcp_fastopen_disable_path(struct tcpcb *tp)
{
	struct in_conninfo *inc = &tp->t_inpcb->inp_inc;
	struct tcp_fastopen_ccache_bucket *ccb;
	struct tcp_fastopen_ccache_entry *cce;

	cce = tcp_fastopen_ccache_lookup(inc, &ccb);
	if (cce) {
		cce->server_mss = 0;
		cce->cookie_len = 0;
		/*
		 * Preserve the existing disable time if it is already
		 * disabled.
		 */
		if (cce->disable_time == 0)
			cce->disable_time = getsbinuptime();
	} else /* use invalid cookie len to create disabled entry */
		tcp_fastopen_ccache_create(ccb, inc, 0,
	   	    TCP_FASTOPEN_MAX_COOKIE_LEN + 1, NULL);

	CCB_UNLOCK(ccb);
	tp->t_flags &= ~TF_FASTOPEN;
}

void
tcp_fastopen_update_cache(struct tcpcb *tp, uint16_t mss,
    uint8_t cookie_len, uint8_t *cookie)
{
	struct in_conninfo *inc = &tp->t_inpcb->inp_inc;
	struct tcp_fastopen_ccache_bucket *ccb;
	struct tcp_fastopen_ccache_entry *cce;

	cce = tcp_fastopen_ccache_lookup(inc, &ccb);
	if (cce) {
		if ((cookie_len >= TCP_FASTOPEN_MIN_COOKIE_LEN) &&
		    (cookie_len <= TCP_FASTOPEN_MAX_COOKIE_LEN) &&
		    ((cookie_len & 0x1) == 0)) {
			cce->server_mss = mss;
			cce->cookie_len = cookie_len;
			memcpy(cce->cookie, cookie, cookie_len);
			cce->disable_time = 0;
		} else {
			/* invalid cookie length, disable entry */
			cce->server_mss = 0;
			cce->cookie_len = 0;
			/*
			 * Preserve the existing disable time if it is
			 * already disabled.
			 */
			if (cce->disable_time == 0)
				cce->disable_time = getsbinuptime();
		}
	} else
		tcp_fastopen_ccache_create(ccb, inc, mss, cookie_len, cookie);

	CCB_UNLOCK(ccb);
}

static struct tcp_fastopen_ccache_entry *
tcp_fastopen_ccache_lookup(struct in_conninfo *inc,
    struct tcp_fastopen_ccache_bucket **ccbp)
{
	struct tcp_fastopen_ccache_bucket *ccb;
	struct tcp_fastopen_ccache_entry *cce;
	uint32_t last_word;
	uint32_t hash;

	hash = jenkins_hash32((uint32_t *)&inc->inc_ie.ie_dependladdr, 4,
	    V_tcp_fastopen_ccache.secret);
	hash = jenkins_hash32((uint32_t *)&inc->inc_ie.ie_dependfaddr, 4,
	    hash);
	last_word = inc->inc_fport;
	hash = jenkins_hash32(&last_word, 1, hash);
	ccb = &V_tcp_fastopen_ccache.base[hash & V_tcp_fastopen_ccache.mask];
	*ccbp = ccb;
	CCB_LOCK(ccb);

	/*
	 * Always returns with locked bucket.
	 */
	TAILQ_FOREACH(cce, &ccb->ccb_entries, cce_link)
		if ((!(cce->af == AF_INET6) == !(inc->inc_flags & INC_ISIPV6)) &&
		    (cce->server_port == inc->inc_ie.ie_fport) &&
		    (((cce->af == AF_INET) &&
		      (cce->cce_client_ip.v4.s_addr == inc->inc_laddr.s_addr) &&
		      (cce->cce_server_ip.v4.s_addr == inc->inc_faddr.s_addr)) ||
		     ((cce->af == AF_INET6) &&
		      IN6_ARE_ADDR_EQUAL(&cce->cce_client_ip.v6, &inc->inc6_laddr) &&
		      IN6_ARE_ADDR_EQUAL(&cce->cce_server_ip.v6, &inc->inc6_faddr))))
			break;

	return (cce);
}

static struct tcp_fastopen_ccache_entry *
tcp_fastopen_ccache_create(struct tcp_fastopen_ccache_bucket *ccb,
    struct in_conninfo *inc, uint16_t mss, uint8_t cookie_len, uint8_t *cookie)
{
	struct tcp_fastopen_ccache_entry *cce;

	/*
	 * 1. Create a new entry, or
	 * 2. Reclaim an existing entry, or
	 * 3. Fail
	 */

	CCB_LOCK_ASSERT(ccb);

	cce = NULL;
	if (ccb->ccb_num_entries < V_tcp_fastopen_ccache.bucket_limit)
		cce = uma_zalloc(V_tcp_fastopen_ccache.zone, M_NOWAIT);

	if (cce == NULL) {
		/*
		 * At bucket limit, or out of memory - reclaim last
		 * entry in bucket.
		 */
		cce = TAILQ_LAST(&ccb->ccb_entries, bucket_entries);
		if (cce == NULL) {
			/* XXX count this event */
			return (NULL);
		}

		TAILQ_REMOVE(&ccb->ccb_entries, cce, cce_link);
	} else
		ccb->ccb_num_entries++;

	TAILQ_INSERT_HEAD(&ccb->ccb_entries, cce, cce_link);
	cce->af = (inc->inc_flags & INC_ISIPV6) ? AF_INET6 : AF_INET;
	if (cce->af == AF_INET) {
		cce->cce_client_ip.v4 = inc->inc_laddr;
		cce->cce_server_ip.v4 = inc->inc_faddr;
	} else {
		cce->cce_client_ip.v6 = inc->inc6_laddr;
		cce->cce_server_ip.v6 = inc->inc6_faddr;
	}
	cce->server_port = inc->inc_fport;
	if ((cookie_len >= TCP_FASTOPEN_MIN_COOKIE_LEN) &&
	    (cookie_len <= TCP_FASTOPEN_MAX_COOKIE_LEN) &&
	    ((cookie_len & 0x1) == 0)) {
		cce->server_mss = mss;
		cce->cookie_len = cookie_len;
		memcpy(cce->cookie, cookie, cookie_len);
		cce->disable_time = 0;
	} else {
		/* invalid cookie length, disable cce */
		cce->server_mss = 0;
		cce->cookie_len = 0;
		cce->disable_time = getsbinuptime();
	}

	return (cce);
}

static void
tcp_fastopen_ccache_bucket_trim(struct tcp_fastopen_ccache_bucket *ccb,
    unsigned int limit)
{
	struct tcp_fastopen_ccache_entry *cce, *cce_tmp;
	unsigned int entries;

	CCB_LOCK(ccb);
	entries = 0;
	TAILQ_FOREACH_SAFE(cce, &ccb->ccb_entries, cce_link, cce_tmp) {
		entries++;
		if (entries > limit)
			tcp_fastopen_ccache_entry_drop(cce, ccb);
	}
	KASSERT(ccb->ccb_num_entries <= (int)limit,
	    ("%s: ccb->ccb_num_entries %d exceeds limit %d", __func__,
		ccb->ccb_num_entries, limit));
	if (limit == 0) {
		KASSERT(TAILQ_EMPTY(&ccb->ccb_entries),
		    ("%s: ccb->ccb_entries not empty", __func__));
		ccb->ccb_num_entries = -1; /* disable bucket */
	}
	CCB_UNLOCK(ccb);
}

static void
tcp_fastopen_ccache_entry_drop(struct tcp_fastopen_ccache_entry *cce,
    struct tcp_fastopen_ccache_bucket *ccb)
{

	CCB_LOCK_ASSERT(ccb);

	TAILQ_REMOVE(&ccb->ccb_entries, cce, cce_link);
	ccb->ccb_num_entries--;
	uma_zfree(V_tcp_fastopen_ccache.zone, cce);
}

static int
sysctl_net_inet_tcp_fastopen_ccache_list(SYSCTL_HANDLER_ARGS)
{
	struct sbuf sb;
	struct tcp_fastopen_ccache_bucket *ccb;
	struct tcp_fastopen_ccache_entry *cce;
	sbintime_t now, duration, limit;
	const int linesize = 128;
	int i, error, num_entries;
	unsigned int j;
#ifdef INET6
	char clt_buf[INET6_ADDRSTRLEN], srv_buf[INET6_ADDRSTRLEN];
#else
	char clt_buf[INET_ADDRSTRLEN], srv_buf[INET_ADDRSTRLEN];
#endif

	if (jailed_without_vnet(curthread->td_ucred) != 0)
		return (EPERM);

	/* Only allow root to read the client cookie cache */
	if (curthread->td_ucred->cr_uid != 0)
		return (EPERM);

	num_entries = 0;
	for (i = 0; i < V_tcp_fastopen_ccache.buckets; i++) {
		ccb = &V_tcp_fastopen_ccache.base[i];
		CCB_LOCK(ccb);
		if (ccb->ccb_num_entries > 0)
			num_entries += ccb->ccb_num_entries;
		CCB_UNLOCK(ccb);
	}
	sbuf_new(&sb, NULL, linesize * (num_entries + 1), SBUF_INCLUDENUL);

	sbuf_printf(&sb,
	            "\nLocal IP address     Remote IP address     Port   MSS"
	            " Disabled Cookie\n");

	now = getsbinuptime();
	limit = (sbintime_t)V_tcp_fastopen_path_disable_time << 32;
	for (i = 0; i < V_tcp_fastopen_ccache.buckets; i++) {
		ccb = &V_tcp_fastopen_ccache.base[i];
		CCB_LOCK(ccb);
		TAILQ_FOREACH(cce, &ccb->ccb_entries, cce_link) {
			if (cce->disable_time != 0) {
				duration = now - cce->disable_time;
				if (limit >= duration)
					duration = limit - duration;
				else
					duration = 0;
			} else
				duration = 0;
			sbuf_printf(&sb,
			            "%-20s %-20s %5u %5u ",
			            inet_ntop(cce->af, &cce->cce_client_ip,
			                clt_buf, sizeof(clt_buf)),
			            inet_ntop(cce->af, &cce->cce_server_ip,
			                srv_buf, sizeof(srv_buf)),
			            ntohs(cce->server_port),
			            cce->server_mss);
			if (duration > 0)
				sbuf_printf(&sb, "%7ds ", sbintime_getsec(duration));
			else
				sbuf_printf(&sb, "%8s ", "No");
			for (j = 0; j < cce->cookie_len; j++)
				sbuf_printf(&sb, "%02x", cce->cookie[j]);
			sbuf_putc(&sb, '\n');
		}
		CCB_UNLOCK(ccb);
	}
	error = sbuf_finish(&sb);
	if (error == 0)
		error = SYSCTL_OUT(req, sbuf_data(&sb), sbuf_len(&sb));
	sbuf_delete(&sb);
	return (error);
}