#include <sys/types.h>
#include <sys/strsun.h>
#include <sys/squeue_impl.h>
#include <sys/squeue.h>
#include <sys/callo.h>
#include <inet/common.h>
#include <inet/ip.h>
#include <inet/tcp.h>
#include <inet/tcp_impl.h>
#include <inet/tcp_cluster.h>
static void tcp_time_wait_purge(tcp_t *, tcp_squeue_priv_t *);
#define TW_BUCKET(t) \
(((t) / MSEC_TO_TICK(TCP_TIME_WAIT_DELAY)) % TCP_TIME_WAIT_BUCKETS)
#define TW_BUCKET_NEXT(b) (((b) + 1) % TCP_TIME_WAIT_BUCKETS)
boolean_t
tcp_time_wait_remove(tcp_t *tcp, tcp_squeue_priv_t *tsp)
{
boolean_t locked = B_FALSE;
if (tsp == NULL) {
tsp = *((tcp_squeue_priv_t **)
squeue_getprivate(tcp->tcp_connp->conn_sqp, SQPRIVATE_TCP));
mutex_enter(&tsp->tcp_time_wait_lock);
locked = B_TRUE;
} else {
ASSERT(MUTEX_HELD(&tsp->tcp_time_wait_lock));
}
if (tcp->tcp_time_wait_expire == 0) {
ASSERT(tcp->tcp_time_wait_next == NULL);
ASSERT(tcp->tcp_time_wait_prev == NULL);
if (locked)
mutex_exit(&tsp->tcp_time_wait_lock);
return (B_FALSE);
}
ASSERT(TCP_IS_DETACHED(tcp));
ASSERT(tcp->tcp_state == TCPS_TIME_WAIT);
ASSERT(tsp->tcp_time_wait_cnt > 0);
if (tcp->tcp_time_wait_next != NULL) {
tcp->tcp_time_wait_next->tcp_time_wait_prev =
tcp->tcp_time_wait_prev;
}
if (tcp->tcp_time_wait_prev != NULL) {
tcp->tcp_time_wait_prev->tcp_time_wait_next =
tcp->tcp_time_wait_next;
} else {
unsigned int bucket;
bucket = TW_BUCKET(tcp->tcp_time_wait_expire);
ASSERT(tsp->tcp_time_wait_bucket[bucket] == tcp);
tsp->tcp_time_wait_bucket[bucket] = tcp->tcp_time_wait_next;
}
tcp->tcp_time_wait_next = NULL;
tcp->tcp_time_wait_prev = NULL;
tcp->tcp_time_wait_expire = 0;
tsp->tcp_time_wait_cnt--;
if (locked)
mutex_exit(&tsp->tcp_time_wait_lock);
return (B_TRUE);
}
#if defined(_BIG_ENDIAN)
#define IPv4_LOCALHOST 0x7f000000U
#define IPv4_LH_MASK 0xffffff00U
#else
#define IPv4_LOCALHOST 0x0000007fU
#define IPv4_LH_MASK 0x00ffffffU
#endif
#define IS_LOCAL_HOST(x) ( \
((x)->tcp_connp->conn_ipversion == IPV4_VERSION && \
((x)->tcp_connp->conn_laddr_v4 & IPv4_LH_MASK) == IPv4_LOCALHOST) || \
((x)->tcp_connp->conn_ipversion == IPV6_VERSION && \
IN6_IS_ADDR_LOOPBACK(&(x)->tcp_connp->conn_laddr_v6)))
void
tcp_time_wait_append(tcp_t *tcp)
{
tcp_stack_t *tcps = tcp->tcp_tcps;
squeue_t *sqp = tcp->tcp_connp->conn_sqp;
tcp_squeue_priv_t *tsp =
*((tcp_squeue_priv_t **)squeue_getprivate(sqp, SQPRIVATE_TCP));
int64_t now, schedule;
unsigned int bucket;
tcp_timers_stop(tcp);
ASSERT(tcp->tcp_timer_tid == 0);
ASSERT(tcp->tcp_ack_tid == 0);
ASSERT(TCP_IS_DETACHED(tcp));
ASSERT(tcp->tcp_state == TCPS_TIME_WAIT);
ASSERT(tcp->tcp_ptpahn == NULL);
ASSERT(tcp->tcp_flow_stopped == 0);
ASSERT(tcp->tcp_time_wait_next == NULL);
ASSERT(tcp->tcp_time_wait_prev == NULL);
ASSERT(tcp->tcp_time_wait_expire == 0);
ASSERT(tcp->tcp_listener == NULL);
TCP_DBGSTAT(tcps, tcp_time_wait);
mutex_enter(&tsp->tcp_time_wait_lock);
if (tcp->tcp_loopback) {
tcp_time_wait_purge(tcp, tsp);
mutex_exit(&tsp->tcp_time_wait_lock);
return;
}
now = ddi_get_lbolt64();
if (tsp->tcp_time_wait_tid == 0) {
ASSERT(tsp->tcp_time_wait_cnt == 0);
tsp->tcp_time_wait_offset =
now % MSEC_TO_TICK(TCP_TIME_WAIT_DELAY);
}
now -= tsp->tcp_time_wait_offset;
schedule = now + MSEC_TO_TICK(tcps->tcps_time_wait_interval);
tcp->tcp_time_wait_expire = schedule;
bucket = TW_BUCKET(tcp->tcp_time_wait_expire);
tcp->tcp_time_wait_next = tsp->tcp_time_wait_bucket[bucket];
tsp->tcp_time_wait_bucket[bucket] = tcp;
if (tcp->tcp_time_wait_next != NULL) {
ASSERT(tcp->tcp_time_wait_next->tcp_time_wait_prev == NULL);
tcp->tcp_time_wait_next->tcp_time_wait_prev = tcp;
}
tsp->tcp_time_wait_cnt++;
schedule += MSEC_TO_TICK(TCP_TIME_WAIT_DELAY);
schedule -= schedule % MSEC_TO_TICK(TCP_TIME_WAIT_DELAY);
if (schedule < tsp->tcp_time_wait_schedule) {
callout_id_t old_tid = tsp->tcp_time_wait_tid;
tsp->tcp_time_wait_schedule = schedule;
tsp->tcp_time_wait_tid =
timeout_generic(CALLOUT_NORMAL,
tcp_time_wait_collector, sqp,
TICK_TO_NSEC(schedule - now),
CALLOUT_TCP_RESOLUTION, CALLOUT_FLAG_ROUNDUP);
mutex_exit(&tsp->tcp_time_wait_lock);
(void) untimeout_default(old_tid, 0);
return;
}
if (tsp->tcp_time_wait_schedule == 0) {
ASSERT(tsp->tcp_time_wait_tid == 0);
tsp->tcp_time_wait_schedule = schedule;
tsp->tcp_time_wait_tid =
timeout_generic(CALLOUT_NORMAL,
tcp_time_wait_collector, sqp,
TICK_TO_NSEC(schedule - now),
CALLOUT_TCP_RESOLUTION, CALLOUT_FLAG_ROUNDUP);
}
mutex_exit(&tsp->tcp_time_wait_lock);
}
static void
tcp_timewait_close(void *arg, mblk_t *mp, void *arg2, ip_recv_attr_t *dummy)
{
conn_t *connp = (conn_t *)arg;
tcp_t *tcp = connp->conn_tcp;
ASSERT(tcp != NULL);
if (tcp->tcp_state == TCPS_CLOSED) {
return;
}
ASSERT((connp->conn_family == AF_INET &&
connp->conn_ipversion == IPV4_VERSION) ||
(connp->conn_family == AF_INET6 &&
(connp->conn_ipversion == IPV4_VERSION ||
connp->conn_ipversion == IPV6_VERSION)));
ASSERT(!tcp->tcp_listener);
ASSERT(TCP_IS_DETACHED(tcp));
tcp_close_detached(tcp);
}
static void
tcp_time_wait_purge(tcp_t *tcp, tcp_squeue_priv_t *tsp)
{
mblk_t *mp;
conn_t *connp = tcp->tcp_connp;
kmutex_t *lock;
ASSERT(MUTEX_HELD(&tsp->tcp_time_wait_lock));
ASSERT(connp->conn_fanout != NULL);
lock = &connp->conn_fanout->connf_lock;
if (mutex_tryenter(lock)) {
mutex_enter(&connp->conn_lock);
if (connp->conn_ref == 2 && cl_inet_disconnect == NULL) {
ipcl_hash_remove_locked(connp, connp->conn_fanout);
connp->conn_state_flags |= CONN_CONDEMNED;
mutex_exit(&connp->conn_lock);
mutex_exit(lock);
if (tsp->tcp_free_list_cnt < tcp_free_list_max_cnt) {
tcp_cleanup(tcp);
ASSERT(connp->conn_latch == NULL);
ASSERT(connp->conn_policy == NULL);
ASSERT(tcp->tcp_tcps == NULL);
ASSERT(connp->conn_netstack == NULL);
tcp->tcp_time_wait_next = tsp->tcp_free_list;
tcp->tcp_in_free_list = B_TRUE;
tsp->tcp_free_list = tcp;
tsp->tcp_free_list_cnt++;
} else {
tcp_bind_hash_remove(tcp);
ixa_cleanup(tcp->tcp_connp->conn_ixa);
tcp_ipsec_cleanup(tcp);
CONN_DEC_REF(tcp->tcp_connp);
}
return;
} else {
CONN_INC_REF_LOCKED(connp);
mutex_exit(&connp->conn_lock);
mutex_exit(lock);
}
} else {
CONN_INC_REF(connp);
}
if (tcp->tcp_closemp.b_prev == NULL) {
tcp->tcp_closemp_used = B_TRUE;
} else {
cmn_err(CE_PANIC,
"tcp_timewait_collector: concurrent use of tcp_closemp: "
"connp %p tcp %p\n", (void *)connp, (void *)tcp);
}
TCP_DEBUG_GETPCSTACK(tcp->tcmp_stk, 15);
mp = &tcp->tcp_closemp;
mutex_exit(&tsp->tcp_time_wait_lock);
SQUEUE_ENTER_ONE(connp->conn_sqp, mp, tcp_timewait_close, connp, NULL,
SQ_FILL, SQTAG_TCP_TIMEWAIT);
mutex_enter(&tsp->tcp_time_wait_lock);
}
void
tcp_time_wait_collector(void *arg)
{
tcp_t *tcp;
int64_t now, sched_active, sched_cur, sched_new;
unsigned int idx;
squeue_t *sqp = (squeue_t *)arg;
tcp_squeue_priv_t *tsp =
*((tcp_squeue_priv_t **)squeue_getprivate(sqp, SQPRIVATE_TCP));
mutex_enter(&tsp->tcp_time_wait_lock);
if (tsp->tcp_time_wait_collector_active) {
mutex_exit(&tsp->tcp_time_wait_lock);
return;
}
tsp->tcp_time_wait_collector_active = B_TRUE;
sched_cur = sched_active = tsp->tcp_time_wait_schedule;
if (tsp->tcp_free_list != NULL) {
TCP_G_STAT(tcp_freelist_cleanup);
while ((tcp = tsp->tcp_free_list) != NULL) {
tsp->tcp_free_list = tcp->tcp_time_wait_next;
tcp->tcp_time_wait_next = NULL;
tsp->tcp_free_list_cnt--;
ASSERT(tcp->tcp_tcps == NULL);
CONN_DEC_REF(tcp->tcp_connp);
}
ASSERT(tsp->tcp_free_list_cnt == 0);
}
if (tsp->tcp_time_wait_cnt == 0) {
tsp->tcp_time_wait_offset = 0;
tsp->tcp_time_wait_schedule = 0;
tsp->tcp_time_wait_tid = 0;
tsp->tcp_time_wait_collector_active = B_FALSE;
mutex_exit(&tsp->tcp_time_wait_lock);
return;
}
retry:
idx = TW_BUCKET(sched_cur - 1);
now = ddi_get_lbolt64() - tsp->tcp_time_wait_offset;
tcp = tsp->tcp_time_wait_bucket[idx];
while (tcp != NULL) {
if (now < tcp->tcp_time_wait_expire) {
break;
}
VERIFY(tcp_time_wait_remove(tcp, tsp));
tcp_time_wait_purge(tcp, tsp);
tcp = tsp->tcp_time_wait_bucket[idx];
}
if (tsp->tcp_time_wait_cnt == 0) {
if (tsp->tcp_time_wait_schedule == sched_active) {
tsp->tcp_time_wait_offset = 0;
tsp->tcp_time_wait_schedule = 0;
tsp->tcp_time_wait_tid = 0;
}
tsp->tcp_time_wait_collector_active = B_FALSE;
mutex_exit(&tsp->tcp_time_wait_lock);
return;
} else {
unsigned int nidx;
sched_new = sched_cur + MSEC_TO_TICK(TCP_TIME_WAIT_DELAY);
nidx = TW_BUCKET_NEXT(idx);
while (tsp->tcp_time_wait_bucket[nidx] == NULL) {
if (nidx == idx) {
break;
}
nidx = TW_BUCKET_NEXT(nidx);
sched_new += MSEC_TO_TICK(TCP_TIME_WAIT_DELAY);
}
ASSERT(tsp->tcp_time_wait_bucket[nidx] != NULL);
}
now = ddi_get_lbolt64() - tsp->tcp_time_wait_offset;
if (sched_new <= now) {
sched_cur = sched_new;
DTRACE_PROBE3(tcp__time__wait__overrun,
tcp_squeue_priv_t *, tsp, int64_t, sched_new, int64_t, now);
goto retry;
}
if (tsp->tcp_time_wait_schedule != sched_active) {
tsp->tcp_time_wait_collector_active = B_FALSE;
mutex_exit(&tsp->tcp_time_wait_lock);
return;
}
tsp->tcp_time_wait_schedule = sched_new;
tsp->tcp_time_wait_tid =
timeout_generic(CALLOUT_NORMAL,
tcp_time_wait_collector, sqp,
TICK_TO_NSEC(sched_new - now),
CALLOUT_TCP_RESOLUTION, CALLOUT_FLAG_ROUNDUP);
tsp->tcp_time_wait_collector_active = B_FALSE;
mutex_exit(&tsp->tcp_time_wait_lock);
}
void
tcp_time_wait_processing(tcp_t *tcp, mblk_t *mp, uint32_t seg_seq,
uint32_t seg_ack, int seg_len, tcpha_t *tcpha, ip_recv_attr_t *ira)
{
int32_t bytes_acked;
int32_t gap;
int32_t rgap;
tcp_opt_t tcpopt;
uint_t flags;
uint32_t new_swnd = 0;
conn_t *nconnp;
conn_t *connp = tcp->tcp_connp;
tcp_stack_t *tcps = tcp->tcp_tcps;
TCPS_BUMP_MIB(tcps, tcpHCInSegs);
DTRACE_PROBE2(tcp__trace__recv, mblk_t *, mp, tcp_t *, tcp);
flags = (unsigned int)tcpha->tha_flags & 0xFF;
new_swnd = ntohs(tcpha->tha_win) <<
((tcpha->tha_flags & TH_SYN) ? 0 : tcp->tcp_snd_ws);
boolean_t keepalive = (seg_len == 0 || seg_len == 1) &&
(seg_seq + 1 == tcp->tcp_rnxt);
if (tcp->tcp_snd_ts_ok && !(flags & TH_RST) && !keepalive) {
int options;
if (tcp->tcp_snd_sack_ok)
tcpopt.tcp = tcp;
else
tcpopt.tcp = NULL;
options = tcp_parse_options(tcpha, &tcpopt);
if (!(options & TCP_OPT_TSTAMP_PRESENT)) {
DTRACE_TCP1(droppedtimestamp, tcp_t *, tcp);
goto done;
} else if (!tcp_paws_check(tcp, &tcpopt)) {
tcp_xmit_ctl(NULL, tcp, tcp->tcp_snxt, tcp->tcp_rnxt,
TH_ACK);
goto done;
}
}
gap = seg_seq - tcp->tcp_rnxt;
rgap = tcp->tcp_rwnd - (gap + seg_len);
if (gap < 0) {
TCPS_BUMP_MIB(tcps, tcpInDataDupSegs);
TCPS_UPDATE_MIB(tcps, tcpInDataDupBytes,
(seg_len > -gap ? -gap : seg_len));
seg_len += gap;
if (seg_len < 0 || (seg_len == 0 && !(flags & TH_FIN))) {
if (flags & TH_RST) {
goto done;
}
if ((flags & TH_FIN) && seg_len == -1) {
if (TCP_IS_DETACHED(tcp)) {
if (tcp_time_wait_remove(tcp, NULL) ==
B_TRUE) {
tcp_time_wait_append(tcp);
TCP_DBGSTAT(tcps,
tcp_rput_time_wait);
}
} else {
ASSERT(tcp != NULL);
TCP_TIMER_RESTART(tcp,
tcps->tcps_time_wait_interval);
}
tcp_xmit_ctl(NULL, tcp, tcp->tcp_snxt,
tcp->tcp_rnxt, TH_ACK);
goto done;
}
flags |= TH_ACK_NEEDED;
seg_len = 0;
goto process_ack;
}
seg_seq = tcp->tcp_rnxt;
}
if ((flags & TH_SYN) && gap > 0 && rgap < 0) {
uint32_t new_iss = tcps->tcps_iss_incr_extra;
int32_t adj;
ip_stack_t *ipst = tcps->tcps_netstack->netstack_ip;
switch (tcps->tcps_strong_iss) {
case 2: {
uint32_t answer[4];
struct {
uint32_t ports;
in6_addr_t src;
in6_addr_t dst;
} arg;
MD5_CTX context;
mutex_enter(&tcps->tcps_iss_key_lock);
context = tcps->tcps_iss_key;
mutex_exit(&tcps->tcps_iss_key_lock);
arg.ports = connp->conn_ports;
arg.src = connp->conn_laddr_v6;
arg.dst = connp->conn_faddr_v6;
MD5Update(&context, (uchar_t *)&arg,
sizeof (arg));
MD5Final((uchar_t *)answer, &context);
answer[0] ^= answer[1] ^ answer[2] ^ answer[3];
new_iss += (gethrtime() >> ISS_NSEC_SHT) + answer[0];
break;
}
case 1:
new_iss += (gethrtime() >> ISS_NSEC_SHT) + 1;
break;
default:
new_iss += (uint32_t)gethrestime_sec() *
tcps->tcps_iss_incr;
break;
}
if ((adj = (int32_t)(tcp->tcp_snxt - new_iss)) > 0) {
tcps->tcps_iss_incr_extra += adj;
}
if (tcp_clean_death(tcp, 0) == -1)
goto done;
nconnp = ipcl_classify(mp, ira, ipst);
if (nconnp != NULL) {
TCP_STAT(tcps, tcp_time_wait_syn_success);
tcp_reinput(nconnp, mp, ira, ipst);
return;
}
goto done;
}
if (rgap < 0) {
TCPS_BUMP_MIB(tcps, tcpInDataPastWinSegs);
TCPS_UPDATE_MIB(tcps, tcpInDataPastWinBytes, -rgap);
seg_len += rgap;
if (seg_len <= 0) {
if (flags & TH_RST) {
goto done;
}
flags |= TH_ACK_NEEDED;
seg_len = 0;
goto process_ack;
}
}
if (tcp->tcp_snd_ts_ok && !(flags & TH_RST) &&
TSTMP_GEQ(tcpopt.tcp_opt_ts_val, tcp->tcp_ts_recent) &&
SEQ_LEQ(seg_seq, tcp->tcp_rack)) {
tcp->tcp_ts_recent = tcpopt.tcp_opt_ts_val;
tcp->tcp_last_rcv_lbolt = ddi_get_lbolt64();
}
if (seg_seq != tcp->tcp_rnxt && seg_len > 0) {
flags |= TH_ACK_NEEDED;
seg_len = 0;
} else if (seg_len > 0) {
TCPS_BUMP_MIB(tcps, tcpInClosed);
TCPS_BUMP_MIB(tcps, tcpInDataInorderSegs);
TCPS_UPDATE_MIB(tcps, tcpInDataInorderBytes, seg_len);
tcp->tcp_cs.tcp_in_data_inorder_segs++;
tcp->tcp_cs.tcp_in_data_inorder_bytes += seg_len;
}
if (flags & TH_RST) {
(void) tcp_clean_death(tcp, 0);
goto done;
}
if (flags & TH_SYN) {
tcp_xmit_ctl("TH_SYN", tcp, seg_ack, seg_seq + 1,
TH_RST|TH_ACK);
goto done;
}
process_ack:
if (flags & TH_ACK) {
bytes_acked = (int)(seg_ack - tcp->tcp_suna);
if (bytes_acked <= 0) {
if (bytes_acked == 0 && seg_len == 0 &&
new_swnd == tcp->tcp_swnd)
TCPS_BUMP_MIB(tcps, tcpInDupAck);
} else {
flags |= TH_ACK_NEEDED;
}
}
if (flags & TH_ACK_NEEDED) {
tcp_xmit_ctl(NULL, tcp, tcp->tcp_snxt,
tcp->tcp_rnxt, TH_ACK);
}
done:
freemsg(mp);
}