#include "opt_inet.h"
#include "opt_inet6.h"
#include "opt_tcpdebug.h"
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/sysctl.h>
#include <sys/mbuf.h>
#include <sys/domain.h>
#include <sys/protosw.h>
#include <sys/socket.h>
#include <sys/socketvar.h>
#include <sys/in_cksum.h>
#include <sys/thread.h>
#include <sys/globaldata.h>
#include <net/if.h>
#include <net/if_var.h>
#include <net/route.h>
#include <net/netmsg2.h>
#include <net/netisr2.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <netinet/in_pcb.h>
#include <netinet/ip_var.h>
#include <netinet6/in6_pcb.h>
#include <netinet/ip6.h>
#include <netinet6/ip6_var.h>
#include <netinet/tcp.h>
#define TCPOUTFLAGS
#include <netinet/tcp_fsm.h>
#include <netinet/tcp_seq.h>
#include <netinet/tcp_timer.h>
#include <netinet/tcp_timer2.h>
#include <netinet/tcp_var.h>
#include <netinet/tcpip.h>
#ifdef TCPDEBUG
#include <netinet/tcp_debug.h>
#endif
#ifdef notyet
extern struct mbuf *m_copypack();
#endif
int path_mtu_discovery = 1;
SYSCTL_INT(_net_inet_tcp, OID_AUTO, path_mtu_discovery, CTLFLAG_RW,
&path_mtu_discovery, 1, "Enable Path MTU Discovery");
static int avoid_pure_win_update = 1;
SYSCTL_INT(_net_inet_tcp, OID_AUTO, avoid_pure_win_update, CTLFLAG_RW,
&avoid_pure_win_update, 1, "Avoid pure window updates when possible");
int tcp_do_autosndbuf = 1;
SYSCTL_INT(_net_inet_tcp, OID_AUTO, sendbuf_auto, CTLFLAG_RW,
&tcp_do_autosndbuf, 0, "Enable automatic send buffer sizing");
int tcp_autosndbuf_inc = 8*1024;
SYSCTL_INT(_net_inet_tcp, OID_AUTO, sendbuf_inc, CTLFLAG_RW,
&tcp_autosndbuf_inc, 0, "Incrementor step size of automatic send buffer");
int tcp_autosndbuf_min = 32768;
SYSCTL_INT(_net_inet_tcp, OID_AUTO, sendbuf_min, CTLFLAG_RW,
&tcp_autosndbuf_min, 0, "Min size of automatic send buffer");
int tcp_autosndbuf_max = 2*1024*1024;
SYSCTL_INT(_net_inet_tcp, OID_AUTO, sendbuf_max, CTLFLAG_RW,
&tcp_autosndbuf_max, 0, "Max size of automatic send buffer");
int tcp_prio_synack = 1;
SYSCTL_INT(_net_inet_tcp, OID_AUTO, prio_synack, CTLFLAG_RW,
&tcp_prio_synack, 0, "Prioritize SYN, SYN|ACK and pure ACK");
static int tcp_idle_cwv = 1;
SYSCTL_INT(_net_inet_tcp, OID_AUTO, idle_cwv, CTLFLAG_RW,
&tcp_idle_cwv, 0,
"Congestion window validation after idle period (part of RFC2861)");
static int tcp_idle_restart = 1;
SYSCTL_INT(_net_inet_tcp, OID_AUTO, idle_restart, CTLFLAG_RW,
&tcp_idle_restart, 0, "Reset congestion window after idle period");
static int tcp_do_tso = 1;
SYSCTL_INT(_net_inet_tcp, OID_AUTO, tso, CTLFLAG_RW,
&tcp_do_tso, 0, "Enable TCP Segmentation Offload (TSO)");
static int tcp_fairsend = 4;
SYSCTL_INT(_net_inet_tcp, OID_AUTO, fairsend, CTLFLAG_RW,
&tcp_fairsend, 0,
"Amount of segments sent before yield to other senders or receivers");
static void tcp_idle_cwnd_validate(struct tcpcb *);
static int tcp_tso_getsize(struct tcpcb *tp, u_int *segsz, u_int *hlen);
static void tcp_output_sched(struct tcpcb *tp);
int
tcp_output(struct tcpcb *tp)
{
struct inpcb * const inp = tp->t_inpcb;
struct socket *so = inp->inp_socket;
long len, recvwin, sendwin;
int nsacked = 0;
int off, flags, error = 0;
#ifdef TCP_SIGNATURE
int sigoff = 0;
#endif
struct mbuf *m;
struct ip *ip;
struct tcphdr *th;
u_char opt[TCP_MAXOLEN];
unsigned int ipoptlen, optlen, hdrlen;
int idle;
boolean_t sendalot;
struct ip6_hdr *ip6;
#ifdef INET6
const boolean_t isipv6 = INP_ISIPV6(inp);
#else
const boolean_t isipv6 = FALSE;
#endif
boolean_t can_tso = FALSE, use_tso;
boolean_t report_sack, idle_cwv = FALSE;
u_int segsz, tso_hlen, tso_lenmax = 0;
int segcnt = 0;
boolean_t need_sched = FALSE;
KKASSERT(so->so_port == &curthread->td_msgport);
if (tp->snd_max == tp->snd_una &&
(ticks - tp->snd_last) >= tp->t_rxtcur && tcp_idle_restart)
idle_cwv = TRUE;
idle = (tp->t_flags & TF_LASTIDLE) || (tp->snd_max == tp->snd_una);
if (idle && (tp->t_flags & TF_MORETOCOME))
tp->t_flags |= TF_LASTIDLE;
else
tp->t_flags &= ~TF_LASTIDLE;
if (TCP_DO_SACK(tp) && tp->snd_nxt != tp->snd_max &&
!IN_FASTRECOVERY(tp))
nsacked = tcp_sack_bytes_below(&tp->scb, tp->snd_nxt);
if (tcp_do_tso
#ifdef TCP_SIGNATURE
&& (tp->t_flags & TF_SIGNATURE) == 0
#endif
) {
if (!isipv6) {
struct rtentry *rt = inp->inp_route.ro_rt;
if (rt != NULL && (rt->rt_flags & RTF_UP) &&
(rt->rt_ifp->if_hwassist & CSUM_TSO)) {
can_tso = TRUE;
tso_lenmax = rt->rt_ifp->if_tsolen;
}
}
}
again:
m = NULL;
ip = NULL;
th = NULL;
ip6 = NULL;
if ((tp->t_flags & (TF_SACK_PERMITTED | TF_NOOPT)) ==
TF_SACK_PERMITTED &&
(!TAILQ_EMPTY(&tp->t_segq) ||
tp->reportblk.rblk_start != tp->reportblk.rblk_end))
report_sack = TRUE;
else
report_sack = FALSE;
if (TCP_DO_SACK(tp) && tp->snd_nxt != tp->snd_max &&
!IN_FASTRECOVERY(tp)) {
tcp_seq old_snd_nxt = tp->snd_nxt;
tcp_sack_skip_sacked(&tp->scb, &tp->snd_nxt);
nsacked += tp->snd_nxt - old_snd_nxt;
}
sendalot = FALSE;
off = tp->snd_nxt - tp->snd_una;
sendwin = min(tp->snd_wnd, tp->snd_cwnd + nsacked);
sendwin = min(sendwin, tp->snd_bwnd);
flags = tcp_outflags[tp->t_state];
if (tp->t_flags & TF_NEEDFIN)
flags |= TH_FIN;
if (tp->t_flags & TF_NEEDSYN)
flags |= TH_SYN;
if (tp->t_flags & TF_FORCE) {
if (sendwin == 0) {
if (off < so->so_snd.ssb_cc)
flags &= ~TH_FIN;
sendwin = 1;
} else {
tcp_callout_stop(tp, tp->tt_persist);
tp->t_rxtshift = 0;
}
}
len = (long)ulmin(so->so_snd.ssb_cc, sendwin) - off;
if ((flags & TH_SYN) && SEQ_GT(tp->snd_nxt, tp->snd_una)) {
flags &= ~TH_SYN;
off--, len++;
if (len > 0 && tp->t_state == TCPS_SYN_SENT) {
tp->t_flags &= ~(TF_ACKNOW | TF_XMITNOW);
return 0;
}
}
if (flags & TH_SYN) {
len = 0;
flags &= ~TH_FIN;
}
if (len < 0) {
len = 0;
if (sendwin == 0 && tp->t_state != TCPS_SYN_RECEIVED) {
tcp_callout_stop(tp, tp->tt_rexmt);
tp->t_rxtshift = 0;
tp->snd_nxt = tp->snd_una;
if (!tcp_callout_active(tp, tp->tt_persist))
tcp_setpersist(tp);
}
}
KASSERT(len >= 0, ("%s: len < 0", __func__));
if (tcp_do_autosndbuf && (so->so_snd.ssb_flags & SSB_AUTOSIZE)) {
const int asbinc = tcp_autosndbuf_inc;
const int hiwat = so->so_snd.ssb_hiwat;
const int lowat = so->so_snd.ssb_lowat;
u_long newsize;
if ((tp->snd_wnd / 4 * 5) >= hiwat &&
so->so_snd.ssb_cc >= (hiwat / 8 * 7) &&
hiwat < tp->snd_bwnd + hiwat / 10 &&
hiwat + asbinc < tcp_autosndbuf_max &&
hiwat < (TCP_MAXWIN << tp->snd_scale) &&
sendwin >= (so->so_snd.ssb_cc -
(tp->snd_nxt - tp->snd_una))) {
newsize = ulmin(hiwat + asbinc, tcp_autosndbuf_max);
if (!ssb_reserve(&so->so_snd, newsize, so, NULL))
atomic_clear_int(&so->so_snd.ssb_flags, SSB_AUTOSIZE);
#if 0
if (newsize >= (TCP_MAXWIN << tp->snd_scale))
atomic_clear_int(&so->so_snd.ssb_flags, SSB_AUTOSIZE);
#endif
} else if ((long)tp->snd_bwnd <
(long)(hiwat * 3 / 4 - lowat - asbinc) &&
hiwat > tp->t_maxseg * 2 + asbinc &&
hiwat + asbinc >= tcp_autosndbuf_min &&
tcp_do_autosndbuf == 1) {
newsize = ulmax(hiwat - asbinc, tp->t_maxseg * 2);
ssb_reserve(&so->so_snd, newsize, so, NULL);
}
}
use_tso = can_tso;
if (report_sack || idle_cwv || (flags & (TH_RST | TH_SYN)))
use_tso = FALSE;
if (use_tso) {
tcp_seq ugr_nxt = tp->snd_nxt;
if ((flags & TH_FIN) && (tp->t_flags & TF_SENTFIN) &&
tp->snd_nxt == tp->snd_max)
--ugr_nxt;
if (SEQ_GT(tp->snd_up, ugr_nxt))
use_tso = FALSE;
}
if (use_tso) {
error = tcp_tso_getsize(tp, &segsz, &tso_hlen);
if (error)
use_tso = FALSE;
}
if (!use_tso) {
segsz = tp->t_maxseg;
tso_hlen = 0;
}
if (len > segsz) {
if (!use_tso) {
len = segsz;
++segcnt;
} else {
int nsegs;
if (__predict_false(tso_lenmax < segsz))
tso_lenmax = segsz << 1;
len = min(len, tso_lenmax);
nsegs = min(len, (IP_MAXPACKET - tso_hlen)) / segsz;
KKASSERT(nsegs > 0);
len = nsegs * segsz;
if (len <= segsz) {
use_tso = FALSE;
++segcnt;
} else {
segcnt += nsegs;
}
}
sendalot = TRUE;
} else {
use_tso = FALSE;
if (len > 0)
++segcnt;
}
if (SEQ_LT(tp->snd_nxt + len, tp->snd_una + so->so_snd.ssb_cc))
flags &= ~TH_FIN;
recvwin = ssb_space(&so->so_rcv);
if (len) {
if (len >= segsz)
goto send;
if (!(tp->t_flags & TF_MORETOCOME) &&
(idle || (tp->t_flags & TF_NODELAY)) &&
len + off >= so->so_snd.ssb_cc &&
!(tp->t_flags & TF_NOPUSH)) {
goto send;
}
if (tp->t_flags & TF_FORCE)
goto send;
if (len >= tp->max_sndwnd / 2 && tp->max_sndwnd > 0)
goto send;
if (SEQ_LT(tp->snd_nxt, tp->snd_max))
goto send;
if (tp->t_flags & TF_XMITNOW)
goto send;
}
if (recvwin > 0) {
long adv = min(recvwin, (long)TCP_MAXWIN << tp->rcv_scale) -
(tp->rcv_adv - tp->rcv_nxt);
long hiwat;
if (avoid_pure_win_update == 0 ||
(tp->t_flags & TF_RXRESIZED)) {
if (adv >= (long) (2 * segsz)) {
goto send;
}
}
hiwat = (long)(TCP_MAXWIN << tp->rcv_scale);
if (hiwat > (long)so->so_rcv.ssb_hiwat)
hiwat = (long)so->so_rcv.ssb_hiwat;
if (adv >= hiwat / 2)
goto send;
}
if (tp->t_flags & TF_ACKNOW)
goto send;
if ((flags & TH_RST) ||
((flags & TH_SYN) && !(tp->t_flags & TF_NEEDSYN)))
goto send;
if (SEQ_GT(tp->snd_up, tp->snd_una))
goto send;
if ((flags & TH_FIN) &&
(!(tp->t_flags & TF_SENTFIN) || tp->snd_nxt == tp->snd_una))
goto send;
if (so->so_snd.ssb_cc > 0 &&
tp->t_state != TCPS_SYN_RECEIVED &&
!tcp_callout_active(tp, tp->tt_rexmt) &&
!tcp_callout_active(tp, tp->tt_persist)) {
tp->t_rxtshift = 0;
tcp_setpersist(tp);
}
tp->t_flags &= ~TF_XMITNOW;
return (0);
send:
if (need_sched && len > 0) {
tcp_output_sched(tp);
return 0;
}
optlen = 0;
if (isipv6)
hdrlen = sizeof(struct ip6_hdr) + sizeof(struct tcphdr);
else
hdrlen = sizeof(struct tcpiphdr);
if (flags & TH_SYN) {
tp->snd_nxt = tp->iss;
if (!(tp->t_flags & TF_NOOPT)) {
u_short mss;
opt[0] = TCPOPT_MAXSEG;
opt[1] = TCPOLEN_MAXSEG;
mss = htons((u_short) tcp_mssopt(tp));
memcpy(opt + 2, &mss, sizeof mss);
optlen = TCPOLEN_MAXSEG;
if ((tp->t_flags & TF_REQ_SCALE) &&
(!(flags & TH_ACK) ||
(tp->t_flags & TF_RCVD_SCALE))) {
*((u_int32_t *)(opt + optlen)) = htonl(
TCPOPT_NOP << 24 |
TCPOPT_WINDOW << 16 |
TCPOLEN_WINDOW << 8 |
tp->request_r_scale);
optlen += 4;
}
if ((tcp_do_sack && !(flags & TH_ACK)) ||
tp->t_flags & TF_SACK_PERMITTED) {
uint32_t *lp = (uint32_t *)(opt + optlen);
*lp = htonl(TCPOPT_SACK_PERMITTED_ALIGNED);
optlen += TCPOLEN_SACK_PERMITTED_ALIGNED;
}
}
}
if ((tp->t_flags & (TF_REQ_TSTMP | TF_NOOPT)) == TF_REQ_TSTMP &&
!(flags & TH_RST) &&
(!(flags & TH_ACK) || (tp->t_flags & TF_RCVD_TSTMP))) {
u_int32_t *lp = (u_int32_t *)(opt + optlen);
*lp++ = htonl(TCPOPT_TSTAMP_HDR);
*lp++ = htonl(ticks);
*lp = htonl(tp->ts_recent);
optlen += TCPOLEN_TSTAMP_APPA;
}
if (tp->rfbuf_ts == 0 && (so->so_rcv.ssb_flags & SSB_AUTOSIZE))
tp->rfbuf_ts = ticks;
if (report_sack)
tcp_sack_fill_report(tp, opt, &optlen);
#ifdef TCP_SIGNATURE
if (tp->t_flags & TF_SIGNATURE) {
int i;
u_char *bp;
bp = (u_char *)opt + optlen;
*bp++ = TCPOPT_SIGNATURE;
*bp++ = TCPOLEN_SIGNATURE;
sigoff = optlen + 2;
for (i = 0; i < TCP_SIGLEN; i++)
*bp++ = 0;
optlen += TCPOLEN_SIGNATURE;
*bp++ = TCPOPT_NOP;
*bp++ = TCPOPT_EOL;
optlen += 2;
}
#endif
KASSERT(optlen <= TCP_MAXOLEN, ("too many TCP options"));
hdrlen += optlen;
if (isipv6) {
ipoptlen = ip6_optlen(inp);
} else {
if (inp->inp_options) {
ipoptlen = inp->inp_options->m_len -
offsetof(struct ipoption, ipopt_list);
} else {
ipoptlen = 0;
}
}
if (use_tso) {
KASSERT(len >= (2 * segsz) && (len % segsz == 0),
("invalid TSO len %ld, segsz %u", len, segsz));
} else {
KASSERT(len <= segsz,
("invalid len %ld, segsz %u", len, segsz));
if (len + optlen + ipoptlen > tp->t_maxopd) {
if (tp->t_maxopd <= optlen + ipoptlen) {
static time_t last_optlen_report;
if (last_optlen_report != time_uptime) {
last_optlen_report = time_uptime;
kprintf("tcpcb %p: MSS (%d) too "
"small to hold options!\n",
tp, tp->t_maxopd);
}
error = EHOSTUNREACH;
goto out;
} else {
flags &= ~TH_FIN;
len = tp->t_maxopd - optlen - ipoptlen;
sendalot = TRUE;
}
}
}
#ifdef INET6
KASSERT(max_linkhdr + hdrlen <= MCLBYTES, ("tcphdr too big"));
#else
KASSERT(max_linkhdr + hdrlen <= MHLEN, ("tcphdr too big"));
#endif
if (len) {
if ((tp->t_flags & TF_FORCE) && len == 1)
tcpstat.tcps_sndprobe++;
else if (SEQ_LT(tp->snd_nxt, tp->snd_max)) {
if (tp->snd_nxt == tp->snd_una)
tp->snd_max_rexmt = tp->snd_max;
if (nsacked) {
tcpstat.tcps_sndsackrtopack++;
tcpstat.tcps_sndsackrtobyte += len;
}
tcpstat.tcps_sndrexmitpack++;
tcpstat.tcps_sndrexmitbyte += len;
} else {
tcpstat.tcps_sndpack++;
tcpstat.tcps_sndbyte += len;
}
if (idle_cwv) {
idle_cwv = FALSE;
tcp_idle_cwnd_validate(tp);
}
tp->snd_last = ticks;
#ifdef notyet
if ((m = m_copypack(so->so_snd.ssb_mb, off, (int)len,
max_linkhdr + hdrlen)) == NULL) {
error = ENOBUFS;
goto after_th;
}
m->m_len += hdrlen;
m->m_data -= hdrlen;
#else
#ifndef INET6
m = m_gethdr(M_NOWAIT, MT_HEADER);
#else
m = m_getl(hdrlen + max_linkhdr, M_NOWAIT, MT_HEADER,
M_PKTHDR, NULL);
#endif
if (m == NULL) {
error = ENOBUFS;
goto after_th;
}
m->m_data += max_linkhdr;
m->m_len = hdrlen;
if (len <= MHLEN - hdrlen - max_linkhdr) {
m_copydata(so->so_snd.ssb_mb, off, (int) len,
mtod(m, caddr_t) + hdrlen);
m->m_len += len;
} else {
m->m_next = m_copym(so->so_snd.ssb_mb, off,
(int) len, M_NOWAIT);
if (m->m_next == NULL) {
m_free(m);
m = NULL;
error = ENOBUFS;
goto after_th;
}
}
#endif
if (off + len == so->so_snd.ssb_cc)
flags |= TH_PUSH;
} else {
if (tp->t_flags & TF_ACKNOW)
tcpstat.tcps_sndacks++;
else if (flags & (TH_SYN | TH_FIN | TH_RST))
tcpstat.tcps_sndctrl++;
else if (SEQ_GT(tp->snd_up, tp->snd_una))
tcpstat.tcps_sndurg++;
else
tcpstat.tcps_sndwinup++;
MGETHDR(m, M_NOWAIT, MT_HEADER);
if (m == NULL) {
error = ENOBUFS;
goto after_th;
}
if (isipv6 &&
(hdrlen + max_linkhdr > MHLEN) && hdrlen <= MHLEN)
MH_ALIGN(m, hdrlen);
else
m->m_data += max_linkhdr;
m->m_len = hdrlen;
if (tcp_prio_synack && (flags & (TH_FIN | TH_RST)) == 0)
m->m_flags |= M_PRIO;
}
m->m_pkthdr.rcvif = NULL;
if (isipv6) {
ip6 = mtod(m, struct ip6_hdr *);
th = (struct tcphdr *)(ip6 + 1);
tcp_fillheaders(tp, ip6, th, use_tso);
} else {
ip = mtod(m, struct ip *);
th = (struct tcphdr *)(ip + 1);
tcp_fillheaders(tp, ip, th, use_tso);
}
after_th:
if (flags & TH_FIN && tp->t_flags & TF_SENTFIN &&
tp->snd_nxt == tp->snd_max)
tp->snd_nxt--;
if (th != NULL) {
if (len || (flags & (TH_SYN|TH_FIN)) ||
tcp_callout_active(tp, tp->tt_persist))
th->th_seq = htonl(tp->snd_nxt);
else
th->th_seq = htonl(tp->snd_max);
th->th_ack = htonl(tp->rcv_nxt);
if (optlen) {
bcopy(opt, th + 1, optlen);
th->th_off = (sizeof(struct tcphdr) + optlen) >> 2;
}
th->th_flags = flags;
}
if (recvwin < (long)(so->so_rcv.ssb_hiwat / 4) &&
recvwin < (long)segsz)
recvwin = 0;
if (recvwin < (tcp_seq_diff_t)(tp->rcv_adv - tp->rcv_nxt))
recvwin = (tcp_seq_diff_t)(tp->rcv_adv - tp->rcv_nxt);
if (recvwin > (long)TCP_MAXWIN << tp->rcv_scale)
recvwin = (long)TCP_MAXWIN << tp->rcv_scale;
if (recvwin == 0)
tp->t_flags |= TF_RXWIN0SENT;
else
tp->t_flags &= ~TF_RXWIN0SENT;
if (th != NULL)
th->th_win = htons((u_short) (recvwin>>tp->rcv_scale));
if (SEQ_GT(tp->snd_up, tp->snd_nxt)) {
KASSERT(!use_tso, ("URG with TSO"));
if (th != NULL) {
th->th_urp = htons((u_short)(tp->snd_up - tp->snd_nxt));
th->th_flags |= TH_URG;
}
} else {
tp->snd_up = tp->snd_una;
}
if (th != NULL) {
#ifdef TCP_SIGNATURE
if (tp->t_flags & TF_SIGNATURE) {
tcpsignature_compute(m, len, optlen,
(u_char *)(th + 1) + sigoff, IPSEC_DIR_OUTBOUND);
}
#endif
m->m_pkthdr.len = hdrlen + len;
if (isipv6) {
th->th_sum = in6_cksum(m, IPPROTO_TCP,
sizeof(struct ip6_hdr),
sizeof(struct tcphdr) + optlen + len);
} else {
m->m_pkthdr.csum_thlen = sizeof(struct tcphdr) + optlen;
if (use_tso) {
m->m_pkthdr.csum_flags = CSUM_TSO;
m->m_pkthdr.tso_segsz = segsz;
} else {
m->m_pkthdr.csum_flags = CSUM_TCP;
m->m_pkthdr.csum_data =
offsetof(struct tcphdr, th_sum);
if (len + optlen) {
th->th_sum = in_addword(th->th_sum,
htons((u_short)(optlen + len)));
}
}
KASSERT(ip->ip_v == IPVERSION,
("%s: IP version incorrect: %d",
__func__, ip->ip_v));
}
}
if (!(tp->t_flags & TF_FORCE) ||
!tcp_callout_active(tp, tp->tt_persist)) {
tcp_seq startseq = tp->snd_nxt;
if (flags & (TH_SYN | TH_FIN)) {
if (flags & TH_SYN)
tp->snd_nxt++;
if (flags & TH_FIN) {
tp->snd_nxt++;
tp->t_flags |= TF_SENTFIN;
}
}
tp->snd_nxt += len;
if (SEQ_GT(tp->snd_nxt, tp->snd_max)) {
tp->snd_max = tp->snd_nxt;
if (tp->t_rtttime == 0) {
tp->t_rtttime = ticks;
tp->t_rtseq = startseq;
tcpstat.tcps_segstimed++;
}
}
if (!tcp_callout_active(tp, tp->tt_rexmt) &&
tp->snd_nxt != tp->snd_una) {
if (tcp_callout_active(tp, tp->tt_persist)) {
tcp_callout_stop(tp, tp->tt_persist);
tp->t_rxtshift = 0;
}
tcp_callout_reset(tp, tp->tt_rexmt, tp->t_rxtcur,
tcp_timer_rexmt);
} else if (len == 0 && so->so_snd.ssb_cc &&
tp->t_state > TCPS_SYN_RECEIVED &&
!tcp_callout_active(tp, tp->tt_rexmt) &&
!tcp_callout_active(tp, tp->tt_persist)) {
tp->t_rxtshift = 0;
tcp_setpersist(tp);
}
} else {
int xlen = len;
if (flags & TH_SYN)
panic("tcp_output: persist timer to send SYN");
if (flags & TH_FIN) {
++xlen;
tp->t_flags |= TF_SENTFIN;
}
if (SEQ_GT(tp->snd_nxt + xlen, tp->snd_max))
tp->snd_max = tp->snd_nxt + xlen;
}
if (th != NULL) {
#ifdef TCPDEBUG
if (so->so_options & SO_DEBUG) {
tcp_trace(TA_OUTPUT, tp->t_state, tp,
mtod(m, void *), th, 0);
}
#endif
if (isipv6) {
ip6->ip6_hlim = in6_selecthlim(inp,
(inp->in6p_route.ro_rt ?
inp->in6p_route.ro_rt->rt_ifp : NULL));
error = ip6_output(m, inp->in6p_outputopts,
&inp->in6p_route, (so->so_options & SO_DONTROUTE),
NULL, NULL, inp);
} else {
struct rtentry *rt;
KASSERT(!INP_CHECK_SOCKAF(so, AF_INET6), ("inet6 pcb"));
ip->ip_len = htons(m->m_pkthdr.len);
ip->ip_ttl = inp->inp_ip_ttl;
ip->ip_tos = inp->inp_ip_tos;
if (path_mtu_discovery &&
(rt = inp->inp_route.ro_rt) &&
(rt->rt_flags & RTF_UP) &&
!(rt->rt_rmx.rmx_locks & RTV_MTU))
{
ip->ip_off |= htons(IP_DF);
}
KASSERT(inp->inp_flags & INP_HASH,
("inpcb has no hash"));
m_sethash(m, inp->inp_hashval);
error = ip_output(m, inp->inp_options, &inp->inp_route,
(so->so_options & SO_DONTROUTE) |
IP_DEBUGROUTE, NULL, inp);
}
} else {
KASSERT(error != 0, ("no error, but th not set"));
}
if (error) {
tp->t_flags &= ~(TF_ACKNOW | TF_XMITNOW);
if (!(tp->t_flags & TF_FORCE) ||
!tcp_callout_active(tp, tp->tt_persist)) {
if (!(flags & TH_SYN))
tp->snd_nxt -= len;
}
out:
if (error == ENOBUFS) {
KASSERT((len == 0 && (flags & (TH_SYN | TH_FIN)) == 0) ||
tcp_callout_active(tp, tp->tt_rexmt) ||
tcp_callout_active(tp, tp->tt_persist),
("neither rexmt nor persist timer is set"));
return (0);
}
if (error == EMSGSIZE) {
tcp_mtudisc(inp, 0);
return 0;
}
if ((error == EHOSTUNREACH || error == ENETDOWN) &&
TCPS_HAVERCVDSYN(tp->t_state)) {
tp->t_softerror = error;
return (0);
}
return (error);
}
tcpstat.tcps_sndtotal++;
if (recvwin > 0 && SEQ_GT(tp->rcv_nxt + recvwin, tp->rcv_adv)) {
tp->rcv_adv = tp->rcv_nxt + recvwin;
tp->t_flags &= ~TF_RXRESIZED;
}
tp->last_ack_sent = tp->rcv_nxt;
tp->t_flags &= ~(TF_ACKNOW | TF_XMITNOW);
if (tcp_delack_enabled)
tcp_callout_stop(tp, tp->tt_delack);
if (sendalot) {
if (tcp_fairsend > 0 && (tp->t_flags & TF_FAIRSEND) &&
segcnt >= tcp_fairsend)
need_sched = TRUE;
goto again;
}
return (0);
}
void
tcp_setpersist(struct tcpcb *tp)
{
int t = ((tp->t_srtt >> 2) + tp->t_rttvar) >> 1;
int tt;
if (tp->t_state == TCPS_SYN_SENT ||
tp->t_state == TCPS_SYN_RECEIVED) {
panic("tcp_setpersist: not established yet, current %s",
tp->t_state == TCPS_SYN_SENT ?
"SYN_SENT" : "SYN_RECEIVED");
}
if (tcp_callout_active(tp, tp->tt_rexmt))
panic("tcp_setpersist: retransmit pending");
TCPT_RANGESET(tt, t * tcp_backoff[tp->t_rxtshift], TCPTV_PERSMIN,
TCPTV_PERSMAX);
tcp_callout_reset(tp, tp->tt_persist, tt, tcp_timer_persist);
if (tp->t_rxtshift < TCP_MAXRXTSHIFT)
tp->t_rxtshift++;
}
static void
tcp_idle_cwnd_validate(struct tcpcb *tp)
{
u_long initial_cwnd = tcp_initial_window(tp);
u_long min_cwnd;
tcpstat.tcps_sndidle++;
min_cwnd = min(tp->snd_cwnd, initial_cwnd);
if (tcp_idle_cwv) {
u_long idle_time, decay_cwnd;
tp->snd_ssthresh = max(tp->snd_ssthresh,
(3 * tp->snd_cwnd) / 4);
idle_time = ticks - tp->snd_last;
decay_cwnd = tp->snd_cwnd;
while (idle_time >= tp->t_rxtcur &&
decay_cwnd > min_cwnd) {
decay_cwnd >>= 1;
idle_time -= tp->t_rxtcur;
}
tp->snd_cwnd = max(decay_cwnd, min_cwnd);
} else {
tp->snd_cwnd = min_cwnd;
}
tp->snd_wacked = 0;
}
static int
tcp_tso_getsize(struct tcpcb *tp, u_int *segsz, u_int *hlen0)
{
struct inpcb * const inp = tp->t_inpcb;
#ifdef INET6
const boolean_t isipv6 = INP_ISIPV6(inp);
#else
const boolean_t isipv6 = FALSE;
#endif
unsigned int ipoptlen, optlen;
u_int hlen;
hlen = sizeof(struct ip) + sizeof(struct tcphdr);
if (isipv6) {
ipoptlen = ip6_optlen(inp);
} else {
if (inp->inp_options) {
ipoptlen = inp->inp_options->m_len -
offsetof(struct ipoption, ipopt_list);
} else {
ipoptlen = 0;
}
}
hlen += ipoptlen;
optlen = 0;
if ((tp->t_flags & (TF_REQ_TSTMP | TF_NOOPT)) == TF_REQ_TSTMP &&
(tp->t_flags & TF_RCVD_TSTMP))
optlen += TCPOLEN_TSTAMP_APPA;
hlen += optlen;
if (tp->t_maxopd <= optlen + ipoptlen)
return EHOSTUNREACH;
*segsz = tp->t_maxopd - optlen - ipoptlen;
*hlen0 = hlen;
return 0;
}
static void
tcp_output_sched_handler(netmsg_t nmsg)
{
struct tcpcb *tp = nmsg->lmsg.u.ms_resultp;
crit_enter();
lwkt_replymsg(&nmsg->lmsg, 0);
crit_exit();
tcp_output_fair(tp);
}
void
tcp_output_init(struct tcpcb *tp)
{
netmsg_init(tp->tt_sndmore, NULL, &netisr_adone_rport, MSGF_DROPABLE,
tcp_output_sched_handler);
tp->tt_sndmore->lmsg.u.ms_resultp = tp;
}
void
tcp_output_cancel(struct tcpcb *tp)
{
crit_enter();
if ((tp->tt_sndmore->lmsg.ms_flags & MSGF_DONE) == 0) {
lwkt_dropmsg(&tp->tt_sndmore->lmsg);
}
crit_exit();
}
boolean_t
tcp_output_pending(struct tcpcb *tp)
{
if ((tp->tt_sndmore->lmsg.ms_flags & MSGF_DONE) == 0)
return TRUE;
else
return FALSE;
}
static void
tcp_output_sched(struct tcpcb *tp)
{
crit_enter();
if (tp->tt_sndmore->lmsg.ms_flags & MSGF_DONE)
lwkt_sendmsg(netisr_cpuport(mycpuid), &tp->tt_sndmore->lmsg);
crit_exit();
}
int
tcp_output_fair(struct tcpcb *tp)
{
int ret;
tp->t_flags |= TF_FAIRSEND;
ret = tcp_output(tp);
tp->t_flags &= ~TF_FAIRSEND;
return ret;
}