#include <sys/types.h>
#include <sys/stream.h>
#include <sys/strsun.h>
#include <sys/strsubr.h>
#include <sys/stropts.h>
#include <sys/strlog.h>
#define _SUN_TPI_VERSION 2
#include <sys/tihdr.h>
#include <sys/suntpi.h>
#include <sys/xti_inet.h>
#include <sys/timod.h>
#include <sys/pattr.h>
#include <sys/squeue_impl.h>
#include <sys/squeue.h>
#include <sys/sockio.h>
#include <sys/tsol/tnet.h>
#include <inet/common.h>
#include <inet/ip.h>
#include <inet/tcp.h>
#include <inet/tcp_impl.h>
#include <inet/snmpcom.h>
#include <inet/proto_set.h>
#include <inet/ipsec_impl.h>
#include <inet/ip_ndp.h>
#include <inet/tcp_sig.h>
static mblk_t *tcp_get_seg_mp(tcp_t *, uint32_t, int32_t *);
static void tcp_wput_cmdblk(queue_t *, mblk_t *);
static void tcp_wput_flush(tcp_t *, mblk_t *);
static void tcp_wput_iocdata(tcp_t *tcp, mblk_t *mp);
static int tcp_xmit_end(tcp_t *);
static int tcp_send(tcp_t *, const int, const int, const int,
const int, int *, uint32_t *, int *, mblk_t **, mblk_t *);
static void tcp_xmit_early_reset(char *, mblk_t *, uint32_t, uint32_t,
int, ip_recv_attr_t *, ip_stack_t *, conn_t *);
static boolean_t tcp_send_rst_chk(tcp_stack_t *);
static void tcp_process_shrunk_swnd(tcp_t *, uint32_t);
static void tcp_fill_header(tcp_t *, uchar_t *, int);
static void tcp_wput_nondata(void *, mblk_t *, void *, ip_recv_attr_t *);
static void tcp_wput_ioctl(void *, mblk_t *, void *, ip_recv_attr_t *);
static void tcp_wput_proto(void *, mblk_t *, void *, ip_recv_attr_t *);
static int tcp_tx_pull_len = 16;
static void
cc_after_idle(tcp_t *tcp)
{
uint32_t old_cwnd = tcp->tcp_cwnd;
if (CC_ALGO(tcp)->after_idle != NULL)
CC_ALGO(tcp)->after_idle(&tcp->tcp_ccv);
DTRACE_PROBE3(cwnd__cc__after__idle, tcp_t *, tcp, uint32_t, old_cwnd,
uint32_t, tcp->tcp_cwnd);
}
int
tcp_wput(queue_t *q, mblk_t *mp)
{
conn_t *connp = Q_TO_CONN(q);
tcp_t *tcp;
void (*output_proc)();
t_scalar_t type;
uchar_t *rptr;
struct iocblk *iocp;
size_t size;
ASSERT(connp->conn_ref >= 2);
switch (DB_TYPE(mp)) {
case M_DATA:
tcp = connp->conn_tcp;
ASSERT(tcp != NULL);
size = msgdsize(mp);
mutex_enter(&tcp->tcp_non_sq_lock);
tcp->tcp_squeue_bytes += size;
if (TCP_UNSENT_BYTES(tcp) > connp->conn_sndbuf) {
tcp_setqfull(tcp);
}
mutex_exit(&tcp->tcp_non_sq_lock);
CONN_INC_REF(connp);
SQUEUE_ENTER_ONE(connp->conn_sqp, mp, tcp_output, connp,
NULL, tcp_squeue_flag, SQTAG_TCP_OUTPUT);
return (0);
case M_CMD:
tcp_wput_cmdblk(q, mp);
return (0);
case M_PROTO:
case M_PCPROTO:
tcp = connp->conn_tcp;
rptr = mp->b_rptr;
if ((mp->b_wptr - rptr) >= sizeof (t_scalar_t)) {
type = ((union T_primitives *)rptr)->type;
} else {
if (connp->conn_debug) {
(void) strlog(TCP_MOD_ID, 0, 1,
SL_ERROR|SL_TRACE,
"tcp_wput_proto, dropping one...");
}
freemsg(mp);
return (0);
}
if (type == T_SVR4_OPTMGMT_REQ) {
cred_t *cr = msg_getcred(mp, NULL);
ASSERT(cr != NULL);
if (cr == NULL) {
tcp_err_ack(tcp, mp, TSYSERR, EINVAL);
return (0);
}
if (snmpcom_req(q, mp, tcp_snmp_set, ip_snmp_get,
cr)) {
return (0);
} else {
output_proc = tcp_wput_proto;
}
} else {
output_proc = tcp_wput_proto;
}
break;
case M_IOCTL:
iocp = (struct iocblk *)mp->b_rptr;
tcp = connp->conn_tcp;
switch (iocp->ioc_cmd) {
case TCP_IOC_ABORT_CONN:
tcp_ioctl_abort_conn(q, mp);
return (0);
case TI_GETPEERNAME:
case TI_GETMYNAME:
mi_copyin(q, mp, NULL,
SIZEOF_STRUCT(strbuf, iocp->ioc_flag));
return (0);
default:
output_proc = tcp_wput_ioctl;
break;
}
break;
default:
output_proc = tcp_wput_nondata;
break;
}
CONN_INC_REF(connp);
SQUEUE_ENTER_ONE(connp->conn_sqp, mp, output_proc, connp,
NULL, tcp_squeue_flag, SQTAG_TCP_WPUT_OTHER);
return (0);
}
void
tcp_wput_data(tcp_t *tcp, mblk_t *mp, boolean_t urgent)
{
int len;
mblk_t *local_time;
mblk_t *mp1;
uint32_t snxt;
int tail_unsent;
int tcpstate;
int usable = 0;
mblk_t *xmit_tail;
int32_t mss;
int32_t num_sack_blk = 0;
int32_t total_hdr_len;
int32_t tcp_hdr_len;
int rc;
conn_t *connp = tcp->tcp_connp;
clock_t now = LBOLT_FASTPATH;
tcpstate = tcp->tcp_state;
if (mp == NULL) {
ASSERT(tcp->tcp_unsent > 0);
len = tcp->tcp_unsent;
goto data_null;
}
ASSERT(mp->b_datap->db_type == M_DATA);
if (tcpstate < TCPS_SYN_SENT || tcpstate > TCPS_CLOSE_WAIT ||
(tcp->tcp_valid_bits & TCP_FSS_VALID) != 0) {
if ((tcp->tcp_valid_bits & TCP_FSS_VALID) != 0) {
#ifdef DEBUG
cmn_err(CE_WARN,
"tcp_wput_data: data after ordrel, %s",
tcp_display(tcp, NULL,
DISP_ADDR_AND_PORT));
#else
if (connp->conn_debug) {
(void) strlog(TCP_MOD_ID, 0, 1,
SL_TRACE|SL_ERROR,
"tcp_wput_data: data after ordrel, %s\n",
tcp_display(tcp, NULL,
DISP_ADDR_AND_PORT));
}
#endif
}
if (tcp->tcp_snd_zcopy_aware &&
(mp->b_datap->db_struioflag & STRUIO_ZCNOTIFY))
tcp_zcopy_notify(tcp);
freemsg(mp);
mutex_enter(&tcp->tcp_non_sq_lock);
if (tcp->tcp_flow_stopped &&
TCP_UNSENT_BYTES(tcp) <= connp->conn_sndlowat) {
tcp_clrqfull(tcp);
}
mutex_exit(&tcp->tcp_non_sq_lock);
return;
}
for (;;) {
ASSERT((uintptr_t)(mp->b_wptr - mp->b_rptr) <=
(uintptr_t)INT_MAX);
len = (int)(mp->b_wptr - mp->b_rptr);
if (len > 0)
break;
mp1 = mp;
mp = mp->b_cont;
freeb(mp1);
if (mp == NULL) {
return;
}
}
if (tcp->tcp_xmit_head == NULL) {
tcp->tcp_xmit_head = mp;
tcp->tcp_xmit_tail = mp;
tcp->tcp_xmit_tail_unsent = len;
} else {
struct datab *dp;
mp1 = tcp->tcp_xmit_last;
if (len < tcp_tx_pull_len &&
(dp = mp1->b_datap)->db_ref == 1 &&
dp->db_lim - mp1->b_wptr >= len) {
ASSERT(len > 0);
ASSERT(!mp1->b_cont);
if (len == 1) {
*mp1->b_wptr++ = *mp->b_rptr;
} else {
bcopy(mp->b_rptr, mp1->b_wptr, len);
mp1->b_wptr += len;
}
if (mp1 == tcp->tcp_xmit_tail)
tcp->tcp_xmit_tail_unsent += len;
mp1->b_cont = mp->b_cont;
if (tcp->tcp_snd_zcopy_aware &&
(mp->b_datap->db_struioflag & STRUIO_ZCNOTIFY))
mp1->b_datap->db_struioflag |= STRUIO_ZCNOTIFY;
freeb(mp);
mp = mp1;
} else {
tcp->tcp_xmit_last->b_cont = mp;
}
len += tcp->tcp_unsent;
}
if ((mp1 = mp->b_cont) != NULL) {
do {
int tlen;
ASSERT((uintptr_t)(mp1->b_wptr - mp1->b_rptr) <=
(uintptr_t)INT_MAX);
tlen = (int)(mp1->b_wptr - mp1->b_rptr);
if (tlen <= 0) {
mp->b_cont = mp1->b_cont;
freeb(mp1);
} else {
len += tlen;
mp = mp1;
}
} while ((mp1 = mp->b_cont) != NULL);
}
tcp->tcp_xmit_last = mp;
tcp->tcp_unsent = len;
if (urgent)
usable = 1;
data_null:
snxt = tcp->tcp_snxt;
xmit_tail = tcp->tcp_xmit_tail;
tail_unsent = tcp->tcp_xmit_tail_unsent;
if (tcp->tcp_snd_sack_ok && tcp->tcp_num_sack_blk > 0) {
int32_t opt_len;
num_sack_blk = MIN(tcp->tcp_max_sack_blk,
tcp->tcp_num_sack_blk);
opt_len = num_sack_blk * sizeof (sack_blk_t) + TCPOPT_NOP_LEN *
2 + TCPOPT_HEADER_LEN;
mss = tcp->tcp_mss - opt_len;
total_hdr_len = connp->conn_ht_iphc_len + opt_len;
tcp_hdr_len = connp->conn_ht_ulp_len + opt_len;
} else {
mss = tcp->tcp_mss;
total_hdr_len = connp->conn_ht_iphc_len;
tcp_hdr_len = connp->conn_ht_ulp_len;
}
if ((tcp->tcp_suna == snxt) && !tcp->tcp_localnet &&
(TICK_TO_MSEC(now - tcp->tcp_last_recv_time) >= tcp->tcp_rto)) {
cc_after_idle(tcp);
}
if (tcpstate == TCPS_SYN_RCVD) {
goto done;
} else {
int usable_r;
if (tcp->tcp_cwnd == 0) {
ASSERT(tcp->tcp_ecn_ok ||
tcp->tcp_state < TCPS_ESTABLISHED);
return;
}
usable_r = snxt - tcp->tcp_suna;
usable_r = tcp->tcp_swnd - usable_r;
ASSERT(tcp->tcp_fin_sent == 0);
if (usable_r < 0) {
if ((-usable_r >> tcp->tcp_snd_ws) > 0) {
tcp_process_shrunk_swnd(tcp, -usable_r);
}
return;
}
if (tcp->tcp_swnd > tcp->tcp_cwnd)
usable_r -= tcp->tcp_swnd - tcp->tcp_cwnd;
if (usable_r > len)
usable_r = len;
if (usable_r > 0) {
usable = usable_r;
} else {
goto done;
}
}
local_time = (mblk_t *)(intptr_t)gethrtime();
if (usable < (int)tcp->tcp_naglim &&
tcp->tcp_naglim > tcp->tcp_last_sent_len &&
snxt != tcp->tcp_suna &&
!(tcp->tcp_valid_bits & TCP_URG_VALID) &&
!(tcp->tcp_valid_bits & TCP_FSS_VALID)) {
goto done;
}
if (tcp->tcp_cork && !tcp->tcp_zero_win_probe) {
if (usable < mss)
goto done;
usable = (usable / mss) * mss;
}
tcp->tcp_tcpha->tha_win = htons(tcp->tcp_rwnd >> tcp->tcp_rcv_ws);
rc = tcp_send(tcp, mss, total_hdr_len, tcp_hdr_len,
num_sack_blk, &usable, &snxt, &tail_unsent, &xmit_tail,
local_time);
if (rc < 0 && tail_unsent < 0) {
do {
xmit_tail = xmit_tail->b_cont;
xmit_tail->b_prev = local_time;
ASSERT((uintptr_t)(xmit_tail->b_wptr -
xmit_tail->b_rptr) <= (uintptr_t)INT_MAX);
tail_unsent += (int)(xmit_tail->b_wptr -
xmit_tail->b_rptr);
} while (tail_unsent < 0);
}
done:;
tcp->tcp_xmit_tail = xmit_tail;
tcp->tcp_xmit_tail_unsent = tail_unsent;
len = tcp->tcp_snxt - snxt;
if (len) {
if (tcp->tcp_snd_sack_ok && tcp->tcp_notsack_list != NULL) {
tcp->tcp_pipe -= len;
tcp_notsack_update(&(tcp->tcp_notsack_list),
tcp->tcp_snxt, snxt,
&(tcp->tcp_num_notsack_blk),
&(tcp->tcp_cnt_notsack_list));
}
tcp->tcp_snxt = snxt + tcp->tcp_fin_sent;
tcp->tcp_rack = tcp->tcp_rnxt;
tcp->tcp_rack_cnt = 0;
if ((snxt + len) == tcp->tcp_suna) {
TCP_TIMER_RESTART(tcp, tcp->tcp_rto);
}
} else if (snxt == tcp->tcp_suna && tcp->tcp_swnd == 0) {
TCP_TIMER_RESTART(tcp, tcp->tcp_rto);
}
tcp->tcp_unsent += len;
mutex_enter(&tcp->tcp_non_sq_lock);
if (tcp->tcp_flow_stopped) {
if (TCP_UNSENT_BYTES(tcp) <= connp->conn_sndlowat) {
tcp_clrqfull(tcp);
}
} else if (TCP_UNSENT_BYTES(tcp) >= connp->conn_sndbuf) {
if (!(tcp->tcp_detached))
tcp_setqfull(tcp);
}
mutex_exit(&tcp->tcp_non_sq_lock);
}
int
tcp_wput_sock(queue_t *wq, mblk_t *mp)
{
conn_t *connp = Q_TO_CONN(wq);
tcp_t *tcp = connp->conn_tcp;
struct T_capability_req *car = (struct T_capability_req *)mp->b_rptr;
ASSERT(wq->q_qinfo == &tcp_sock_winit);
wq->q_qinfo = &tcp_winit;
ASSERT(IPCL_IS_TCP(connp));
ASSERT(TCP_IS_SOCKET(tcp));
if (DB_TYPE(mp) == M_PCPROTO &&
MBLKL(mp) == sizeof (struct T_capability_req) &&
car->PRIM_type == T_CAPABILITY_REQ) {
tcp_capability_req(tcp, mp);
return (0);
}
tcp_wput(wq, mp);
return (0);
}
int
tcp_wput_fallback(queue_t *wq, mblk_t *mp)
{
#ifdef DEBUG
cmn_err(CE_CONT, "tcp_wput_fallback: Message during fallback \n");
#endif
freemsg(mp);
return (0);
}
static void
tcp_wput_nondata(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(DB_TYPE(mp) != M_IOCTL);
if (TCP_IS_DETACHED(tcp) || (tcp->tcp_state == TCPS_CLOSED)) {
freemsg(mp);
return;
}
switch (DB_TYPE(mp)) {
case M_IOCDATA:
tcp_wput_iocdata(tcp, mp);
break;
case M_FLUSH:
tcp_wput_flush(tcp, mp);
break;
default:
ip_wput_nondata(connp->conn_wq, mp);
break;
}
}
static void
tcp_wput_flush(tcp_t *tcp, mblk_t *mp)
{
uchar_t fval = *mp->b_rptr;
mblk_t *tail;
conn_t *connp = tcp->tcp_connp;
queue_t *q = connp->conn_wq;
if ((fval & FLUSHW) && tcp->tcp_xmit_head != NULL &&
!(tcp->tcp_valid_bits & TCP_URG_VALID)) {
tail = tcp->tcp_xmit_tail;
tail->b_wptr -= tcp->tcp_xmit_tail_unsent;
tcp->tcp_xmit_tail_unsent = 0;
tcp->tcp_unsent = 0;
if (tail->b_wptr != tail->b_rptr)
tail = tail->b_cont;
if (tail) {
mblk_t **excess = &tcp->tcp_xmit_head;
for (;;) {
mblk_t *mp1 = *excess;
if (mp1 == tail)
break;
tcp->tcp_xmit_tail = mp1;
tcp->tcp_xmit_last = mp1;
excess = &mp1->b_cont;
}
*excess = NULL;
tcp_close_mpp(&tail);
if (tcp->tcp_snd_zcopy_aware)
tcp_zcopy_notify(tcp);
}
mutex_enter(&tcp->tcp_non_sq_lock);
if (tcp->tcp_flow_stopped) {
tcp_clrqfull(tcp);
}
mutex_exit(&tcp->tcp_non_sq_lock);
}
if (fval & FLUSHR) {
*mp->b_rptr = fval & ~FLUSHW;
qreply(q, mp);
return;
}
freemsg(mp);
}
static void
tcp_wput_iocdata(tcp_t *tcp, mblk_t *mp)
{
mblk_t *mp1;
struct iocblk *iocp = (struct iocblk *)mp->b_rptr;
STRUCT_HANDLE(strbuf, sb);
uint_t addrlen;
conn_t *connp = tcp->tcp_connp;
queue_t *q = connp->conn_wq;
switch (iocp->ioc_cmd) {
case TI_GETMYNAME:
case TI_GETPEERNAME:
break;
default:
mutex_enter(&connp->conn_lock);
if (connp->conn_state_flags & CONN_CLOSING) {
mutex_exit(&connp->conn_lock);
iocp->ioc_error = EINVAL;
mp->b_datap->db_type = M_IOCNAK;
iocp->ioc_count = 0;
qreply(q, mp);
return;
}
CONN_INC_IOCTLREF_LOCKED(connp);
ip_wput_nondata(q, mp);
CONN_DEC_IOCTLREF(connp);
return;
}
switch (mi_copy_state(q, mp, &mp1)) {
case -1:
return;
case MI_COPY_CASE(MI_COPY_IN, 1):
break;
case MI_COPY_CASE(MI_COPY_OUT, 1):
mi_copyout(q, mp);
return;
case MI_COPY_CASE(MI_COPY_OUT, 2):
mi_copy_done(q, mp, 0);
return;
default:
mi_copy_done(q, mp, EPROTO);
return;
}
if (!OK_32PTR(mp1->b_rptr)) {
mi_copy_done(q, mp, EINVAL);
return;
}
STRUCT_SET_HANDLE(sb, iocp->ioc_flag, (void *)mp1->b_rptr);
if (connp->conn_family == AF_INET)
addrlen = sizeof (sin_t);
else
addrlen = sizeof (sin6_t);
if (STRUCT_FGET(sb, maxlen) < addrlen) {
mi_copy_done(q, mp, EINVAL);
return;
}
switch (iocp->ioc_cmd) {
case TI_GETMYNAME:
break;
case TI_GETPEERNAME:
if (tcp->tcp_state < TCPS_SYN_RCVD) {
mi_copy_done(q, mp, ENOTCONN);
return;
}
break;
}
mp1 = mi_copyout_alloc(q, mp, STRUCT_FGETP(sb, buf), addrlen, B_TRUE);
if (!mp1)
return;
STRUCT_FSET(sb, len, addrlen);
switch (((struct iocblk *)mp->b_rptr)->ioc_cmd) {
case TI_GETMYNAME:
(void) conn_getsockname(connp, (struct sockaddr *)mp1->b_wptr,
&addrlen);
break;
case TI_GETPEERNAME:
(void) conn_getpeername(connp, (struct sockaddr *)mp1->b_wptr,
&addrlen);
break;
}
mp1->b_wptr += addrlen;
mi_copyout(q, mp);
}
static void
tcp_wput_ioctl(void *arg, mblk_t *mp, void *arg2, ip_recv_attr_t *dummy)
{
conn_t *connp = (conn_t *)arg;
tcp_t *tcp = connp->conn_tcp;
queue_t *q = connp->conn_wq;
struct iocblk *iocp;
ASSERT(DB_TYPE(mp) == M_IOCTL);
ASSERT((connp->conn_fanout != NULL && connp->conn_ref >= 4) ||
(connp->conn_fanout == NULL && connp->conn_ref >= 3));
iocp = (struct iocblk *)mp->b_rptr;
switch (iocp->ioc_cmd) {
case _SIOCSOCKFALLBACK:
if (!tcp->tcp_issocket) {
DB_TYPE(mp) = M_IOCNAK;
iocp->ioc_error = EINVAL;
} else {
tcp_use_pure_tpi(tcp);
DB_TYPE(mp) = M_IOCACK;
iocp->ioc_error = 0;
}
iocp->ioc_count = 0;
iocp->ioc_rval = 0;
qreply(q, mp);
return;
}
mutex_enter(&(connp)->conn_lock);
if ((connp)->conn_state_flags & CONN_CLOSING) {
mutex_exit(&(connp)->conn_lock);
iocp->ioc_error = EINVAL;
mp->b_datap->db_type = M_IOCNAK;
iocp->ioc_count = 0;
qreply(q, mp);
return;
}
CONN_INC_IOCTLREF_LOCKED(connp);
ip_wput_nondata(q, mp);
CONN_DEC_IOCTLREF(connp);
}
static void
tcp_wput_proto(void *arg, mblk_t *mp, void *arg2, ip_recv_attr_t *dummy)
{
conn_t *connp = (conn_t *)arg;
tcp_t *tcp = connp->conn_tcp;
union T_primitives *tprim = (union T_primitives *)mp->b_rptr;
uchar_t *rptr;
t_scalar_t type;
cred_t *cr;
ASSERT((connp->conn_fanout != NULL && connp->conn_ref >= 4) ||
(connp->conn_fanout == NULL && connp->conn_ref >= 3));
rptr = mp->b_rptr;
ASSERT((uintptr_t)(mp->b_wptr - rptr) <= (uintptr_t)INT_MAX);
if ((mp->b_wptr - rptr) >= sizeof (t_scalar_t)) {
type = ((union T_primitives *)rptr)->type;
if (type == T_EXDATA_REQ) {
tcp_output_urgent(connp, mp, arg2, NULL);
} else if (type != T_DATA_REQ) {
goto non_urgent_data;
} else {
tcp_wput_data(tcp, mp->b_cont, B_TRUE);
freeb(mp);
}
return;
} else {
if (connp->conn_debug) {
(void) strlog(TCP_MOD_ID, 0, 1, SL_ERROR|SL_TRACE,
"tcp_wput_proto, dropping one...");
}
freemsg(mp);
return;
}
non_urgent_data:
switch ((int)tprim->type) {
case O_T_BIND_REQ:
case T_BIND_REQ:
tcp_tpi_bind(tcp, mp);
break;
case T_UNBIND_REQ:
tcp_tpi_unbind(tcp, mp);
break;
case O_T_CONN_RES:
case T_CONN_RES:
tcp_tli_accept(tcp, mp);
break;
case T_CONN_REQ:
tcp_tpi_connect(tcp, mp);
break;
case T_DISCON_REQ:
tcp_disconnect(tcp, mp);
break;
case T_CAPABILITY_REQ:
tcp_capability_req(tcp, mp);
break;
case T_INFO_REQ:
tcp_info_req(tcp, mp);
break;
case T_SVR4_OPTMGMT_REQ:
case T_OPTMGMT_REQ:
cr = msg_getcred(mp, NULL);
ASSERT(cr != NULL);
if (cr == NULL) {
tcp_err_ack(tcp, mp, TSYSERR, EINVAL);
return;
}
if ((int)tprim->type == T_SVR4_OPTMGMT_REQ) {
svr4_optcom_req(connp->conn_wq, mp, cr, &tcp_opt_obj);
} else {
tpi_optcom_req(connp->conn_wq, mp, cr, &tcp_opt_obj);
}
break;
case T_UNITDATA_REQ:
tcp_err_ack(tcp, mp, TNOTSUPPORT, 0);
break;
case T_ORDREL_REQ:
freemsg(mp);
if (tcp->tcp_fused)
tcp_unfuse(tcp);
if (tcp_xmit_end(tcp) != 0) {
if (connp->conn_debug) {
(void) strlog(TCP_MOD_ID, 0, 1,
SL_ERROR|SL_TRACE,
"tcp_wput_proto, T_ORDREL_REQ out of "
"state %s",
tcp_display(tcp, NULL,
DISP_ADDR_AND_PORT));
}
}
break;
case T_ADDR_REQ:
tcp_addr_req(tcp, mp);
break;
default:
if (connp->conn_debug) {
(void) strlog(TCP_MOD_ID, 0, 1, SL_ERROR|SL_TRACE,
"tcp_wput_proto, bogus TPI msg, type %d",
tprim->type);
}
tcp_err_ack(tcp, mp, TNOTSUPPORT, 0);
break;
}
}
static void
tcp_wput_cmdblk(queue_t *q, mblk_t *mp)
{
void *data;
mblk_t *datamp = mp->b_cont;
conn_t *connp = Q_TO_CONN(q);
tcp_t *tcp = connp->conn_tcp;
cmdblk_t *cmdp = (cmdblk_t *)mp->b_rptr;
if (datamp == NULL || MBLKL(datamp) < cmdp->cb_len) {
cmdp->cb_error = EPROTO;
qreply(q, mp);
return;
}
data = datamp->b_rptr;
switch (cmdp->cb_cmd) {
case TI_GETPEERNAME:
if (tcp->tcp_state < TCPS_SYN_RCVD)
cmdp->cb_error = ENOTCONN;
else
cmdp->cb_error = conn_getpeername(connp, data,
&cmdp->cb_len);
break;
case TI_GETMYNAME:
cmdp->cb_error = conn_getsockname(connp, data, &cmdp->cb_len);
break;
default:
cmdp->cb_error = EINVAL;
break;
}
qreply(q, mp);
}
void
tcp_output(void *arg, mblk_t *mp, void *arg2, ip_recv_attr_t *dummy)
{
int len;
int hdrlen;
int plen;
mblk_t *mp1;
uchar_t *rptr;
uint32_t snxt;
tcpha_t *tcpha;
struct datab *db;
uint32_t suna;
uint32_t mss;
ipaddr_t *dst;
ipaddr_t *src;
uint32_t sum;
int usable;
conn_t *connp = (conn_t *)arg;
tcp_t *tcp = connp->conn_tcp;
uint32_t msize;
tcp_stack_t *tcps = tcp->tcp_tcps;
ip_xmit_attr_t *ixa;
clock_t now;
ASSERT((connp->conn_fanout != NULL && connp->conn_ref >= 4) ||
(connp->conn_fanout == NULL && connp->conn_ref >= 3));
ASSERT(DB_TYPE(mp) == M_DATA);
msize = (mp->b_cont == NULL) ? MBLKL(mp) : msgdsize(mp);
mutex_enter(&tcp->tcp_non_sq_lock);
tcp->tcp_squeue_bytes -= msize;
mutex_exit(&tcp->tcp_non_sq_lock);
if (tcp->tcp_fused && tcp_fuse_output(tcp, mp, msize))
return;
mss = tcp->tcp_mss;
if (tcp->tcp_snd_zcopy_aware && !tcp->tcp_snd_zcopy_on)
mp = tcp_zcopy_backoff(tcp, mp, B_FALSE);
ASSERT((uintptr_t)(mp->b_wptr - mp->b_rptr) <= (uintptr_t)INT_MAX);
len = (int)(mp->b_wptr - mp->b_rptr);
if (tcp->tcp_unsent != 0 ||
tcp->tcp_cork ||
tcp->tcp_md5sig ||
mp->b_cont != NULL ||
tcp->tcp_state != TCPS_ESTABLISHED ||
len == 0 ||
len > mss ||
tcp->tcp_valid_bits != 0) {
tcp_wput_data(tcp, mp, B_FALSE);
return;
}
ASSERT(tcp->tcp_xmit_tail_unsent == 0);
ASSERT(tcp->tcp_fin_sent == 0);
if (tcp->tcp_xmit_head == NULL) {
tcp->tcp_xmit_head = mp;
} else {
tcp->tcp_xmit_last->b_cont = mp;
}
tcp->tcp_xmit_last = mp;
tcp->tcp_xmit_tail = mp;
snxt = tcp->tcp_snxt;
now = LBOLT_FASTPATH;
if ((tcp->tcp_suna == snxt) && !tcp->tcp_localnet &&
(TICK_TO_MSEC(now - tcp->tcp_last_recv_time) >= tcp->tcp_rto)) {
cc_after_idle(tcp);
}
usable = tcp->tcp_swnd;
if (usable > tcp->tcp_cwnd)
usable = tcp->tcp_cwnd;
usable -= snxt;
suna = tcp->tcp_suna;
usable += suna;
if (len > usable) {
goto slow;
}
mutex_enter(&tcp->tcp_non_sq_lock);
if (tcp->tcp_flow_stopped &&
TCP_UNSENT_BYTES(tcp) <= connp->conn_sndlowat) {
tcp_clrqfull(tcp);
}
mutex_exit(&tcp->tcp_non_sq_lock);
if ((len < mss) && (snxt != suna) &&
(len < (int)tcp->tcp_naglim) &&
(tcp->tcp_last_sent_len < tcp->tcp_naglim)) {
tcp->tcp_unsent = len;
tcp->tcp_xmit_tail_unsent = len;
return;
}
if (snxt == suna) {
TCP_TIMER_RESTART(tcp, tcp->tcp_rto);
}
tcp->tcp_rack_cnt = 0;
tcp->tcp_snxt = snxt + len;
tcp->tcp_rack = tcp->tcp_rnxt;
if ((mp1 = dupb(mp)) == 0)
goto no_memory;
mp->b_prev = (mblk_t *)(intptr_t)gethrtime();
mp->b_next = (mblk_t *)(uintptr_t)snxt;
tcpha = tcp->tcp_tcpha;
tcpha->tha_flags = (TH_ACK|TH_PUSH);
sum = len + connp->conn_ht_ulp_len + connp->conn_sum;
sum = (sum >> 16) + (sum & 0xFFFF);
tcpha->tha_sum = htons(sum);
tcpha->tha_seq = htonl(snxt);
TCPS_BUMP_MIB(tcps, tcpOutDataSegs);
TCPS_UPDATE_MIB(tcps, tcpOutDataBytes, len);
TCPS_BUMP_MIB(tcps, tcpHCOutSegs);
tcp->tcp_cs.tcp_out_data_segs++;
tcp->tcp_cs.tcp_out_data_bytes += len;
tcpha->tha_win = htons(tcp->tcp_rwnd >> tcp->tcp_rcv_ws);
tcp->tcp_last_sent_len = (ushort_t)len;
plen = len + connp->conn_ht_iphc_len;
ixa = connp->conn_ixa;
ixa->ixa_pktlen = plen;
if (ixa->ixa_flags & IXAF_IS_IPV4) {
tcp->tcp_ipha->ipha_length = htons(plen);
} else {
tcp->tcp_ip6h->ip6_plen = htons(plen - IPV6_HDR_LEN);
}
hdrlen = connp->conn_ht_iphc_len;
rptr = mp1->b_rptr - hdrlen;
db = mp1->b_datap;
if ((db->db_ref != 2) || rptr < db->db_base ||
(!OK_32PTR(rptr))) {
mp = allocb(hdrlen + tcps->tcps_wroff_xtra, BPRI_MED);
if (!mp) {
freemsg(mp1);
goto no_memory;
}
mp->b_cont = mp1;
mp1 = mp;
rptr = &mp1->b_rptr[tcps->tcps_wroff_xtra];
mp1->b_wptr = &rptr[hdrlen];
}
mp1->b_rptr = rptr;
if (tcp->tcp_snd_ts_ok) {
U32_TO_BE32(now,
(char *)tcpha + TCP_MIN_HEADER_LENGTH + 4);
U32_TO_BE32(tcp->tcp_ts_recent,
(char *)tcpha + TCP_MIN_HEADER_LENGTH + 8);
} else {
ASSERT(connp->conn_ht_ulp_len == TCP_MIN_HEADER_LENGTH);
}
dst = (ipaddr_t *)rptr;
src = (ipaddr_t *)connp->conn_ht_iphc;
dst[0] = src[0];
dst[1] = src[1];
dst[2] = src[2];
dst[3] = src[3];
dst[4] = src[4];
dst[5] = src[5];
dst[6] = src[6];
dst[7] = src[7];
dst[8] = src[8];
dst[9] = src[9];
if (hdrlen -= 40) {
hdrlen >>= 2;
dst += 10;
src += 10;
do {
*dst++ = *src++;
} while (--hdrlen);
}
if (tcp->tcp_ecn_ok) {
TCP_SET_ECT(tcp, rptr);
tcpha = (tcpha_t *)(rptr + ixa->ixa_ip_hdr_length);
if (tcp->tcp_ecn_echo_on)
tcpha->tha_flags |= TH_ECE;
if (tcp->tcp_cwr && !tcp->tcp_ecn_cwr_sent) {
tcpha->tha_flags |= TH_CWR;
tcp->tcp_ecn_cwr_sent = B_TRUE;
}
}
if (tcp->tcp_ip_forward_progress) {
tcp->tcp_ip_forward_progress = B_FALSE;
connp->conn_ixa->ixa_flags |= IXAF_REACH_CONF;
} else {
connp->conn_ixa->ixa_flags &= ~IXAF_REACH_CONF;
}
tcp_send_data(tcp, mp1);
return;
no_memory:
return;
slow:
tcp->tcp_unsent = len;
tcp->tcp_xmit_tail_unsent = len;
tcp_wput_data(tcp, NULL, B_FALSE);
}
void
tcp_output_urgent(void *arg, mblk_t *mp, void *arg2, ip_recv_attr_t *dummy)
{
int len;
uint32_t msize;
conn_t *connp = (conn_t *)arg;
tcp_t *tcp = connp->conn_tcp;
msize = msgdsize(mp);
len = msize - 1;
if (len < 0) {
freemsg(mp);
return;
}
len += tcp->tcp_unsent;
len += tcp->tcp_snxt;
tcp->tcp_urg = len;
tcp->tcp_valid_bits |= TCP_URG_VALID;
if (tcp->tcp_fused && tcp_fuse_output(tcp, mp, msize))
return;
if (DB_TYPE(mp) != M_DATA) {
mblk_t *mp1 = mp;
ASSERT(!IPCL_IS_NONSTR(connp));
mp = mp->b_cont;
freeb(mp1);
}
tcp_wput_data(tcp, mp, B_TRUE);
}
void
tcp_close_output(void *arg, mblk_t *mp, void *arg2, ip_recv_attr_t *dummy)
{
char *msg;
conn_t *connp = (conn_t *)arg;
tcp_t *tcp = connp->conn_tcp;
clock_t delta = 0;
tcp_stack_t *tcps = tcp->tcp_tcps;
ASSERT((connp->conn_fanout != NULL &&
connp->conn_ref >= (IPCL_IS_NONSTR(connp) ? 3 : 4)) ||
(connp->conn_fanout == NULL &&
connp->conn_ref >= (IPCL_IS_NONSTR(connp) ? 2 : 3)));
mutex_enter(&tcp->tcp_eager_lock);
if (tcp->tcp_conn_req_cnt_q0 != 0 || tcp->tcp_conn_req_cnt_q != 0) {
tcp_eager_cleanup(tcp, IPCL_IS_NONSTR(connp) ? 1 : 0);
tcp->tcp_wait_for_eagers = 1;
}
mutex_exit(&tcp->tcp_eager_lock);
tcp->tcp_lso = B_FALSE;
msg = NULL;
switch (tcp->tcp_state) {
case TCPS_CLOSED:
case TCPS_IDLE:
break;
case TCPS_BOUND:
if (tcp->tcp_listener != NULL) {
ASSERT(IPCL_IS_NONSTR(connp));
mutex_enter(&tcp->tcp_saved_listener->tcp_eager_lock);
tcp_eager_unlink(tcp);
mutex_exit(&tcp->tcp_saved_listener->tcp_eager_lock);
CONN_DEC_REF(tcp->tcp_saved_listener->tcp_connp);
}
break;
case TCPS_LISTEN:
break;
case TCPS_SYN_SENT:
msg = "tcp_close, during connect";
break;
case TCPS_SYN_RCVD:
default:
if (tcp->tcp_fused)
tcp_unfuse(tcp);
if (connp->conn_linger && connp->conn_lingertime == 0) {
msg = "tcp_close, zero lingertime";
break;
}
if (tcp->tcp_rcv_list || tcp->tcp_reass_head) {
msg = "tcp_close, unread data";
break;
}
if (tcp->tcp_listener != NULL) {
ASSERT(IPCL_IS_NONSTR(connp));
msg = "tcp_close, close before accept";
mutex_enter(&tcp->tcp_saved_listener->tcp_eager_lock);
tcp_eager_unlink(tcp);
mutex_exit(&tcp->tcp_saved_listener->tcp_eager_lock);
CONN_DEC_REF(tcp->tcp_saved_listener->tcp_connp);
break;
}
(void) tcp_xmit_end(tcp);
if (connp->conn_linger && connp->conn_lingertime > 0 &&
!(tcp->tcp_fin_acked) &&
tcp->tcp_state >= TCPS_ESTABLISHED) {
if (tcp->tcp_closeflags & (FNDELAY|FNONBLOCK)) {
tcp->tcp_client_errno = EWOULDBLOCK;
} else if (tcp->tcp_client_errno == 0) {
ASSERT(tcp->tcp_linger_tid == 0);
tcp->tcp_linger_tid = TCP_TIMER(tcp,
tcp_close_linger_timeout,
connp->conn_lingertime * MILLISEC);
if (tcp->tcp_linger_tid == 0)
tcp->tcp_client_errno = ENOSR;
else
return;
}
if (tcp->tcp_state <= TCPS_LISTEN)
break;
}
tcp_acceptor_hash_remove(tcp);
mutex_enter(&tcp->tcp_non_sq_lock);
if (tcp->tcp_flow_stopped) {
tcp_clrqfull(tcp);
}
mutex_exit(&tcp->tcp_non_sq_lock);
if (tcp->tcp_timer_tid != 0) {
delta = TCP_TIMER_CANCEL(tcp, tcp->tcp_timer_tid);
tcp->tcp_timer_tid = 0;
}
tcp_timers_stop(tcp);
tcp->tcp_detached = B_TRUE;
if (tcp->tcp_state == TCPS_TIME_WAIT) {
tcp_time_wait_append(tcp);
TCP_DBGSTAT(tcps, tcp_detach_time_wait);
ASSERT(connp->conn_ref >=
(IPCL_IS_NONSTR(connp) ? 2 : 3));
goto finish;
}
if (delta >= 0)
tcp->tcp_timer_tid = TCP_TIMER(tcp, tcp_timer,
delta ? delta : 1);
ASSERT(connp->conn_ref >= (IPCL_IS_NONSTR(connp) ? 2 : 3));
goto finish;
}
if (msg) {
if (tcp->tcp_state == TCPS_ESTABLISHED ||
tcp->tcp_state == TCPS_CLOSE_WAIT)
TCPS_BUMP_MIB(tcps, tcpEstabResets);
if (tcp->tcp_state == TCPS_SYN_SENT ||
tcp->tcp_state == TCPS_SYN_RCVD)
TCPS_BUMP_MIB(tcps, tcpAttemptFails);
tcp_xmit_ctl(msg, tcp, tcp->tcp_snxt, 0, TH_RST);
}
tcp_closei_local(tcp);
CONN_DEC_REF(connp);
ASSERT(connp->conn_ref >= (IPCL_IS_NONSTR(connp) ? 1 : 2));
finish:
if (!tcp->tcp_wait_for_eagers) {
tcp->tcp_detached = B_TRUE;
connp->conn_rq = NULL;
connp->conn_wq = NULL;
if (IPCL_IS_NONSTR(connp)) {
sock_upcalls_t *upcalls = connp->conn_upcalls;
sock_upper_handle_t handle = connp->conn_upper_handle;
ASSERT(upcalls != NULL);
ASSERT(upcalls->su_closed != NULL);
ASSERT(handle != NULL);
mutex_enter(&connp->conn_lock);
connp->conn_upper_handle = NULL;
connp->conn_upcalls = NULL;
mutex_exit(&connp->conn_lock);
upcalls->su_closed(handle);
}
}
mutex_enter(&tcp->tcp_closelock);
tcp->tcp_closed = 1;
cv_signal(&tcp->tcp_closecv);
mutex_exit(&tcp->tcp_closelock);
}
void
tcp_shutdown_output(void *arg, mblk_t *mp, void *arg2, ip_recv_attr_t *dummy)
{
conn_t *connp = (conn_t *)arg;
tcp_t *tcp = connp->conn_tcp;
freemsg(mp);
if (tcp->tcp_fused)
tcp_unfuse(tcp);
if (tcp_xmit_end(tcp) != 0) {
if (connp->conn_debug) {
(void) strlog(TCP_MOD_ID, 0, 1,
SL_ERROR|SL_TRACE,
"tcp_shutdown_output() out of state %s",
tcp_display(tcp, NULL, DISP_ADDR_AND_PORT));
}
}
}
void
tcp_send_data(tcp_t *tcp, mblk_t *mp)
{
conn_t *connp = tcp->tcp_connp;
if (tcp->tcp_snd_zcopy_aware && !tcp->tcp_snd_zcopy_on &&
!tcp->tcp_xmit_zc_clean) {
ip_drop_output("TCP ZC was disabled but not clean", mp, NULL);
freemsg(mp);
return;
}
DTRACE_TCP5(send, mblk_t *, NULL, ip_xmit_attr_t *, connp->conn_ixa,
__dtrace_tcp_void_ip_t *, mp->b_rptr, tcp_t *, tcp,
__dtrace_tcp_tcph_t *,
&mp->b_rptr[connp->conn_ixa->ixa_ip_hdr_length]);
ASSERT(connp->conn_ixa->ixa_notify_cookie == connp->conn_tcp);
(void) conn_ip_output(mp, connp->conn_ixa);
}
void
tcp_send_synack(void *arg, mblk_t *mp, void *arg2, ip_recv_attr_t *dummy)
{
conn_t *econnp = (conn_t *)arg;
tcp_t *tcp = econnp->conn_tcp;
ip_xmit_attr_t *ixa = econnp->conn_ixa;
if (tcp->tcp_state == TCPS_CLOSED) {
freemsg(mp);
return;
}
ixa->ixa_pktlen = msgdsize(mp);
(void) conn_ip_output(mp, ixa);
}
static int
tcp_send(tcp_t *tcp, const int mss, const int total_hdr_len,
const int tcp_hdr_len, const int num_sack_blk, int *usable,
uint32_t *snxt, int *tail_unsent, mblk_t **xmit_tail, mblk_t *local_time)
{
int num_lso_seg = 1;
uint_t lso_usable = 0;
boolean_t do_lso_send = B_FALSE;
tcp_stack_t *tcps = tcp->tcp_tcps;
conn_t *connp = tcp->tcp_connp;
ip_xmit_attr_t *ixa = connp->conn_ixa;
if (tcp->tcp_lso && (tcp->tcp_valid_bits & ~TCP_FSS_VALID) == 0)
do_lso_send = B_TRUE;
for (;;) {
struct datab *db;
tcpha_t *tcpha;
uint32_t sum;
mblk_t *mp, *mp1;
uchar_t *rptr;
int len;
if (do_lso_send) {
if ((*usable - 1) / mss >= 1) {
lso_usable = MIN(tcp->tcp_lso_max, *usable);
num_lso_seg = lso_usable / mss;
if (lso_usable % mss) {
num_lso_seg++;
tcp->tcp_last_sent_len = (ushort_t)
(lso_usable % mss);
} else {
tcp->tcp_last_sent_len = (ushort_t)mss;
}
} else {
do_lso_send = B_FALSE;
num_lso_seg = 1;
lso_usable = mss;
}
}
ASSERT(num_lso_seg <= IP_MAXPACKET / mss + 1);
len = mss;
if (len > *usable) {
ASSERT(do_lso_send == B_FALSE);
len = *usable;
if (len <= 0) {
break;
}
if (len < (tcp->tcp_max_swnd >> 1) &&
(tcp->tcp_unsent - (*snxt - tcp->tcp_snxt)) > len &&
!((tcp->tcp_valid_bits & TCP_URG_VALID) &&
len == 1) && (! tcp->tcp_zero_win_probe)) {
if (*snxt == tcp->tcp_snxt &&
*snxt == tcp->tcp_suna) {
TCP_TIMER_RESTART(tcp, tcp->tcp_rto);
}
break;
}
}
tcpha = tcp->tcp_tcpha;
if (do_lso_send)
len = lso_usable;
*usable -= len;
if (*usable > 0)
tcpha->tha_flags = TH_ACK;
else
tcpha->tha_flags = (TH_ACK | TH_PUSH);
if (do_lso_send) {
sum = 0;
} else {
sum = len + tcp_hdr_len + connp->conn_sum;
sum = (sum >> 16) + (sum & 0xFFFF);
}
tcpha->tha_sum = htons(sum);
tcpha->tha_seq = htonl(*snxt);
if (tcp->tcp_md5sig || (tcp->tcp_valid_bits != 0 &&
(tcp->tcp_valid_bits != TCP_FSS_VALID ||
*snxt + len == tcp->tcp_fss))) {
uchar_t *prev_rptr;
uint32_t prev_snxt = tcp->tcp_snxt;
if (*tail_unsent == 0) {
ASSERT((*xmit_tail)->b_cont != NULL);
*xmit_tail = (*xmit_tail)->b_cont;
prev_rptr = (*xmit_tail)->b_rptr;
*tail_unsent = (int)((*xmit_tail)->b_wptr -
(*xmit_tail)->b_rptr);
} else {
prev_rptr = (*xmit_tail)->b_rptr;
(*xmit_tail)->b_rptr = (*xmit_tail)->b_wptr -
*tail_unsent;
}
mp = tcp_xmit_mp(tcp, *xmit_tail, len, NULL, NULL,
*snxt, B_FALSE, (uint32_t *)&len, B_FALSE);
tcp->tcp_snxt = prev_snxt;
if (prev_rptr == (*xmit_tail)->b_rptr) {
if ((*xmit_tail)->b_next == NULL) {
(*xmit_tail)->b_prev = local_time;
(*xmit_tail)->b_next =
(mblk_t *)(uintptr_t)(*snxt);
}
} else
(*xmit_tail)->b_rptr = prev_rptr;
if (mp == NULL) {
return (-1);
}
mp1 = mp->b_cont;
if (len <= mss)
tcp->tcp_last_sent_len = (ushort_t)len;
while (mp1->b_cont) {
*xmit_tail = (*xmit_tail)->b_cont;
(*xmit_tail)->b_prev = local_time;
(*xmit_tail)->b_next =
(mblk_t *)(uintptr_t)(*snxt);
mp1 = mp1->b_cont;
}
*snxt += len;
*tail_unsent = (*xmit_tail)->b_wptr - mp1->b_wptr;
TCPS_BUMP_MIB(tcps, tcpHCOutSegs);
TCPS_BUMP_MIB(tcps, tcpOutDataSegs);
TCPS_UPDATE_MIB(tcps, tcpOutDataBytes, len);
tcp->tcp_cs.tcp_out_data_segs++;
tcp->tcp_cs.tcp_out_data_bytes += len;
tcp_send_data(tcp, mp);
continue;
}
*snxt += len;
TCPS_BUMP_MIB(tcps, tcpHCOutSegs);
TCPS_BUMP_MIB(tcps, tcpOutDataSegs);
TCPS_UPDATE_MIB(tcps, tcpOutDataBytes, len);
tcp->tcp_cs.tcp_out_data_segs++;
tcp->tcp_cs.tcp_out_data_bytes += len;
if (*tail_unsent) {
rptr = (*xmit_tail)->b_wptr - *tail_unsent;
if (rptr != (*xmit_tail)->b_rptr) {
*tail_unsent -= len;
if (len <= mss)
tcp->tcp_last_sent_len = (ushort_t)len;
len += total_hdr_len;
ixa->ixa_pktlen = len;
if (ixa->ixa_flags & IXAF_IS_IPV4) {
tcp->tcp_ipha->ipha_length = htons(len);
} else {
tcp->tcp_ip6h->ip6_plen =
htons(len - IPV6_HDR_LEN);
}
mp = dupb(*xmit_tail);
if (mp == NULL) {
return (-1);
}
mp->b_rptr = rptr;
if ((*xmit_tail)->b_next == NULL) {
(*xmit_tail)->b_prev = local_time;
(*xmit_tail)->b_next =
(mblk_t *)(uintptr_t)(*snxt-len);
}
goto must_alloc;
}
} else {
*xmit_tail = (*xmit_tail)->b_cont;
ASSERT((uintptr_t)((*xmit_tail)->b_wptr -
(*xmit_tail)->b_rptr) <= (uintptr_t)INT_MAX);
*tail_unsent = (int)((*xmit_tail)->b_wptr -
(*xmit_tail)->b_rptr);
}
(*xmit_tail)->b_prev = local_time;
(*xmit_tail)->b_next = (mblk_t *)(uintptr_t)(*snxt - len);
*tail_unsent -= len;
if (len <= mss)
tcp->tcp_last_sent_len = (ushort_t)len;
len += total_hdr_len;
ixa->ixa_pktlen = len;
if (ixa->ixa_flags & IXAF_IS_IPV4) {
tcp->tcp_ipha->ipha_length = htons(len);
} else {
tcp->tcp_ip6h->ip6_plen = htons(len - IPV6_HDR_LEN);
}
mp = dupb(*xmit_tail);
if (mp == NULL) {
return (-1);
}
len = total_hdr_len;
rptr = mp->b_rptr - len;
if (!OK_32PTR(rptr) ||
((db = mp->b_datap), db->db_ref != 2) ||
rptr < db->db_base) {
must_alloc:;
mp1 = allocb(connp->conn_ht_iphc_allocated +
tcps->tcps_wroff_xtra, BPRI_MED);
if (mp1 == NULL) {
freemsg(mp);
return (-1);
}
mp1->b_cont = mp;
mp = mp1;
len = total_hdr_len;
rptr = &mp->b_rptr[tcps->tcps_wroff_xtra];
mp->b_wptr = &rptr[len];
}
tcp_fill_header(tcp, rptr, num_sack_blk);
mp->b_rptr = rptr;
if (*tail_unsent) {
int spill = *tail_unsent;
mp1 = mp->b_cont;
if (mp1 == NULL)
mp1 = mp;
while (spill < 0) {
mblk_t *nmp;
int nmpsz;
nmp = (*xmit_tail)->b_cont;
nmpsz = MBLKL(nmp);
if (!do_lso_send && (spill + nmpsz > 0)) {
if (tcp->tcp_maxpsz_multiplier > 0)
break;
if (tcp->tcp_localnet &&
!tcp->tcp_cork &&
(nmpsz < roundup((mss >> 1), 64)))
break;
}
*xmit_tail = nmp;
ASSERT((uintptr_t)nmpsz <= (uintptr_t)INT_MAX);
(*xmit_tail)->b_prev = local_time;
(*xmit_tail)->b_next =
(mblk_t *)(uintptr_t)(*snxt - len);
mp1->b_cont = dupb(*xmit_tail);
mp1 = mp1->b_cont;
spill += nmpsz;
if (mp1 == NULL) {
*tail_unsent = spill;
freemsg(mp);
return (-1);
}
}
if (spill >= 0) {
mp1->b_wptr -= spill;
*tail_unsent = spill;
} else {
*usable -= spill;
*snxt += spill;
tcp->tcp_last_sent_len += spill;
TCPS_UPDATE_MIB(tcps, tcpOutDataBytes, spill);
tcp->tcp_cs.tcp_out_data_bytes += spill;
tcpha = (tcpha_t *)(rptr +
ixa->ixa_ip_hdr_length);
sum += spill;
sum = (sum >> 16) + (sum & 0xFFFF);
tcpha->tha_sum = htons(sum);
if (connp->conn_ipversion == IPV4_VERSION) {
sum = ntohs(
((ipha_t *)rptr)->ipha_length) +
spill;
((ipha_t *)rptr)->ipha_length =
htons(sum);
} else {
sum = ntohs(
((ip6_t *)rptr)->ip6_plen) +
spill;
((ip6_t *)rptr)->ip6_plen =
htons(sum);
}
ixa->ixa_pktlen += spill;
*tail_unsent = 0;
}
}
if (tcp->tcp_ip_forward_progress) {
tcp->tcp_ip_forward_progress = B_FALSE;
ixa->ixa_flags |= IXAF_REACH_CONF;
} else {
ixa->ixa_flags &= ~IXAF_REACH_CONF;
}
if (do_lso_send) {
lso_info_set(mp, mss, HW_LSO);
ixa->ixa_fragsize = IP_MAXPACKET;
ixa->ixa_extra_ident = num_lso_seg - 1;
DTRACE_PROBE2(tcp_send_lso, int, num_lso_seg,
boolean_t, B_TRUE);
tcp_send_data(tcp, mp);
ixa->ixa_fragsize = ixa->ixa_pmtu;
ixa->ixa_extra_ident = 0;
TCPS_BUMP_MIB(tcps, tcpHCOutSegs);
TCP_STAT(tcps, tcp_lso_times);
TCP_STAT_UPDATE(tcps, tcp_lso_pkt_out, num_lso_seg);
} else {
lso_info_cleanup(mp);
tcp_send_data(tcp, mp);
TCPS_BUMP_MIB(tcps, tcpHCOutSegs);
}
}
return (0);
}
static int
tcp_xmit_end(tcp_t *tcp)
{
mblk_t *mp;
tcp_stack_t *tcps = tcp->tcp_tcps;
iulp_t uinfo;
ip_stack_t *ipst = tcps->tcps_netstack->netstack_ip;
conn_t *connp = tcp->tcp_connp;
if (tcp->tcp_state < TCPS_SYN_RCVD ||
tcp->tcp_state > TCPS_CLOSE_WAIT) {
return (-1);
}
tcp->tcp_fss = tcp->tcp_snxt + tcp->tcp_unsent;
tcp->tcp_valid_bits |= TCP_FSS_VALID;
if (tcp->tcp_unsent == 0) {
mp = tcp_xmit_mp(tcp, NULL, 0, NULL, NULL,
tcp->tcp_fss, B_FALSE, NULL, B_FALSE);
if (mp) {
tcp_send_data(tcp, mp);
} else {
tcp->tcp_snxt = tcp->tcp_fss + 1;
TCP_TIMER_RESTART(tcp, tcp->tcp_rto);
}
if (tcp->tcp_rexmit && tcp->tcp_rexmit_nxt == tcp->tcp_fss) {
tcp->tcp_rexmit_nxt = tcp->tcp_snxt;
}
} else {
if (tcp->tcp_cork)
tcp->tcp_cork = B_FALSE;
tcp_wput_data(tcp, NULL, B_FALSE);
}
if (tcps->tcps_rtt_updates == 0 ||
tcp->tcp_rtt_update < tcps->tcps_rtt_updates)
return (0);
bzero(&uinfo, sizeof (uinfo));
uinfo.iulp_rtt = NSEC2MSEC(tcp->tcp_rtt_sa);
uinfo.iulp_rtt_sd = NSEC2MSEC(tcp->tcp_rtt_sd);
if (connp->conn_ipversion == IPV4_VERSION) {
if (connp->conn_faddr_v4 != tcp->tcp_ipha->ipha_dst) {
return (0);
}
(void) dce_update_uinfo_v4(connp->conn_faddr_v4, &uinfo, ipst);
} else {
uint_t ifindex;
if (!(IN6_ARE_ADDR_EQUAL(&connp->conn_faddr_v6,
&tcp->tcp_ip6h->ip6_dst))) {
return (0);
}
ifindex = 0;
if (IN6_IS_ADDR_LINKSCOPE(&connp->conn_faddr_v6)) {
ip_xmit_attr_t *ixa = connp->conn_ixa;
if (ixa->ixa_nce != NULL) {
ifindex = ixa->ixa_nce->nce_common->ncec_ill->
ill_phyint->phyint_ifindex;
} else {
return (0);
}
}
(void) dce_update_uinfo(&connp->conn_faddr_v6, ifindex, &uinfo,
ipst);
}
return (0);
}
void
tcp_xmit_ctl(char *str, tcp_t *tcp, uint32_t seq, uint32_t ack, int ctl)
{
uchar_t *rptr;
tcpha_t *tcpha;
ipha_t *ipha = NULL;
ip6_t *ip6h = NULL;
uint32_t sum;
int total_hdr_len;
int ip_hdr_len;
mblk_t *mp;
tcp_stack_t *tcps = tcp->tcp_tcps;
conn_t *connp = tcp->tcp_connp;
ip_xmit_attr_t *ixa = connp->conn_ixa;
sum = connp->conn_ht_ulp_len + connp->conn_sum;
total_hdr_len = connp->conn_ht_iphc_len;
ip_hdr_len = ixa->ixa_ip_hdr_length;
if (str != NULL && connp->conn_debug) {
(void) strlog(TCP_MOD_ID, 0, 1, SL_TRACE,
"tcp_xmit_ctl: '%s', seq 0x%x, ack 0x%x, ctl 0x%x",
str, seq, ack, ctl);
}
mp = allocb(connp->conn_ht_iphc_allocated + tcps->tcps_wroff_xtra,
BPRI_MED);
if (mp == NULL) {
return;
}
rptr = &mp->b_rptr[tcps->tcps_wroff_xtra];
mp->b_rptr = rptr;
mp->b_wptr = &rptr[total_hdr_len];
bcopy(connp->conn_ht_iphc, rptr, total_hdr_len);
ixa->ixa_pktlen = total_hdr_len;
if (ixa->ixa_flags & IXAF_IS_IPV4) {
ipha = (ipha_t *)rptr;
ipha->ipha_length = htons(total_hdr_len);
} else {
ip6h = (ip6_t *)rptr;
ip6h->ip6_plen = htons(total_hdr_len - IPV6_HDR_LEN);
}
tcpha = (tcpha_t *)&rptr[ip_hdr_len];
tcpha->tha_flags = (uint8_t)ctl;
if (ctl & TH_RST) {
TCPS_BUMP_MIB(tcps, tcpOutRsts);
TCPS_BUMP_MIB(tcps, tcpOutControl);
if (tcp->tcp_snd_ts_ok &&
tcp->tcp_state > TCPS_SYN_SENT) {
mp->b_wptr = &rptr[total_hdr_len - TCPOPT_REAL_TS_LEN];
*(mp->b_wptr) = TCPOPT_EOL;
ixa->ixa_pktlen = total_hdr_len - TCPOPT_REAL_TS_LEN;
if (connp->conn_ipversion == IPV4_VERSION) {
ipha->ipha_length = htons(total_hdr_len -
TCPOPT_REAL_TS_LEN);
} else {
ip6h->ip6_plen = htons(total_hdr_len -
IPV6_HDR_LEN - TCPOPT_REAL_TS_LEN);
}
tcpha->tha_offset_and_reserved -= (3 << 4);
sum -= TCPOPT_REAL_TS_LEN;
}
}
if (ctl & TH_ACK) {
if (tcp->tcp_snd_ts_ok) {
uint32_t llbolt = (uint32_t)LBOLT_FASTPATH;
U32_TO_BE32(llbolt,
(char *)tcpha + TCP_MIN_HEADER_LENGTH+4);
U32_TO_BE32(tcp->tcp_ts_recent,
(char *)tcpha + TCP_MIN_HEADER_LENGTH+8);
}
tcpha->tha_win = htons(tcp->tcp_rwnd >> tcp->tcp_rcv_ws);
tcp->tcp_tcpha->tha_win = tcpha->tha_win;
tcp->tcp_rack = ack;
tcp->tcp_rack_cnt = 0;
TCPS_BUMP_MIB(tcps, tcpOutAck);
}
tcpha->tha_seq = htonl(seq);
tcpha->tha_ack = htonl(ack);
if (tcp->tcp_md5sig) {
uint8_t digest[MD5_DIGEST_LENGTH];
int tcplen = (int)(mp->b_wptr - rptr) +
TCPOPT_REAL_MD5_LEN - ip_hdr_len;
if (tcpsig_signature(mp->b_cont, tcp, tcpha, tcplen, digest,
false)) {
uint8_t *wptr = mp->b_wptr;
wptr[0] = TCPOPT_NOP;
wptr[1] = TCPOPT_NOP;
wptr[2] = TCPOPT_MD5;
wptr[3] = TCPOPT_MD5_LEN;
bcopy(digest, &wptr[4], sizeof (digest));
tcpha->tha_offset_and_reserved += (5 << 4);
mp->b_wptr += TCPOPT_REAL_MD5_LEN;
ixa->ixa_pktlen += TCPOPT_REAL_MD5_LEN;
if (ixa->ixa_flags & IXAF_IS_IPV4) {
ipha->ipha_length = htons(ntohs(
ipha->ipha_length) + TCPOPT_REAL_MD5_LEN);
} else {
ip6h->ip6_plen = htons(ntohs(ip6h->ip6_plen) +
TCPOPT_REAL_MD5_LEN);
}
} else {
freemsg(mp);
return;
}
}
TCPS_BUMP_MIB(tcps, tcpHCOutSegs);
sum = (sum >> 16) + (sum & 0xFFFF);
tcpha->tha_sum = htons(sum);
tcp_send_data(tcp, mp);
}
static void
tcp_xmit_early_reset(char *str, mblk_t *mp, uint32_t seq, uint32_t ack, int ctl,
ip_recv_attr_t *ira, ip_stack_t *ipst, conn_t *connp)
{
ipha_t *ipha = NULL;
ip6_t *ip6h = NULL;
ushort_t len;
tcpha_t *tcpha;
int i;
ipaddr_t v4addr;
in6_addr_t v6addr;
netstack_t *ns = ipst->ips_netstack;
tcp_stack_t *tcps = ns->netstack_tcp;
ip_xmit_attr_t ixas, *ixa;
uint_t ip_hdr_len = ira->ira_ip_hdr_length;
boolean_t need_refrele = B_FALSE;
ushort_t port;
if (!tcp_send_rst_chk(tcps)) {
TCP_STAT(tcps, tcp_rst_unsent);
freemsg(mp);
return;
}
if (connp != NULL) {
ASSERT(connp->conn_on_sqp);
ixa = conn_get_ixa_exclusive(connp);
if (ixa == NULL) {
TCP_STAT(tcps, tcp_rst_unsent);
freemsg(mp);
return;
}
need_refrele = B_TRUE;
} else {
bzero(&ixas, sizeof (ixas));
ixa = &ixas;
ixa->ixa_flags |= IXAF_SET_ULP_CKSUM | IXAF_VERIFY_SOURCE;
ixa->ixa_protocol = IPPROTO_TCP;
ixa->ixa_zoneid = ira->ira_zoneid;
ixa->ixa_ifindex = 0;
ixa->ixa_ipst = ipst;
ixa->ixa_cred = kcred;
ixa->ixa_cpid = NOPID;
}
if (str && tcps->tcps_dbg) {
(void) strlog(TCP_MOD_ID, 0, 1, SL_TRACE,
"tcp_xmit_early_reset: '%s', seq 0x%x, ack 0x%x, "
"flags 0x%x",
str, seq, ack, ctl);
}
if (mp->b_datap->db_ref != 1) {
mblk_t *mp1 = copyb(mp);
freemsg(mp);
mp = mp1;
if (mp == NULL)
goto done;
} else if (mp->b_cont) {
freemsg(mp->b_cont);
mp->b_cont = NULL;
DB_CKSUMFLAGS(mp) = 0;
}
if (IPH_HDR_VERSION(mp->b_rptr) == IPV4_VERSION) {
ipha = (ipha_t *)mp->b_rptr;
for (i = IP_SIMPLE_HDR_LENGTH; i < (int)ip_hdr_len; i++)
mp->b_rptr[i] = IPOPT_EOL;
if (ipha->ipha_src == 0 || ipha->ipha_src == INADDR_BROADCAST ||
CLASSD(ipha->ipha_src)) {
BUMP_MIB(&ipst->ips_ip_mib, ipIfStatsInDiscards);
ip_drop_input("ipIfStatsInDiscards", mp, NULL);
freemsg(mp);
goto done;
}
} else {
ip6h = (ip6_t *)mp->b_rptr;
if (IN6_IS_ADDR_UNSPECIFIED(&ip6h->ip6_src) ||
IN6_IS_ADDR_MULTICAST(&ip6h->ip6_src)) {
BUMP_MIB(&ipst->ips_ip6_mib, ipIfStatsInDiscards);
ip_drop_input("ipIfStatsInDiscards", mp, NULL);
freemsg(mp);
goto done;
}
if (ip_hdr_len > IPV6_HDR_LEN) {
uint8_t *to;
to = mp->b_rptr + ip_hdr_len - IPV6_HDR_LEN;
ovbcopy(ip6h, to, IPV6_HDR_LEN);
mp->b_rptr += ip_hdr_len - IPV6_HDR_LEN;
ip_hdr_len = IPV6_HDR_LEN;
ip6h = (ip6_t *)mp->b_rptr;
ip6h->ip6_nxt = IPPROTO_TCP;
}
}
tcpha = (tcpha_t *)&mp->b_rptr[ip_hdr_len];
if (tcpha->tha_flags & TH_RST) {
freemsg(mp);
goto done;
}
tcpha->tha_offset_and_reserved = (5 << 4);
len = ip_hdr_len + sizeof (tcpha_t);
mp->b_wptr = &mp->b_rptr[len];
if (IPH_HDR_VERSION(mp->b_rptr) == IPV4_VERSION) {
ipha->ipha_length = htons(len);
v4addr = ipha->ipha_src;
ipha->ipha_src = ipha->ipha_dst;
ipha->ipha_dst = v4addr;
ipha->ipha_ident = 0;
ipha->ipha_ttl = (uchar_t)tcps->tcps_ipv4_ttl;
ixa->ixa_flags |= IXAF_IS_IPV4;
ixa->ixa_ip_hdr_length = ip_hdr_len;
} else {
ip6h->ip6_plen = htons(len - IPV6_HDR_LEN);
v6addr = ip6h->ip6_src;
ip6h->ip6_src = ip6h->ip6_dst;
ip6h->ip6_dst = v6addr;
ip6h->ip6_hops = (uchar_t)tcps->tcps_ipv6_hoplimit;
ixa->ixa_flags &= ~IXAF_IS_IPV4;
if (IN6_IS_ADDR_LINKSCOPE(&ip6h->ip6_dst)) {
ixa->ixa_flags |= IXAF_SCOPEID_SET;
ixa->ixa_scopeid = ira->ira_ruifindex;
}
ixa->ixa_ip_hdr_length = IPV6_HDR_LEN;
}
ixa->ixa_pktlen = len;
port = tcpha->tha_fport;
tcpha->tha_fport = tcpha->tha_lport;
tcpha->tha_lport = port;
tcpha->tha_ack = htonl(ack);
tcpha->tha_seq = htonl(seq);
tcpha->tha_win = 0;
tcpha->tha_sum = htons(sizeof (tcpha_t));
tcpha->tha_flags = (uint8_t)ctl;
if (ctl & TH_RST) {
if (ctl & TH_ACK) {
DTRACE_TCP5(accept__refused, mblk_t *, NULL,
void, NULL, void_ip_t *, mp->b_rptr, tcp_t *, NULL,
tcph_t *, tcpha);
}
TCPS_BUMP_MIB(tcps, tcpOutRsts);
TCPS_BUMP_MIB(tcps, tcpOutControl);
}
if (ixa->ixa_free_flags & IXA_FREE_TSL) {
ASSERT(ixa->ixa_tsl != NULL);
label_rele(ixa->ixa_tsl);
ixa->ixa_free_flags &= ~IXA_FREE_TSL;
}
ixa->ixa_tsl = ira->ira_tsl;
if (ira->ira_flags & IRAF_IPSEC_SECURE) {
if (!ipsec_in_to_out(ira, ixa, mp, ipha, ip6h)) {
BUMP_MIB(&ipst->ips_ip_mib, ipIfStatsOutDiscards);
goto done;
}
} else {
ixa->ixa_flags |= IXAF_NO_IPSEC;
}
DTRACE_TCP5(send, mblk_t *, NULL, ip_xmit_attr_t *, ixa,
__dtrace_tcp_void_ip_t *, mp->b_rptr, tcp_t *, NULL,
__dtrace_tcp_tcph_t *, tcpha);
(void) ip_output_simple(mp, ixa);
done:
ixa_cleanup(ixa);
if (need_refrele) {
ASSERT(ixa != &ixas);
ixa_refrele(ixa);
}
}
void
tcp_xmit_listeners_reset(mblk_t *mp, ip_recv_attr_t *ira, ip_stack_t *ipst,
conn_t *connp)
{
uchar_t *rptr;
uint32_t seg_len;
tcpha_t *tcpha;
uint32_t seg_seq;
uint32_t seg_ack;
uint_t flags;
ipha_t *ipha;
ip6_t *ip6h;
boolean_t policy_present;
netstack_t *ns = ipst->ips_netstack;
tcp_stack_t *tcps = ns->netstack_tcp;
ipsec_stack_t *ipss = tcps->tcps_netstack->netstack_ipsec;
uint_t ip_hdr_len = ira->ira_ip_hdr_length;
TCP_STAT(tcps, tcp_no_listener);
DTRACE_TCP5(receive, mblk_t *, NULL, ip_xmit_attr_t *, NULL,
__dtrace_tcp_void_ip_t *, mp->b_rptr, tcp_t *, NULL,
__dtrace_tcp_tcph_t *, &mp->b_rptr[ip_hdr_len]);
if (IPH_HDR_VERSION(mp->b_rptr) == IPV4_VERSION) {
policy_present = ipss->ipsec_inbound_v4_policy_present;
ipha = (ipha_t *)mp->b_rptr;
ip6h = NULL;
} else {
policy_present = ipss->ipsec_inbound_v6_policy_present;
ipha = NULL;
ip6h = (ip6_t *)mp->b_rptr;
}
if (policy_present) {
mp = ipsec_check_global_policy(mp, (conn_t *)NULL, ipha, ip6h,
ira, ns);
if (mp == NULL)
return;
}
if (is_system_labeled() && !tsol_can_reply_error(mp, ira)) {
DTRACE_PROBE2(
tx__ip__log__error__nolistener__tcp,
char *, "Could not reply with RST to mp(1)",
mblk_t *, mp);
ip2dbg(("tcp_xmit_listeners_reset: not permitted to reply\n"));
freemsg(mp);
return;
}
rptr = mp->b_rptr;
tcpha = (tcpha_t *)&rptr[ip_hdr_len];
seg_seq = ntohl(tcpha->tha_seq);
seg_ack = ntohl(tcpha->tha_ack);
flags = tcpha->tha_flags;
seg_len = msgdsize(mp) - (TCP_HDR_LENGTH(tcpha) + ip_hdr_len);
if (flags & TH_RST) {
freemsg(mp);
} else if (flags & TH_ACK) {
tcp_xmit_early_reset("no tcp, reset", mp, seg_ack, 0, TH_RST,
ira, ipst, connp);
} else {
if (flags & TH_SYN) {
seg_len++;
} else {
freemsg(mp);
TCP_STAT(tcps, tcp_rst_unsent);
return;
}
tcp_xmit_early_reset("no tcp, reset/ack", mp, 0,
seg_seq + seg_len, TH_RST | TH_ACK, ira, ipst, connp);
}
}
static void
tcp_xmit_mp_aux_iss(tcp_t *tcp, conn_t *connp, tcpha_t *tcpha, mblk_t *mp,
uint_t *flags)
{
uint32_t u1;
uint8_t *wptr = mp->b_wptr;
tcp_stack_t *tcps = tcp->tcp_tcps;
boolean_t add_sack = B_FALSE;
ASSERT(tcp->tcp_state == TCPS_SYN_SENT ||
tcp->tcp_state == TCPS_SYN_RCVD ||
tcp->tcp_state == TCPS_FIN_WAIT_1);
wptr[0] = TCPOPT_MAXSEG;
wptr[1] = TCPOPT_MAXSEG_LEN;
wptr += 2;
u1 = tcp->tcp_initial_pmtu - (connp->conn_ipversion == IPV4_VERSION ?
IP_SIMPLE_HDR_LENGTH : IPV6_HDR_LEN) - TCP_MIN_HEADER_LENGTH;
U16_TO_BE16(u1, wptr);
wptr += 2;
tcpha->tha_offset_and_reserved += (1 << 4);
switch (tcp->tcp_state) {
case TCPS_SYN_SENT:
*flags = TH_SYN;
if (tcp->tcp_snd_sack_ok)
add_sack = B_TRUE;
if (tcp->tcp_snd_ts_ok) {
uint32_t llbolt = (uint32_t)LBOLT_FASTPATH;
if (add_sack) {
wptr[0] = TCPOPT_SACK_PERMITTED;
wptr[1] = TCPOPT_SACK_OK_LEN;
add_sack = B_FALSE;
} else {
wptr[0] = TCPOPT_NOP;
wptr[1] = TCPOPT_NOP;
}
wptr[2] = TCPOPT_TSTAMP;
wptr[3] = TCPOPT_TSTAMP_LEN;
wptr += 4;
U32_TO_BE32(llbolt, wptr);
wptr += 4;
ASSERT(tcp->tcp_ts_recent == 0);
U32_TO_BE32(0L, wptr);
wptr += 4;
tcpha->tha_offset_and_reserved += (3 << 4);
}
if (tcp->tcp_ecn_ok)
*flags |= (TH_ECE | TH_CWR);
break;
case TCPS_SYN_RCVD:
*flags |= TH_SYN;
if (tcps->tcps_use_smss_as_mss_opt) {
u1 = tcp->tcp_mss;
U16_TO_BE16(u1, wptr - 2);
}
if (tcp->tcp_snd_sack_ok) {
if (tcp->tcp_snd_ts_ok) {
uint8_t *tmp_wptr;
tmp_wptr = (uint8_t *)tcpha +
TCP_MIN_HEADER_LENGTH;
ASSERT(tmp_wptr[0] == TCPOPT_NOP &&
tmp_wptr[1] == TCPOPT_NOP);
tmp_wptr[0] = TCPOPT_SACK_PERMITTED;
tmp_wptr[1] = TCPOPT_SACK_OK_LEN;
} else {
add_sack = B_TRUE;
}
}
if (tcp->tcp_ecn_ok)
*flags |= TH_ECE;
break;
default:
*flags |= TH_SYN;
break;
}
if (add_sack) {
wptr[0] = TCPOPT_NOP;
wptr[1] = TCPOPT_NOP;
wptr[2] = TCPOPT_SACK_PERMITTED;
wptr[3] = TCPOPT_SACK_OK_LEN;
wptr += TCPOPT_REAL_SACK_OK_LEN;
tcpha->tha_offset_and_reserved += (1 << 4);
}
if (tcp->tcp_snd_ws_ok) {
wptr[0] = TCPOPT_NOP;
wptr[1] = TCPOPT_WSCALE;
wptr[2] = TCPOPT_WS_LEN;
wptr[3] = (uchar_t)tcp->tcp_rcv_ws;
wptr += TCPOPT_REAL_WS_LEN;
tcpha->tha_offset_and_reserved += (1 << 4);
}
mp->b_wptr = wptr;
u1 = (int)(mp->b_wptr - mp->b_rptr);
u1 += connp->conn_sum;
u1 = (u1 >> 16) + (u1 & 0xFFFF);
tcpha->tha_sum = htons(u1);
TCPS_BUMP_MIB(tcps, tcpOutControl);
}
static void
tcp_xmit_mp_aux_fss(tcp_t *tcp, ip_xmit_attr_t *ixa, uint_t *flags)
{
if (!tcp->tcp_fin_acked) {
*flags |= TH_FIN;
TCPS_BUMP_MIB(tcp->tcp_tcps, tcpOutControl);
}
if (!tcp->tcp_fin_sent) {
tcp->tcp_fin_sent = B_TRUE;
switch (tcp->tcp_state) {
case TCPS_SYN_RCVD:
tcp->tcp_state = TCPS_FIN_WAIT_1;
DTRACE_TCP6(state__change, void, NULL,
ip_xmit_attr_t *, ixa, void, NULL,
tcp_t *, tcp, void, NULL,
int32_t, TCPS_SYN_RCVD);
break;
case TCPS_ESTABLISHED:
tcp->tcp_state = TCPS_FIN_WAIT_1;
DTRACE_TCP6(state__change, void, NULL,
ip_xmit_attr_t *, ixa, void, NULL,
tcp_t *, tcp, void, NULL,
int32_t, TCPS_ESTABLISHED);
break;
case TCPS_CLOSE_WAIT:
tcp->tcp_state = TCPS_LAST_ACK;
DTRACE_TCP6(state__change, void, NULL,
ip_xmit_attr_t *, ixa, void, NULL,
tcp_t *, tcp, void, NULL,
int32_t, TCPS_CLOSE_WAIT);
break;
}
if (tcp->tcp_suna == tcp->tcp_snxt)
TCP_TIMER_RESTART(tcp, tcp->tcp_rto);
tcp->tcp_snxt = tcp->tcp_fss + 1;
}
}
mblk_t *
tcp_xmit_mp(tcp_t *tcp, mblk_t *mp, int32_t max_to_send, int32_t *offset,
mblk_t **end_mp, uint32_t seq, boolean_t sendall, uint32_t *seg_len,
boolean_t rexmit)
{
int data_length;
int32_t off = 0;
uint_t flags;
mblk_t *mp1;
mblk_t *mp2;
uchar_t *rptr;
tcpha_t *tcpha;
int32_t num_sack_blk = 0;
int32_t sack_opt_len = 0, opt_len = 0;
tcp_stack_t *tcps = tcp->tcp_tcps;
conn_t *connp = tcp->tcp_connp;
ip_xmit_attr_t *ixa = connp->conn_ixa;
mp1 = allocb(connp->conn_ht_iphc_allocated + tcps->tcps_wroff_xtra,
BPRI_MED);
if (mp1 == NULL)
return (NULL);
data_length = 0;
if (tcp->tcp_snd_sack_ok && tcp->tcp_num_sack_blk > 0) {
num_sack_blk = MIN(tcp->tcp_max_sack_blk,
tcp->tcp_num_sack_blk);
sack_opt_len = num_sack_blk * sizeof (sack_blk_t) +
TCPOPT_NOP_LEN * 2 + TCPOPT_HEADER_LEN;
opt_len += sack_opt_len;
}
if (tcp->tcp_md5sig)
opt_len += TCPOPT_REAL_MD5_LEN;
if (max_to_send + opt_len > tcp->tcp_mss)
max_to_send -= opt_len;
if (offset != NULL) {
off = *offset;
*end_mp = NULL;
}
for (mp2 = mp1; mp && data_length != max_to_send; mp = mp->b_cont) {
if (mp2 != mp1 && !sendall &&
data_length + (int)(mp->b_wptr - mp->b_rptr) >
max_to_send)
break;
mp2->b_cont = dupb(mp);
mp2 = mp2->b_cont;
if (!mp2) {
freemsg(mp1);
return (NULL);
}
mp2->b_rptr += off;
ASSERT((uintptr_t)(mp2->b_wptr - mp2->b_rptr) <=
(uintptr_t)INT_MAX);
data_length += (int)(mp2->b_wptr - mp2->b_rptr);
if (data_length > max_to_send) {
mp2->b_wptr -= data_length - max_to_send;
data_length = max_to_send;
off = mp2->b_wptr - mp->b_rptr;
break;
} else {
off = 0;
}
}
if (offset != NULL) {
*offset = off;
*end_mp = mp;
}
if (seg_len != NULL) {
*seg_len = data_length;
}
tcp->tcp_tcpha->tha_win = htons(tcp->tcp_rwnd >> tcp->tcp_rcv_ws);
rptr = mp1->b_rptr + tcps->tcps_wroff_xtra;
mp1->b_rptr = rptr;
mp1->b_wptr = rptr + connp->conn_ht_iphc_len + sack_opt_len;
bcopy(connp->conn_ht_iphc, rptr, connp->conn_ht_iphc_len);
tcpha = (tcpha_t *)&rptr[ixa->ixa_ip_hdr_length];
tcpha->tha_seq = htonl(seq);
if (data_length != 0 && (tcp->tcp_unsent == 0 ||
tcp->tcp_unsent == data_length)) {
flags = TH_ACK | TH_PUSH;
} else {
flags = TH_ACK;
}
if (tcp->tcp_ecn_ok) {
if (tcp->tcp_ecn_echo_on)
flags |= TH_ECE;
if (data_length > 0 && !rexmit) {
TCP_SET_ECT(tcp, rptr);
if (tcp->tcp_cwr && !tcp->tcp_ecn_cwr_sent) {
flags |= TH_CWR;
tcp->tcp_ecn_cwr_sent = B_TRUE;
}
}
}
if (tcp->tcp_valid_bits) {
uint32_t u1;
if ((tcp->tcp_valid_bits & TCP_ISS_VALID) &&
seq == tcp->tcp_iss) {
tcp_xmit_mp_aux_iss(tcp, connp, tcpha, mp1, &flags);
} else if ((tcp->tcp_valid_bits & TCP_FSS_VALID) &&
(seq + data_length) == tcp->tcp_fss) {
tcp_xmit_mp_aux_fss(tcp, ixa, &flags);
}
u1 = tcp->tcp_urg - seq + TCP_OLD_URP_INTERPRETATION;
if ((tcp->tcp_valid_bits & TCP_URG_VALID) && u1 != 0 &&
u1 < (uint32_t)(64 * 1024)) {
flags |= TH_URG;
TCPS_BUMP_MIB(tcps, tcpOutUrg);
tcpha->tha_urp = htons(u1);
}
}
tcpha->tha_flags = (uchar_t)flags;
tcp->tcp_rack = tcp->tcp_rnxt;
tcp->tcp_rack_cnt = 0;
if (tcp->tcp_snd_ts_ok) {
if (tcp->tcp_state != TCPS_SYN_SENT) {
uint32_t llbolt = (uint32_t)LBOLT_FASTPATH;
U32_TO_BE32(llbolt,
(char *)tcpha + TCP_MIN_HEADER_LENGTH + 4);
U32_TO_BE32(tcp->tcp_ts_recent,
(char *)tcpha + TCP_MIN_HEADER_LENGTH + 8);
}
}
if (num_sack_blk > 0) {
uchar_t *wptr = (uchar_t *)tcpha + connp->conn_ht_ulp_len;
sack_blk_t *tmp;
int32_t i;
wptr[0] = TCPOPT_NOP;
wptr[1] = TCPOPT_NOP;
wptr[2] = TCPOPT_SACK;
wptr[3] = TCPOPT_HEADER_LEN + num_sack_blk *
sizeof (sack_blk_t);
wptr += TCPOPT_REAL_SACK_LEN;
tmp = tcp->tcp_sack_list;
for (i = 0; i < num_sack_blk; i++) {
U32_TO_BE32(tmp[i].begin, wptr);
wptr += sizeof (tcp_seq);
U32_TO_BE32(tmp[i].end, wptr);
wptr += sizeof (tcp_seq);
}
tcpha->tha_offset_and_reserved += ((num_sack_blk * 2 + 1) << 4);
}
if (tcp->tcp_md5sig) {
uint8_t digest[MD5_DIGEST_LENGTH];
int tcplen = data_length + (int)(mp1->b_wptr - rptr) +
TCPOPT_REAL_MD5_LEN - ixa->ixa_ip_hdr_length;
if (tcpsig_signature(mp1->b_cont, tcp, tcpha, tcplen, digest,
false)) {
uint8_t *wptr = mp1->b_wptr;
wptr[0] = TCPOPT_NOP;
wptr[1] = TCPOPT_NOP;
wptr[2] = TCPOPT_MD5;
wptr[3] = TCPOPT_MD5_LEN;
bcopy(digest, &wptr[4], sizeof (digest));
tcpha->tha_offset_and_reserved += (5 << 4);
mp1->b_wptr += TCPOPT_REAL_MD5_LEN;
} else {
freemsg(mp1);
return (NULL);
}
}
ASSERT((uintptr_t)(mp1->b_wptr - rptr) <= (uintptr_t)INT_MAX);
data_length += (int)(mp1->b_wptr - rptr);
ixa->ixa_pktlen = data_length;
if (ixa->ixa_flags & IXAF_IS_IPV4) {
((ipha_t *)rptr)->ipha_length = htons(data_length);
} else {
ip6_t *ip6 = (ip6_t *)rptr;
ip6->ip6_plen = htons(data_length - IPV6_HDR_LEN);
}
data_length -= ixa->ixa_ip_hdr_length;
data_length += connp->conn_sum;
data_length = (data_length >> 16) + (data_length & 0xFFFF);
tcpha->tha_sum = htons(data_length);
if (tcp->tcp_ip_forward_progress) {
tcp->tcp_ip_forward_progress = B_FALSE;
connp->conn_ixa->ixa_flags |= IXAF_REACH_CONF;
} else {
connp->conn_ixa->ixa_flags &= ~IXAF_REACH_CONF;
}
return (mp1);
}
static boolean_t
tcp_send_rst_chk(tcp_stack_t *tcps)
{
int64_t now;
if (tcps->tcps_rst_sent_rate_enabled != 0) {
now = ddi_get_lbolt64();
if (TICK_TO_MSEC(now - tcps->tcps_last_rst_intrvl) >
1*SECONDS) {
tcps->tcps_last_rst_intrvl = now;
tcps->tcps_rst_cnt = 1;
} else if (++tcps->tcps_rst_cnt > tcps->tcps_rst_sent_rate) {
return (B_FALSE);
}
}
return (B_TRUE);
}
void
tcp_sack_rexmit(tcp_t *tcp, uint_t *flags)
{
notsack_blk_t *notsack_blk;
int32_t usable_swnd;
int32_t mss;
uint32_t seg_len;
mblk_t *xmit_mp;
tcp_stack_t *tcps = tcp->tcp_tcps;
ASSERT(tcp->tcp_notsack_list != NULL);
ASSERT(tcp->tcp_rexmit == B_FALSE);
if (tcp->tcp_notsack_list == NULL) {
return;
}
notsack_blk = tcp->tcp_notsack_list;
mss = tcp->tcp_mss;
usable_swnd = tcp->tcp_cwnd_ssthresh - tcp->tcp_pipe;
if (usable_swnd <= 0) {
usable_swnd = mss;
}
tcp->tcp_csuna = tcp->tcp_snxt;
notsack_blk = tcp->tcp_notsack_list;
while (usable_swnd > 0) {
mblk_t *snxt_mp, *tmp_mp;
tcp_seq begin = tcp->tcp_sack_snxt;
tcp_seq end;
int32_t off;
for (; notsack_blk != NULL; notsack_blk = notsack_blk->next) {
if (SEQ_GT(notsack_blk->end, begin) &&
(notsack_blk->sack_cnt >=
tcps->tcps_dupack_fast_retransmit)) {
end = notsack_blk->end;
if (SEQ_LT(begin, notsack_blk->begin)) {
begin = notsack_blk->begin;
}
break;
}
}
if (notsack_blk == NULL) {
usable_swnd = tcp->tcp_cwnd_ssthresh - tcp->tcp_pipe;
if (usable_swnd <= 0 || tcp->tcp_unsent == 0) {
tcp->tcp_cwnd = tcp->tcp_snxt - tcp->tcp_suna;
ASSERT(tcp->tcp_cwnd > 0);
return;
} else {
usable_swnd = usable_swnd / mss;
tcp->tcp_cwnd = tcp->tcp_snxt - tcp->tcp_suna +
MAX((uint32_t)usable_swnd * (uint32_t)mss,
(uint32_t)mss);
*flags |= TH_XMIT_NEEDED;
return;
}
}
seg_len = end - begin;
if (seg_len > mss)
seg_len = mss;
snxt_mp = tcp_get_seg_mp(tcp, begin, &off);
ASSERT(snxt_mp != NULL);
if (snxt_mp == NULL) {
return;
}
xmit_mp = tcp_xmit_mp(tcp, snxt_mp, seg_len, &off,
&tmp_mp, begin, B_TRUE, &seg_len, B_TRUE);
if (xmit_mp == NULL)
return;
usable_swnd -= seg_len;
tcp->tcp_pipe += seg_len;
tcp->tcp_sack_snxt = begin + seg_len;
tcp_send_data(tcp, xmit_mp);
snxt_mp->b_prev = (mblk_t *)(intptr_t)gethrtime();
TCPS_BUMP_MIB(tcps, tcpRetransSegs);
TCPS_UPDATE_MIB(tcps, tcpRetransBytes, seg_len);
TCPS_BUMP_MIB(tcps, tcpOutSackRetransSegs);
tcp->tcp_cs.tcp_out_retrans_segs++;
tcp->tcp_cs.tcp_out_retrans_bytes += seg_len;
if (SEQ_GT(tcp->tcp_sack_snxt, tcp->tcp_rexmit_max)) {
tcp->tcp_rexmit_max = tcp->tcp_sack_snxt;
}
}
}
void
tcp_ss_rexmit(tcp_t *tcp)
{
uint32_t snxt;
uint32_t smax;
int32_t win;
int32_t mss;
int32_t off;
mblk_t *snxt_mp;
tcp_stack_t *tcps = tcp->tcp_tcps;
if (SEQ_LT(tcp->tcp_rexmit_nxt, tcp->tcp_rexmit_max)) {
smax = tcp->tcp_rexmit_max;
snxt = tcp->tcp_rexmit_nxt;
if (SEQ_LT(snxt, tcp->tcp_suna)) {
snxt = tcp->tcp_suna;
}
win = MIN(tcp->tcp_cwnd, tcp->tcp_swnd);
win -= snxt - tcp->tcp_suna;
mss = tcp->tcp_mss;
snxt_mp = tcp_get_seg_mp(tcp, snxt, &off);
while (SEQ_LT(snxt, smax) && (win > 0) && (snxt_mp != NULL)) {
mblk_t *xmit_mp;
mblk_t *old_snxt_mp = snxt_mp;
uint32_t cnt = mss;
if (win < cnt) {
cnt = win;
}
if (SEQ_GT(snxt + cnt, smax)) {
cnt = smax - snxt;
}
xmit_mp = tcp_xmit_mp(tcp, snxt_mp, cnt, &off,
&snxt_mp, snxt, B_TRUE, &cnt, B_TRUE);
if (xmit_mp == NULL)
return;
tcp_send_data(tcp, xmit_mp);
snxt += cnt;
win -= cnt;
old_snxt_mp->b_prev = (mblk_t *)(intptr_t)gethrtime();
TCPS_BUMP_MIB(tcps, tcpRetransSegs);
TCPS_UPDATE_MIB(tcps, tcpRetransBytes, cnt);
tcp->tcp_cs.tcp_out_retrans_segs++;
tcp->tcp_cs.tcp_out_retrans_bytes += cnt;
tcp->tcp_rexmit_nxt = snxt;
}
if (SEQ_LT(snxt, smax) || win == 0) {
return;
}
}
if (tcp->tcp_unsent) {
tcp_wput_data(tcp, NULL, B_FALSE);
}
}
void
tcp_rexmit_after_error(tcp_t *tcp)
{
if (!SEQ_LT(tcp->tcp_suna, tcp->tcp_snxt) ||
(tcp->tcp_xmit_head == NULL))
return;
if ((tcp->tcp_valid_bits & TCP_FSS_VALID) && (tcp->tcp_unsent == 0))
tcp->tcp_rexmit_max = tcp->tcp_fss;
else
tcp->tcp_rexmit_max = tcp->tcp_snxt;
tcp->tcp_rexmit_nxt = tcp->tcp_suna;
tcp->tcp_rexmit = B_TRUE;
tcp->tcp_dupack_cnt = 0;
tcp_ss_rexmit(tcp);
}
static mblk_t *
tcp_get_seg_mp(tcp_t *tcp, uint32_t seq, int32_t *off)
{
int32_t cnt;
mblk_t *mp;
if (SEQ_LT(seq, tcp->tcp_suna) || SEQ_GT(seq, tcp->tcp_snxt))
return (NULL);
cnt = seq - tcp->tcp_suna;
mp = tcp->tcp_xmit_head;
while (cnt > 0 && mp != NULL) {
cnt -= mp->b_wptr - mp->b_rptr;
if (cnt <= 0) {
cnt += mp->b_wptr - mp->b_rptr;
break;
}
mp = mp->b_cont;
}
ASSERT(mp != NULL);
*off = cnt;
return (mp);
}
void
tcp_update_xmit_tail(tcp_t *tcp, uint32_t snxt)
{
mblk_t *xmit_tail;
int32_t offset;
tcp->tcp_snxt = snxt;
xmit_tail = tcp_get_seg_mp(tcp, snxt, &offset);
ASSERT(xmit_tail != NULL);
tcp->tcp_xmit_tail = xmit_tail;
tcp->tcp_xmit_tail_unsent = xmit_tail->b_wptr -
xmit_tail->b_rptr - offset;
}
static void
tcp_process_shrunk_swnd(tcp_t *tcp, uint32_t shrunk_count)
{
uint32_t snxt = tcp->tcp_snxt;
ASSERT(shrunk_count > 0);
if (!tcp->tcp_is_wnd_shrnk) {
tcp->tcp_snxt_shrunk = snxt;
tcp->tcp_is_wnd_shrnk = B_TRUE;
} else if (SEQ_GT(snxt, tcp->tcp_snxt_shrunk)) {
tcp->tcp_snxt_shrunk = snxt;
}
snxt -= shrunk_count;
tcp_update_xmit_tail(tcp, snxt);
tcp->tcp_unsent += shrunk_count;
TCP_NOTSACK_REMOVE_ALL(tcp->tcp_notsack_list, tcp);
if (tcp->tcp_suna == tcp->tcp_snxt && tcp->tcp_swnd == 0)
TCP_TIMER_RESTART(tcp, tcp->tcp_rto);
}
static void
tcp_fill_header(tcp_t *tcp, uchar_t *rptr, int num_sack_blk)
{
tcpha_t *tcp_tmpl, *tcpha;
uint32_t *dst, *src;
int hdrlen;
conn_t *connp = tcp->tcp_connp;
ASSERT(OK_32PTR(rptr));
tcp_tmpl = tcp->tcp_tcpha;
tcpha = (tcpha_t *)(rptr + connp->conn_ixa->ixa_ip_hdr_length);
dst = (uint32_t *)rptr;
src = (uint32_t *)connp->conn_ht_iphc;
hdrlen = connp->conn_ht_iphc_len;
if (tcp->tcp_snd_ts_ok) {
U32_TO_BE32(LBOLT_FASTPATH,
(char *)tcp_tmpl + TCP_MIN_HEADER_LENGTH + 4);
U32_TO_BE32(tcp->tcp_ts_recent,
(char *)tcp_tmpl + TCP_MIN_HEADER_LENGTH + 8);
} else {
ASSERT(connp->conn_ht_ulp_len == TCP_MIN_HEADER_LENGTH);
}
dst[0] = src[0];
dst[1] = src[1];
dst[2] = src[2];
dst[3] = src[3];
dst[4] = src[4];
dst[5] = src[5];
dst[6] = src[6];
dst[7] = src[7];
dst[8] = src[8];
dst[9] = src[9];
if (hdrlen -= 40) {
hdrlen >>= 2;
dst += 10;
src += 10;
do {
*dst++ = *src++;
} while (--hdrlen);
}
if (tcp->tcp_ecn_ok && !tcp->tcp_zero_win_probe) {
TCP_SET_ECT(tcp, rptr);
if (tcp->tcp_ecn_echo_on)
tcpha->tha_flags |= TH_ECE;
if (tcp->tcp_cwr && !tcp->tcp_ecn_cwr_sent) {
tcpha->tha_flags |= TH_CWR;
tcp->tcp_ecn_cwr_sent = B_TRUE;
}
}
if (num_sack_blk > 0) {
uchar_t *wptr = rptr + connp->conn_ht_iphc_len;
sack_blk_t *tmp;
int32_t i;
wptr[0] = TCPOPT_NOP;
wptr[1] = TCPOPT_NOP;
wptr[2] = TCPOPT_SACK;
wptr[3] = TCPOPT_HEADER_LEN + num_sack_blk *
sizeof (sack_blk_t);
wptr += TCPOPT_REAL_SACK_LEN;
tmp = tcp->tcp_sack_list;
for (i = 0; i < num_sack_blk; i++) {
U32_TO_BE32(tmp[i].begin, wptr);
wptr += sizeof (tcp_seq);
U32_TO_BE32(tmp[i].end, wptr);
wptr += sizeof (tcp_seq);
}
tcpha->tha_offset_and_reserved +=
((num_sack_blk * 2 + 1) << 4);
}
}