1*a9643ea8Slogwang /*- 2*a9643ea8Slogwang * Copyright (c) 2002 Andre Oppermann, Internet Business Solutions AG 3*a9643ea8Slogwang * All rights reserved. 4*a9643ea8Slogwang * 5*a9643ea8Slogwang * Redistribution and use in source and binary forms, with or without 6*a9643ea8Slogwang * modification, are permitted provided that the following conditions 7*a9643ea8Slogwang * are met: 8*a9643ea8Slogwang * 1. Redistributions of source code must retain the above copyright 9*a9643ea8Slogwang * notice, this list of conditions and the following disclaimer. 10*a9643ea8Slogwang * 2. Redistributions in binary form must reproduce the above copyright 11*a9643ea8Slogwang * notice, this list of conditions and the following disclaimer in the 12*a9643ea8Slogwang * documentation and/or other materials provided with the distribution. 13*a9643ea8Slogwang * 3. The name of the author may not be used to endorse or promote 14*a9643ea8Slogwang * products derived from this software without specific prior written 15*a9643ea8Slogwang * permission. 16*a9643ea8Slogwang * 17*a9643ea8Slogwang * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 18*a9643ea8Slogwang * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19*a9643ea8Slogwang * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20*a9643ea8Slogwang * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 21*a9643ea8Slogwang * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22*a9643ea8Slogwang * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 23*a9643ea8Slogwang * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 24*a9643ea8Slogwang * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 25*a9643ea8Slogwang * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 26*a9643ea8Slogwang * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 27*a9643ea8Slogwang * SUCH DAMAGE. 28*a9643ea8Slogwang */ 29*a9643ea8Slogwang 30*a9643ea8Slogwang /* 31*a9643ea8Slogwang * The tcp_hostcache moves the tcp-specific cached metrics from the routing 32*a9643ea8Slogwang * table to a dedicated structure indexed by the remote IP address. It keeps 33*a9643ea8Slogwang * information on the measured TCP parameters of past TCP sessions to allow 34*a9643ea8Slogwang * better initial start values to be used with later connections to/from the 35*a9643ea8Slogwang * same source. Depending on the network parameters (delay, max MTU, 36*a9643ea8Slogwang * congestion window) between local and remote sites, this can lead to 37*a9643ea8Slogwang * significant speed-ups for new TCP connections after the first one. 38*a9643ea8Slogwang * 39*a9643ea8Slogwang * Due to the tcp_hostcache, all TCP-specific metrics information in the 40*a9643ea8Slogwang * routing table have been removed. The inpcb no longer keeps a pointer to 41*a9643ea8Slogwang * the routing entry, and protocol-initiated route cloning has been removed 42*a9643ea8Slogwang * as well. With these changes, the routing table has gone back to being 43*a9643ea8Slogwang * more lightwight and only carries information related to packet forwarding. 44*a9643ea8Slogwang * 45*a9643ea8Slogwang * tcp_hostcache is designed for multiple concurrent access in SMP 46*a9643ea8Slogwang * environments and high contention. All bucket rows have their own lock and 47*a9643ea8Slogwang * thus multiple lookups and modifies can be done at the same time as long as 48*a9643ea8Slogwang * they are in different bucket rows. If a request for insertion of a new 49*a9643ea8Slogwang * record can't be satisfied, it simply returns an empty structure. Nobody 50*a9643ea8Slogwang * and nothing outside of tcp_hostcache.c will ever point directly to any 51*a9643ea8Slogwang * entry in the tcp_hostcache. All communication is done in an 52*a9643ea8Slogwang * object-oriented way and only functions of tcp_hostcache will manipulate 53*a9643ea8Slogwang * hostcache entries. Otherwise, we are unable to achieve good behaviour in 54*a9643ea8Slogwang * concurrent access situations. Since tcp_hostcache is only caching 55*a9643ea8Slogwang * information, there are no fatal consequences if we either can't satisfy 56*a9643ea8Slogwang * any particular request or have to drop/overwrite an existing entry because 57*a9643ea8Slogwang * of bucket limit memory constrains. 58*a9643ea8Slogwang */ 59*a9643ea8Slogwang 60*a9643ea8Slogwang /* 61*a9643ea8Slogwang * Many thanks to jlemon for basic structure of tcp_syncache which is being 62*a9643ea8Slogwang * followed here. 63*a9643ea8Slogwang */ 64*a9643ea8Slogwang 65*a9643ea8Slogwang #include <sys/cdefs.h> 66*a9643ea8Slogwang __FBSDID("$FreeBSD$"); 67*a9643ea8Slogwang 68*a9643ea8Slogwang #include "opt_inet6.h" 69*a9643ea8Slogwang 70*a9643ea8Slogwang #include <sys/param.h> 71*a9643ea8Slogwang #include <sys/systm.h> 72*a9643ea8Slogwang #include <sys/kernel.h> 73*a9643ea8Slogwang #include <sys/lock.h> 74*a9643ea8Slogwang #include <sys/mutex.h> 75*a9643ea8Slogwang #include <sys/malloc.h> 76*a9643ea8Slogwang #include <sys/sbuf.h> 77*a9643ea8Slogwang #include <sys/socket.h> 78*a9643ea8Slogwang #include <sys/socketvar.h> 79*a9643ea8Slogwang #include <sys/sysctl.h> 80*a9643ea8Slogwang 81*a9643ea8Slogwang #include <net/if.h> 82*a9643ea8Slogwang #include <net/if_var.h> 83*a9643ea8Slogwang #include <net/route.h> 84*a9643ea8Slogwang #include <net/vnet.h> 85*a9643ea8Slogwang 86*a9643ea8Slogwang #include <netinet/in.h> 87*a9643ea8Slogwang #include <netinet/in_systm.h> 88*a9643ea8Slogwang #include <netinet/ip.h> 89*a9643ea8Slogwang #include <netinet/in_var.h> 90*a9643ea8Slogwang #include <netinet/in_pcb.h> 91*a9643ea8Slogwang #include <netinet/ip_var.h> 92*a9643ea8Slogwang #ifdef INET6 93*a9643ea8Slogwang #include <netinet/ip6.h> 94*a9643ea8Slogwang #include <netinet6/ip6_var.h> 95*a9643ea8Slogwang #endif 96*a9643ea8Slogwang #include <netinet/tcp.h> 97*a9643ea8Slogwang #include <netinet/tcp_var.h> 98*a9643ea8Slogwang #include <netinet/tcp_hostcache.h> 99*a9643ea8Slogwang #ifdef INET6 100*a9643ea8Slogwang #include <netinet6/tcp6_var.h> 101*a9643ea8Slogwang #endif 102*a9643ea8Slogwang 103*a9643ea8Slogwang #include <vm/uma.h> 104*a9643ea8Slogwang 105*a9643ea8Slogwang /* Arbitrary values */ 106*a9643ea8Slogwang #define TCP_HOSTCACHE_HASHSIZE 512 107*a9643ea8Slogwang #define TCP_HOSTCACHE_BUCKETLIMIT 30 108*a9643ea8Slogwang #define TCP_HOSTCACHE_EXPIRE 60*60 /* one hour */ 109*a9643ea8Slogwang #define TCP_HOSTCACHE_PRUNE 5*60 /* every 5 minutes */ 110*a9643ea8Slogwang 111*a9643ea8Slogwang static VNET_DEFINE(struct tcp_hostcache, tcp_hostcache); 112*a9643ea8Slogwang #define V_tcp_hostcache VNET(tcp_hostcache) 113*a9643ea8Slogwang 114*a9643ea8Slogwang static VNET_DEFINE(struct callout, tcp_hc_callout); 115*a9643ea8Slogwang #define V_tcp_hc_callout VNET(tcp_hc_callout) 116*a9643ea8Slogwang 117*a9643ea8Slogwang static struct hc_metrics *tcp_hc_lookup(struct in_conninfo *); 118*a9643ea8Slogwang static struct hc_metrics *tcp_hc_insert(struct in_conninfo *); 119*a9643ea8Slogwang static int sysctl_tcp_hc_list(SYSCTL_HANDLER_ARGS); 120*a9643ea8Slogwang static int sysctl_tcp_hc_purgenow(SYSCTL_HANDLER_ARGS); 121*a9643ea8Slogwang static void tcp_hc_purge_internal(int); 122*a9643ea8Slogwang static void tcp_hc_purge(void *); 123*a9643ea8Slogwang 124*a9643ea8Slogwang static SYSCTL_NODE(_net_inet_tcp, OID_AUTO, hostcache, CTLFLAG_RW, 0, 125*a9643ea8Slogwang "TCP Host cache"); 126*a9643ea8Slogwang 127*a9643ea8Slogwang SYSCTL_UINT(_net_inet_tcp_hostcache, OID_AUTO, cachelimit, CTLFLAG_VNET | CTLFLAG_RDTUN, 128*a9643ea8Slogwang &VNET_NAME(tcp_hostcache.cache_limit), 0, 129*a9643ea8Slogwang "Overall entry limit for hostcache"); 130*a9643ea8Slogwang 131*a9643ea8Slogwang SYSCTL_UINT(_net_inet_tcp_hostcache, OID_AUTO, hashsize, CTLFLAG_VNET | CTLFLAG_RDTUN, 132*a9643ea8Slogwang &VNET_NAME(tcp_hostcache.hashsize), 0, 133*a9643ea8Slogwang "Size of TCP hostcache hashtable"); 134*a9643ea8Slogwang 135*a9643ea8Slogwang SYSCTL_UINT(_net_inet_tcp_hostcache, OID_AUTO, bucketlimit, 136*a9643ea8Slogwang CTLFLAG_VNET | CTLFLAG_RDTUN, &VNET_NAME(tcp_hostcache.bucket_limit), 0, 137*a9643ea8Slogwang "Per-bucket hash limit for hostcache"); 138*a9643ea8Slogwang 139*a9643ea8Slogwang SYSCTL_UINT(_net_inet_tcp_hostcache, OID_AUTO, count, CTLFLAG_VNET | CTLFLAG_RD, 140*a9643ea8Slogwang &VNET_NAME(tcp_hostcache.cache_count), 0, 141*a9643ea8Slogwang "Current number of entries in hostcache"); 142*a9643ea8Slogwang 143*a9643ea8Slogwang SYSCTL_INT(_net_inet_tcp_hostcache, OID_AUTO, expire, CTLFLAG_VNET | CTLFLAG_RW, 144*a9643ea8Slogwang &VNET_NAME(tcp_hostcache.expire), 0, 145*a9643ea8Slogwang "Expire time of TCP hostcache entries"); 146*a9643ea8Slogwang 147*a9643ea8Slogwang SYSCTL_INT(_net_inet_tcp_hostcache, OID_AUTO, prune, CTLFLAG_VNET | CTLFLAG_RW, 148*a9643ea8Slogwang &VNET_NAME(tcp_hostcache.prune), 0, 149*a9643ea8Slogwang "Time between purge runs"); 150*a9643ea8Slogwang 151*a9643ea8Slogwang SYSCTL_INT(_net_inet_tcp_hostcache, OID_AUTO, purge, CTLFLAG_VNET | CTLFLAG_RW, 152*a9643ea8Slogwang &VNET_NAME(tcp_hostcache.purgeall), 0, 153*a9643ea8Slogwang "Expire all entires on next purge run"); 154*a9643ea8Slogwang 155*a9643ea8Slogwang SYSCTL_PROC(_net_inet_tcp_hostcache, OID_AUTO, list, 156*a9643ea8Slogwang CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_SKIP, 0, 0, 157*a9643ea8Slogwang sysctl_tcp_hc_list, "A", "List of all hostcache entries"); 158*a9643ea8Slogwang 159*a9643ea8Slogwang SYSCTL_PROC(_net_inet_tcp_hostcache, OID_AUTO, purgenow, 160*a9643ea8Slogwang CTLTYPE_INT | CTLFLAG_RW, NULL, 0, 161*a9643ea8Slogwang sysctl_tcp_hc_purgenow, "I", "Immediately purge all entries"); 162*a9643ea8Slogwang 163*a9643ea8Slogwang static MALLOC_DEFINE(M_HOSTCACHE, "hostcache", "TCP hostcache"); 164*a9643ea8Slogwang 165*a9643ea8Slogwang #define HOSTCACHE_HASH(ip) \ 166*a9643ea8Slogwang (((ip)->s_addr ^ ((ip)->s_addr >> 7) ^ ((ip)->s_addr >> 17)) & \ 167*a9643ea8Slogwang V_tcp_hostcache.hashmask) 168*a9643ea8Slogwang 169*a9643ea8Slogwang /* XXX: What is the recommended hash to get good entropy for IPv6 addresses? */ 170*a9643ea8Slogwang #define HOSTCACHE_HASH6(ip6) \ 171*a9643ea8Slogwang (((ip6)->s6_addr32[0] ^ \ 172*a9643ea8Slogwang (ip6)->s6_addr32[1] ^ \ 173*a9643ea8Slogwang (ip6)->s6_addr32[2] ^ \ 174*a9643ea8Slogwang (ip6)->s6_addr32[3]) & \ 175*a9643ea8Slogwang V_tcp_hostcache.hashmask) 176*a9643ea8Slogwang 177*a9643ea8Slogwang #define THC_LOCK(lp) mtx_lock(lp) 178*a9643ea8Slogwang #define THC_UNLOCK(lp) mtx_unlock(lp) 179*a9643ea8Slogwang 180*a9643ea8Slogwang void 181*a9643ea8Slogwang tcp_hc_init(void) 182*a9643ea8Slogwang { 183*a9643ea8Slogwang u_int cache_limit; 184*a9643ea8Slogwang int i; 185*a9643ea8Slogwang 186*a9643ea8Slogwang /* 187*a9643ea8Slogwang * Initialize hostcache structures. 188*a9643ea8Slogwang */ 189*a9643ea8Slogwang V_tcp_hostcache.cache_count = 0; 190*a9643ea8Slogwang V_tcp_hostcache.hashsize = TCP_HOSTCACHE_HASHSIZE; 191*a9643ea8Slogwang V_tcp_hostcache.bucket_limit = TCP_HOSTCACHE_BUCKETLIMIT; 192*a9643ea8Slogwang V_tcp_hostcache.expire = TCP_HOSTCACHE_EXPIRE; 193*a9643ea8Slogwang V_tcp_hostcache.prune = TCP_HOSTCACHE_PRUNE; 194*a9643ea8Slogwang 195*a9643ea8Slogwang TUNABLE_INT_FETCH("net.inet.tcp.hostcache.hashsize", 196*a9643ea8Slogwang &V_tcp_hostcache.hashsize); 197*a9643ea8Slogwang if (!powerof2(V_tcp_hostcache.hashsize)) { 198*a9643ea8Slogwang printf("WARNING: hostcache hash size is not a power of 2.\n"); 199*a9643ea8Slogwang V_tcp_hostcache.hashsize = TCP_HOSTCACHE_HASHSIZE; /* default */ 200*a9643ea8Slogwang } 201*a9643ea8Slogwang V_tcp_hostcache.hashmask = V_tcp_hostcache.hashsize - 1; 202*a9643ea8Slogwang 203*a9643ea8Slogwang TUNABLE_INT_FETCH("net.inet.tcp.hostcache.bucketlimit", 204*a9643ea8Slogwang &V_tcp_hostcache.bucket_limit); 205*a9643ea8Slogwang 206*a9643ea8Slogwang cache_limit = V_tcp_hostcache.hashsize * V_tcp_hostcache.bucket_limit; 207*a9643ea8Slogwang V_tcp_hostcache.cache_limit = cache_limit; 208*a9643ea8Slogwang TUNABLE_INT_FETCH("net.inet.tcp.hostcache.cachelimit", 209*a9643ea8Slogwang &V_tcp_hostcache.cache_limit); 210*a9643ea8Slogwang if (V_tcp_hostcache.cache_limit > cache_limit) 211*a9643ea8Slogwang V_tcp_hostcache.cache_limit = cache_limit; 212*a9643ea8Slogwang 213*a9643ea8Slogwang /* 214*a9643ea8Slogwang * Allocate the hash table. 215*a9643ea8Slogwang */ 216*a9643ea8Slogwang V_tcp_hostcache.hashbase = (struct hc_head *) 217*a9643ea8Slogwang malloc(V_tcp_hostcache.hashsize * sizeof(struct hc_head), 218*a9643ea8Slogwang M_HOSTCACHE, M_WAITOK | M_ZERO); 219*a9643ea8Slogwang 220*a9643ea8Slogwang /* 221*a9643ea8Slogwang * Initialize the hash buckets. 222*a9643ea8Slogwang */ 223*a9643ea8Slogwang for (i = 0; i < V_tcp_hostcache.hashsize; i++) { 224*a9643ea8Slogwang TAILQ_INIT(&V_tcp_hostcache.hashbase[i].hch_bucket); 225*a9643ea8Slogwang V_tcp_hostcache.hashbase[i].hch_length = 0; 226*a9643ea8Slogwang mtx_init(&V_tcp_hostcache.hashbase[i].hch_mtx, "tcp_hc_entry", 227*a9643ea8Slogwang NULL, MTX_DEF); 228*a9643ea8Slogwang } 229*a9643ea8Slogwang 230*a9643ea8Slogwang /* 231*a9643ea8Slogwang * Allocate the hostcache entries. 232*a9643ea8Slogwang */ 233*a9643ea8Slogwang V_tcp_hostcache.zone = 234*a9643ea8Slogwang uma_zcreate("hostcache", sizeof(struct hc_metrics), 235*a9643ea8Slogwang NULL, NULL, NULL, NULL, UMA_ALIGN_PTR, 0); 236*a9643ea8Slogwang uma_zone_set_max(V_tcp_hostcache.zone, V_tcp_hostcache.cache_limit); 237*a9643ea8Slogwang 238*a9643ea8Slogwang /* 239*a9643ea8Slogwang * Set up periodic cache cleanup. 240*a9643ea8Slogwang */ 241*a9643ea8Slogwang callout_init(&V_tcp_hc_callout, 1); 242*a9643ea8Slogwang callout_reset(&V_tcp_hc_callout, V_tcp_hostcache.prune * hz, 243*a9643ea8Slogwang tcp_hc_purge, curvnet); 244*a9643ea8Slogwang } 245*a9643ea8Slogwang 246*a9643ea8Slogwang #ifdef VIMAGE 247*a9643ea8Slogwang void 248*a9643ea8Slogwang tcp_hc_destroy(void) 249*a9643ea8Slogwang { 250*a9643ea8Slogwang int i; 251*a9643ea8Slogwang 252*a9643ea8Slogwang callout_drain(&V_tcp_hc_callout); 253*a9643ea8Slogwang 254*a9643ea8Slogwang /* Purge all hc entries. */ 255*a9643ea8Slogwang tcp_hc_purge_internal(1); 256*a9643ea8Slogwang 257*a9643ea8Slogwang /* Free the uma zone and the allocated hash table. */ 258*a9643ea8Slogwang uma_zdestroy(V_tcp_hostcache.zone); 259*a9643ea8Slogwang 260*a9643ea8Slogwang for (i = 0; i < V_tcp_hostcache.hashsize; i++) 261*a9643ea8Slogwang mtx_destroy(&V_tcp_hostcache.hashbase[i].hch_mtx); 262*a9643ea8Slogwang free(V_tcp_hostcache.hashbase, M_HOSTCACHE); 263*a9643ea8Slogwang } 264*a9643ea8Slogwang #endif 265*a9643ea8Slogwang 266*a9643ea8Slogwang /* 267*a9643ea8Slogwang * Internal function: look up an entry in the hostcache or return NULL. 268*a9643ea8Slogwang * 269*a9643ea8Slogwang * If an entry has been returned, the caller becomes responsible for 270*a9643ea8Slogwang * unlocking the bucket row after he is done reading/modifying the entry. 271*a9643ea8Slogwang */ 272*a9643ea8Slogwang static struct hc_metrics * 273*a9643ea8Slogwang tcp_hc_lookup(struct in_conninfo *inc) 274*a9643ea8Slogwang { 275*a9643ea8Slogwang int hash; 276*a9643ea8Slogwang struct hc_head *hc_head; 277*a9643ea8Slogwang struct hc_metrics *hc_entry; 278*a9643ea8Slogwang 279*a9643ea8Slogwang KASSERT(inc != NULL, ("tcp_hc_lookup with NULL in_conninfo pointer")); 280*a9643ea8Slogwang 281*a9643ea8Slogwang /* 282*a9643ea8Slogwang * Hash the foreign ip address. 283*a9643ea8Slogwang */ 284*a9643ea8Slogwang if (inc->inc_flags & INC_ISIPV6) 285*a9643ea8Slogwang hash = HOSTCACHE_HASH6(&inc->inc6_faddr); 286*a9643ea8Slogwang else 287*a9643ea8Slogwang hash = HOSTCACHE_HASH(&inc->inc_faddr); 288*a9643ea8Slogwang 289*a9643ea8Slogwang hc_head = &V_tcp_hostcache.hashbase[hash]; 290*a9643ea8Slogwang 291*a9643ea8Slogwang /* 292*a9643ea8Slogwang * Acquire lock for this bucket row; we release the lock if we don't 293*a9643ea8Slogwang * find an entry, otherwise the caller has to unlock after he is 294*a9643ea8Slogwang * done. 295*a9643ea8Slogwang */ 296*a9643ea8Slogwang THC_LOCK(&hc_head->hch_mtx); 297*a9643ea8Slogwang 298*a9643ea8Slogwang /* 299*a9643ea8Slogwang * Iterate through entries in bucket row looking for a match. 300*a9643ea8Slogwang */ 301*a9643ea8Slogwang TAILQ_FOREACH(hc_entry, &hc_head->hch_bucket, rmx_q) { 302*a9643ea8Slogwang if (inc->inc_flags & INC_ISIPV6) { 303*a9643ea8Slogwang /* XXX: check ip6_zoneid */ 304*a9643ea8Slogwang if (memcmp(&inc->inc6_faddr, &hc_entry->ip6, 305*a9643ea8Slogwang sizeof(inc->inc6_faddr)) == 0) 306*a9643ea8Slogwang return hc_entry; 307*a9643ea8Slogwang } else { 308*a9643ea8Slogwang if (memcmp(&inc->inc_faddr, &hc_entry->ip4, 309*a9643ea8Slogwang sizeof(inc->inc_faddr)) == 0) 310*a9643ea8Slogwang return hc_entry; 311*a9643ea8Slogwang } 312*a9643ea8Slogwang } 313*a9643ea8Slogwang 314*a9643ea8Slogwang /* 315*a9643ea8Slogwang * We were unsuccessful and didn't find anything. 316*a9643ea8Slogwang */ 317*a9643ea8Slogwang THC_UNLOCK(&hc_head->hch_mtx); 318*a9643ea8Slogwang return NULL; 319*a9643ea8Slogwang } 320*a9643ea8Slogwang 321*a9643ea8Slogwang /* 322*a9643ea8Slogwang * Internal function: insert an entry into the hostcache or return NULL if 323*a9643ea8Slogwang * unable to allocate a new one. 324*a9643ea8Slogwang * 325*a9643ea8Slogwang * If an entry has been returned, the caller becomes responsible for 326*a9643ea8Slogwang * unlocking the bucket row after he is done reading/modifying the entry. 327*a9643ea8Slogwang */ 328*a9643ea8Slogwang static struct hc_metrics * 329*a9643ea8Slogwang tcp_hc_insert(struct in_conninfo *inc) 330*a9643ea8Slogwang { 331*a9643ea8Slogwang int hash; 332*a9643ea8Slogwang struct hc_head *hc_head; 333*a9643ea8Slogwang struct hc_metrics *hc_entry; 334*a9643ea8Slogwang 335*a9643ea8Slogwang KASSERT(inc != NULL, ("tcp_hc_insert with NULL in_conninfo pointer")); 336*a9643ea8Slogwang 337*a9643ea8Slogwang /* 338*a9643ea8Slogwang * Hash the foreign ip address. 339*a9643ea8Slogwang */ 340*a9643ea8Slogwang if (inc->inc_flags & INC_ISIPV6) 341*a9643ea8Slogwang hash = HOSTCACHE_HASH6(&inc->inc6_faddr); 342*a9643ea8Slogwang else 343*a9643ea8Slogwang hash = HOSTCACHE_HASH(&inc->inc_faddr); 344*a9643ea8Slogwang 345*a9643ea8Slogwang hc_head = &V_tcp_hostcache.hashbase[hash]; 346*a9643ea8Slogwang 347*a9643ea8Slogwang /* 348*a9643ea8Slogwang * Acquire lock for this bucket row; we release the lock if we don't 349*a9643ea8Slogwang * find an entry, otherwise the caller has to unlock after he is 350*a9643ea8Slogwang * done. 351*a9643ea8Slogwang */ 352*a9643ea8Slogwang THC_LOCK(&hc_head->hch_mtx); 353*a9643ea8Slogwang 354*a9643ea8Slogwang /* 355*a9643ea8Slogwang * If the bucket limit is reached, reuse the least-used element. 356*a9643ea8Slogwang */ 357*a9643ea8Slogwang if (hc_head->hch_length >= V_tcp_hostcache.bucket_limit || 358*a9643ea8Slogwang V_tcp_hostcache.cache_count >= V_tcp_hostcache.cache_limit) { 359*a9643ea8Slogwang hc_entry = TAILQ_LAST(&hc_head->hch_bucket, hc_qhead); 360*a9643ea8Slogwang /* 361*a9643ea8Slogwang * At first we were dropping the last element, just to 362*a9643ea8Slogwang * reacquire it in the next two lines again, which isn't very 363*a9643ea8Slogwang * efficient. Instead just reuse the least used element. 364*a9643ea8Slogwang * We may drop something that is still "in-use" but we can be 365*a9643ea8Slogwang * "lossy". 366*a9643ea8Slogwang * Just give up if this bucket row is empty and we don't have 367*a9643ea8Slogwang * anything to replace. 368*a9643ea8Slogwang */ 369*a9643ea8Slogwang if (hc_entry == NULL) { 370*a9643ea8Slogwang THC_UNLOCK(&hc_head->hch_mtx); 371*a9643ea8Slogwang return NULL; 372*a9643ea8Slogwang } 373*a9643ea8Slogwang TAILQ_REMOVE(&hc_head->hch_bucket, hc_entry, rmx_q); 374*a9643ea8Slogwang V_tcp_hostcache.hashbase[hash].hch_length--; 375*a9643ea8Slogwang V_tcp_hostcache.cache_count--; 376*a9643ea8Slogwang TCPSTAT_INC(tcps_hc_bucketoverflow); 377*a9643ea8Slogwang #if 0 378*a9643ea8Slogwang uma_zfree(V_tcp_hostcache.zone, hc_entry); 379*a9643ea8Slogwang #endif 380*a9643ea8Slogwang } else { 381*a9643ea8Slogwang /* 382*a9643ea8Slogwang * Allocate a new entry, or balk if not possible. 383*a9643ea8Slogwang */ 384*a9643ea8Slogwang hc_entry = uma_zalloc(V_tcp_hostcache.zone, M_NOWAIT); 385*a9643ea8Slogwang if (hc_entry == NULL) { 386*a9643ea8Slogwang THC_UNLOCK(&hc_head->hch_mtx); 387*a9643ea8Slogwang return NULL; 388*a9643ea8Slogwang } 389*a9643ea8Slogwang } 390*a9643ea8Slogwang 391*a9643ea8Slogwang /* 392*a9643ea8Slogwang * Initialize basic information of hostcache entry. 393*a9643ea8Slogwang */ 394*a9643ea8Slogwang bzero(hc_entry, sizeof(*hc_entry)); 395*a9643ea8Slogwang if (inc->inc_flags & INC_ISIPV6) { 396*a9643ea8Slogwang hc_entry->ip6 = inc->inc6_faddr; 397*a9643ea8Slogwang hc_entry->ip6_zoneid = inc->inc6_zoneid; 398*a9643ea8Slogwang } else 399*a9643ea8Slogwang hc_entry->ip4 = inc->inc_faddr; 400*a9643ea8Slogwang hc_entry->rmx_head = hc_head; 401*a9643ea8Slogwang hc_entry->rmx_expire = V_tcp_hostcache.expire; 402*a9643ea8Slogwang 403*a9643ea8Slogwang /* 404*a9643ea8Slogwang * Put it upfront. 405*a9643ea8Slogwang */ 406*a9643ea8Slogwang TAILQ_INSERT_HEAD(&hc_head->hch_bucket, hc_entry, rmx_q); 407*a9643ea8Slogwang V_tcp_hostcache.hashbase[hash].hch_length++; 408*a9643ea8Slogwang V_tcp_hostcache.cache_count++; 409*a9643ea8Slogwang TCPSTAT_INC(tcps_hc_added); 410*a9643ea8Slogwang 411*a9643ea8Slogwang return hc_entry; 412*a9643ea8Slogwang } 413*a9643ea8Slogwang 414*a9643ea8Slogwang /* 415*a9643ea8Slogwang * External function: look up an entry in the hostcache and fill out the 416*a9643ea8Slogwang * supplied TCP metrics structure. Fills in NULL when no entry was found or 417*a9643ea8Slogwang * a value is not set. 418*a9643ea8Slogwang */ 419*a9643ea8Slogwang void 420*a9643ea8Slogwang tcp_hc_get(struct in_conninfo *inc, struct hc_metrics_lite *hc_metrics_lite) 421*a9643ea8Slogwang { 422*a9643ea8Slogwang struct hc_metrics *hc_entry; 423*a9643ea8Slogwang 424*a9643ea8Slogwang /* 425*a9643ea8Slogwang * Find the right bucket. 426*a9643ea8Slogwang */ 427*a9643ea8Slogwang hc_entry = tcp_hc_lookup(inc); 428*a9643ea8Slogwang 429*a9643ea8Slogwang /* 430*a9643ea8Slogwang * If we don't have an existing object. 431*a9643ea8Slogwang */ 432*a9643ea8Slogwang if (hc_entry == NULL) { 433*a9643ea8Slogwang bzero(hc_metrics_lite, sizeof(*hc_metrics_lite)); 434*a9643ea8Slogwang return; 435*a9643ea8Slogwang } 436*a9643ea8Slogwang hc_entry->rmx_hits++; 437*a9643ea8Slogwang hc_entry->rmx_expire = V_tcp_hostcache.expire; /* start over again */ 438*a9643ea8Slogwang 439*a9643ea8Slogwang hc_metrics_lite->rmx_mtu = hc_entry->rmx_mtu; 440*a9643ea8Slogwang hc_metrics_lite->rmx_ssthresh = hc_entry->rmx_ssthresh; 441*a9643ea8Slogwang hc_metrics_lite->rmx_rtt = hc_entry->rmx_rtt; 442*a9643ea8Slogwang hc_metrics_lite->rmx_rttvar = hc_entry->rmx_rttvar; 443*a9643ea8Slogwang hc_metrics_lite->rmx_cwnd = hc_entry->rmx_cwnd; 444*a9643ea8Slogwang hc_metrics_lite->rmx_sendpipe = hc_entry->rmx_sendpipe; 445*a9643ea8Slogwang hc_metrics_lite->rmx_recvpipe = hc_entry->rmx_recvpipe; 446*a9643ea8Slogwang 447*a9643ea8Slogwang /* 448*a9643ea8Slogwang * Unlock bucket row. 449*a9643ea8Slogwang */ 450*a9643ea8Slogwang THC_UNLOCK(&hc_entry->rmx_head->hch_mtx); 451*a9643ea8Slogwang } 452*a9643ea8Slogwang 453*a9643ea8Slogwang /* 454*a9643ea8Slogwang * External function: look up an entry in the hostcache and return the 455*a9643ea8Slogwang * discovered path MTU. Returns NULL if no entry is found or value is not 456*a9643ea8Slogwang * set. 457*a9643ea8Slogwang */ 458*a9643ea8Slogwang u_long 459*a9643ea8Slogwang tcp_hc_getmtu(struct in_conninfo *inc) 460*a9643ea8Slogwang { 461*a9643ea8Slogwang struct hc_metrics *hc_entry; 462*a9643ea8Slogwang u_long mtu; 463*a9643ea8Slogwang 464*a9643ea8Slogwang hc_entry = tcp_hc_lookup(inc); 465*a9643ea8Slogwang if (hc_entry == NULL) { 466*a9643ea8Slogwang return 0; 467*a9643ea8Slogwang } 468*a9643ea8Slogwang hc_entry->rmx_hits++; 469*a9643ea8Slogwang hc_entry->rmx_expire = V_tcp_hostcache.expire; /* start over again */ 470*a9643ea8Slogwang 471*a9643ea8Slogwang mtu = hc_entry->rmx_mtu; 472*a9643ea8Slogwang THC_UNLOCK(&hc_entry->rmx_head->hch_mtx); 473*a9643ea8Slogwang return mtu; 474*a9643ea8Slogwang } 475*a9643ea8Slogwang 476*a9643ea8Slogwang /* 477*a9643ea8Slogwang * External function: update the MTU value of an entry in the hostcache. 478*a9643ea8Slogwang * Creates a new entry if none was found. 479*a9643ea8Slogwang */ 480*a9643ea8Slogwang void 481*a9643ea8Slogwang tcp_hc_updatemtu(struct in_conninfo *inc, u_long mtu) 482*a9643ea8Slogwang { 483*a9643ea8Slogwang struct hc_metrics *hc_entry; 484*a9643ea8Slogwang 485*a9643ea8Slogwang /* 486*a9643ea8Slogwang * Find the right bucket. 487*a9643ea8Slogwang */ 488*a9643ea8Slogwang hc_entry = tcp_hc_lookup(inc); 489*a9643ea8Slogwang 490*a9643ea8Slogwang /* 491*a9643ea8Slogwang * If we don't have an existing object, try to insert a new one. 492*a9643ea8Slogwang */ 493*a9643ea8Slogwang if (hc_entry == NULL) { 494*a9643ea8Slogwang hc_entry = tcp_hc_insert(inc); 495*a9643ea8Slogwang if (hc_entry == NULL) 496*a9643ea8Slogwang return; 497*a9643ea8Slogwang } 498*a9643ea8Slogwang hc_entry->rmx_updates++; 499*a9643ea8Slogwang hc_entry->rmx_expire = V_tcp_hostcache.expire; /* start over again */ 500*a9643ea8Slogwang 501*a9643ea8Slogwang hc_entry->rmx_mtu = mtu; 502*a9643ea8Slogwang 503*a9643ea8Slogwang /* 504*a9643ea8Slogwang * Put it upfront so we find it faster next time. 505*a9643ea8Slogwang */ 506*a9643ea8Slogwang TAILQ_REMOVE(&hc_entry->rmx_head->hch_bucket, hc_entry, rmx_q); 507*a9643ea8Slogwang TAILQ_INSERT_HEAD(&hc_entry->rmx_head->hch_bucket, hc_entry, rmx_q); 508*a9643ea8Slogwang 509*a9643ea8Slogwang /* 510*a9643ea8Slogwang * Unlock bucket row. 511*a9643ea8Slogwang */ 512*a9643ea8Slogwang THC_UNLOCK(&hc_entry->rmx_head->hch_mtx); 513*a9643ea8Slogwang } 514*a9643ea8Slogwang 515*a9643ea8Slogwang /* 516*a9643ea8Slogwang * External function: update the TCP metrics of an entry in the hostcache. 517*a9643ea8Slogwang * Creates a new entry if none was found. 518*a9643ea8Slogwang */ 519*a9643ea8Slogwang void 520*a9643ea8Slogwang tcp_hc_update(struct in_conninfo *inc, struct hc_metrics_lite *hcml) 521*a9643ea8Slogwang { 522*a9643ea8Slogwang struct hc_metrics *hc_entry; 523*a9643ea8Slogwang 524*a9643ea8Slogwang hc_entry = tcp_hc_lookup(inc); 525*a9643ea8Slogwang if (hc_entry == NULL) { 526*a9643ea8Slogwang hc_entry = tcp_hc_insert(inc); 527*a9643ea8Slogwang if (hc_entry == NULL) 528*a9643ea8Slogwang return; 529*a9643ea8Slogwang } 530*a9643ea8Slogwang hc_entry->rmx_updates++; 531*a9643ea8Slogwang hc_entry->rmx_expire = V_tcp_hostcache.expire; /* start over again */ 532*a9643ea8Slogwang 533*a9643ea8Slogwang if (hcml->rmx_rtt != 0) { 534*a9643ea8Slogwang if (hc_entry->rmx_rtt == 0) 535*a9643ea8Slogwang hc_entry->rmx_rtt = hcml->rmx_rtt; 536*a9643ea8Slogwang else 537*a9643ea8Slogwang hc_entry->rmx_rtt = 538*a9643ea8Slogwang (hc_entry->rmx_rtt + hcml->rmx_rtt) / 2; 539*a9643ea8Slogwang TCPSTAT_INC(tcps_cachedrtt); 540*a9643ea8Slogwang } 541*a9643ea8Slogwang if (hcml->rmx_rttvar != 0) { 542*a9643ea8Slogwang if (hc_entry->rmx_rttvar == 0) 543*a9643ea8Slogwang hc_entry->rmx_rttvar = hcml->rmx_rttvar; 544*a9643ea8Slogwang else 545*a9643ea8Slogwang hc_entry->rmx_rttvar = 546*a9643ea8Slogwang (hc_entry->rmx_rttvar + hcml->rmx_rttvar) / 2; 547*a9643ea8Slogwang TCPSTAT_INC(tcps_cachedrttvar); 548*a9643ea8Slogwang } 549*a9643ea8Slogwang if (hcml->rmx_ssthresh != 0) { 550*a9643ea8Slogwang if (hc_entry->rmx_ssthresh == 0) 551*a9643ea8Slogwang hc_entry->rmx_ssthresh = hcml->rmx_ssthresh; 552*a9643ea8Slogwang else 553*a9643ea8Slogwang hc_entry->rmx_ssthresh = 554*a9643ea8Slogwang (hc_entry->rmx_ssthresh + hcml->rmx_ssthresh) / 2; 555*a9643ea8Slogwang TCPSTAT_INC(tcps_cachedssthresh); 556*a9643ea8Slogwang } 557*a9643ea8Slogwang if (hcml->rmx_cwnd != 0) { 558*a9643ea8Slogwang if (hc_entry->rmx_cwnd == 0) 559*a9643ea8Slogwang hc_entry->rmx_cwnd = hcml->rmx_cwnd; 560*a9643ea8Slogwang else 561*a9643ea8Slogwang hc_entry->rmx_cwnd = 562*a9643ea8Slogwang (hc_entry->rmx_cwnd + hcml->rmx_cwnd) / 2; 563*a9643ea8Slogwang /* TCPSTAT_INC(tcps_cachedcwnd); */ 564*a9643ea8Slogwang } 565*a9643ea8Slogwang if (hcml->rmx_sendpipe != 0) { 566*a9643ea8Slogwang if (hc_entry->rmx_sendpipe == 0) 567*a9643ea8Slogwang hc_entry->rmx_sendpipe = hcml->rmx_sendpipe; 568*a9643ea8Slogwang else 569*a9643ea8Slogwang hc_entry->rmx_sendpipe = 570*a9643ea8Slogwang (hc_entry->rmx_sendpipe + hcml->rmx_sendpipe) /2; 571*a9643ea8Slogwang /* TCPSTAT_INC(tcps_cachedsendpipe); */ 572*a9643ea8Slogwang } 573*a9643ea8Slogwang if (hcml->rmx_recvpipe != 0) { 574*a9643ea8Slogwang if (hc_entry->rmx_recvpipe == 0) 575*a9643ea8Slogwang hc_entry->rmx_recvpipe = hcml->rmx_recvpipe; 576*a9643ea8Slogwang else 577*a9643ea8Slogwang hc_entry->rmx_recvpipe = 578*a9643ea8Slogwang (hc_entry->rmx_recvpipe + hcml->rmx_recvpipe) /2; 579*a9643ea8Slogwang /* TCPSTAT_INC(tcps_cachedrecvpipe); */ 580*a9643ea8Slogwang } 581*a9643ea8Slogwang 582*a9643ea8Slogwang TAILQ_REMOVE(&hc_entry->rmx_head->hch_bucket, hc_entry, rmx_q); 583*a9643ea8Slogwang TAILQ_INSERT_HEAD(&hc_entry->rmx_head->hch_bucket, hc_entry, rmx_q); 584*a9643ea8Slogwang THC_UNLOCK(&hc_entry->rmx_head->hch_mtx); 585*a9643ea8Slogwang } 586*a9643ea8Slogwang 587*a9643ea8Slogwang /* 588*a9643ea8Slogwang * Sysctl function: prints the list and values of all hostcache entries in 589*a9643ea8Slogwang * unsorted order. 590*a9643ea8Slogwang */ 591*a9643ea8Slogwang static int 592*a9643ea8Slogwang sysctl_tcp_hc_list(SYSCTL_HANDLER_ARGS) 593*a9643ea8Slogwang { 594*a9643ea8Slogwang const int linesize = 128; 595*a9643ea8Slogwang struct sbuf sb; 596*a9643ea8Slogwang int i, error; 597*a9643ea8Slogwang struct hc_metrics *hc_entry; 598*a9643ea8Slogwang #ifdef INET6 599*a9643ea8Slogwang char ip6buf[INET6_ADDRSTRLEN]; 600*a9643ea8Slogwang #endif 601*a9643ea8Slogwang 602*a9643ea8Slogwang sbuf_new(&sb, NULL, linesize * (V_tcp_hostcache.cache_count + 1), 603*a9643ea8Slogwang SBUF_INCLUDENUL); 604*a9643ea8Slogwang 605*a9643ea8Slogwang sbuf_printf(&sb, 606*a9643ea8Slogwang "\nIP address MTU SSTRESH RTT RTTVAR " 607*a9643ea8Slogwang " CWND SENDPIPE RECVPIPE HITS UPD EXP\n"); 608*a9643ea8Slogwang 609*a9643ea8Slogwang #define msec(u) (((u) + 500) / 1000) 610*a9643ea8Slogwang for (i = 0; i < V_tcp_hostcache.hashsize; i++) { 611*a9643ea8Slogwang THC_LOCK(&V_tcp_hostcache.hashbase[i].hch_mtx); 612*a9643ea8Slogwang TAILQ_FOREACH(hc_entry, &V_tcp_hostcache.hashbase[i].hch_bucket, 613*a9643ea8Slogwang rmx_q) { 614*a9643ea8Slogwang sbuf_printf(&sb, 615*a9643ea8Slogwang "%-15s %5lu %8lu %6lums %6lums %8lu %8lu %8lu %4lu " 616*a9643ea8Slogwang "%4lu %4i\n", 617*a9643ea8Slogwang hc_entry->ip4.s_addr ? inet_ntoa(hc_entry->ip4) : 618*a9643ea8Slogwang #ifdef INET6 619*a9643ea8Slogwang ip6_sprintf(ip6buf, &hc_entry->ip6), 620*a9643ea8Slogwang #else 621*a9643ea8Slogwang "IPv6?", 622*a9643ea8Slogwang #endif 623*a9643ea8Slogwang hc_entry->rmx_mtu, 624*a9643ea8Slogwang hc_entry->rmx_ssthresh, 625*a9643ea8Slogwang msec(hc_entry->rmx_rtt * 626*a9643ea8Slogwang (RTM_RTTUNIT / (hz * TCP_RTT_SCALE))), 627*a9643ea8Slogwang msec(hc_entry->rmx_rttvar * 628*a9643ea8Slogwang (RTM_RTTUNIT / (hz * TCP_RTTVAR_SCALE))), 629*a9643ea8Slogwang hc_entry->rmx_cwnd, 630*a9643ea8Slogwang hc_entry->rmx_sendpipe, 631*a9643ea8Slogwang hc_entry->rmx_recvpipe, 632*a9643ea8Slogwang hc_entry->rmx_hits, 633*a9643ea8Slogwang hc_entry->rmx_updates, 634*a9643ea8Slogwang hc_entry->rmx_expire); 635*a9643ea8Slogwang } 636*a9643ea8Slogwang THC_UNLOCK(&V_tcp_hostcache.hashbase[i].hch_mtx); 637*a9643ea8Slogwang } 638*a9643ea8Slogwang #undef msec 639*a9643ea8Slogwang error = sbuf_finish(&sb); 640*a9643ea8Slogwang if (error == 0) 641*a9643ea8Slogwang error = SYSCTL_OUT(req, sbuf_data(&sb), sbuf_len(&sb)); 642*a9643ea8Slogwang sbuf_delete(&sb); 643*a9643ea8Slogwang return(error); 644*a9643ea8Slogwang } 645*a9643ea8Slogwang 646*a9643ea8Slogwang /* 647*a9643ea8Slogwang * Caller has to make sure the curvnet is set properly. 648*a9643ea8Slogwang */ 649*a9643ea8Slogwang static void 650*a9643ea8Slogwang tcp_hc_purge_internal(int all) 651*a9643ea8Slogwang { 652*a9643ea8Slogwang struct hc_metrics *hc_entry, *hc_next; 653*a9643ea8Slogwang int i; 654*a9643ea8Slogwang 655*a9643ea8Slogwang for (i = 0; i < V_tcp_hostcache.hashsize; i++) { 656*a9643ea8Slogwang THC_LOCK(&V_tcp_hostcache.hashbase[i].hch_mtx); 657*a9643ea8Slogwang TAILQ_FOREACH_SAFE(hc_entry, 658*a9643ea8Slogwang &V_tcp_hostcache.hashbase[i].hch_bucket, rmx_q, hc_next) { 659*a9643ea8Slogwang if (all || hc_entry->rmx_expire <= 0) { 660*a9643ea8Slogwang TAILQ_REMOVE(&V_tcp_hostcache.hashbase[i].hch_bucket, 661*a9643ea8Slogwang hc_entry, rmx_q); 662*a9643ea8Slogwang uma_zfree(V_tcp_hostcache.zone, hc_entry); 663*a9643ea8Slogwang V_tcp_hostcache.hashbase[i].hch_length--; 664*a9643ea8Slogwang V_tcp_hostcache.cache_count--; 665*a9643ea8Slogwang } else 666*a9643ea8Slogwang hc_entry->rmx_expire -= V_tcp_hostcache.prune; 667*a9643ea8Slogwang } 668*a9643ea8Slogwang THC_UNLOCK(&V_tcp_hostcache.hashbase[i].hch_mtx); 669*a9643ea8Slogwang } 670*a9643ea8Slogwang } 671*a9643ea8Slogwang 672*a9643ea8Slogwang /* 673*a9643ea8Slogwang * Expire and purge (old|all) entries in the tcp_hostcache. Runs 674*a9643ea8Slogwang * periodically from the callout. 675*a9643ea8Slogwang */ 676*a9643ea8Slogwang static void 677*a9643ea8Slogwang tcp_hc_purge(void *arg) 678*a9643ea8Slogwang { 679*a9643ea8Slogwang CURVNET_SET((struct vnet *) arg); 680*a9643ea8Slogwang int all = 0; 681*a9643ea8Slogwang 682*a9643ea8Slogwang if (V_tcp_hostcache.purgeall) { 683*a9643ea8Slogwang all = 1; 684*a9643ea8Slogwang V_tcp_hostcache.purgeall = 0; 685*a9643ea8Slogwang } 686*a9643ea8Slogwang 687*a9643ea8Slogwang tcp_hc_purge_internal(all); 688*a9643ea8Slogwang 689*a9643ea8Slogwang callout_reset(&V_tcp_hc_callout, V_tcp_hostcache.prune * hz, 690*a9643ea8Slogwang tcp_hc_purge, arg); 691*a9643ea8Slogwang CURVNET_RESTORE(); 692*a9643ea8Slogwang } 693*a9643ea8Slogwang 694*a9643ea8Slogwang /* 695*a9643ea8Slogwang * Expire and purge all entries in hostcache immediately. 696*a9643ea8Slogwang */ 697*a9643ea8Slogwang static int 698*a9643ea8Slogwang sysctl_tcp_hc_purgenow(SYSCTL_HANDLER_ARGS) 699*a9643ea8Slogwang { 700*a9643ea8Slogwang int error, val; 701*a9643ea8Slogwang 702*a9643ea8Slogwang val = 0; 703*a9643ea8Slogwang error = sysctl_handle_int(oidp, &val, 0, req); 704*a9643ea8Slogwang if (error || !req->newptr) 705*a9643ea8Slogwang return (error); 706*a9643ea8Slogwang 707*a9643ea8Slogwang tcp_hc_purge_internal(1); 708*a9643ea8Slogwang 709*a9643ea8Slogwang callout_reset(&V_tcp_hc_callout, V_tcp_hostcache.prune * hz, 710*a9643ea8Slogwang tcp_hc_purge, curvnet); 711*a9643ea8Slogwang 712*a9643ea8Slogwang return (0); 713*a9643ea8Slogwang } 714