#include "opt_inet.h"
#include "opt_inet6.h"
#include "opt_tcpdebug.h"
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/callout.h>
#include <sys/kernel.h>
#include <sys/sysctl.h>
#include <sys/malloc.h>
#include <sys/mpipe.h>
#include <sys/mbuf.h>
#ifdef INET6
#include <sys/domain.h>
#endif
#include <sys/proc.h>
#include <sys/caps.h>
#include <sys/socket.h>
#include <sys/socketops.h>
#include <sys/socketvar.h>
#include <sys/protosw.h>
#include <sys/random.h>
#include <sys/in_cksum.h>
#include <sys/ktr.h>
#include <net/route.h>
#include <net/if.h>
#include <net/netisr2.h>
#define _IP_VHL
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <netinet/ip6.h>
#include <netinet/in_pcb.h>
#include <netinet6/in6_pcb.h>
#include <netinet/in_var.h>
#include <netinet/ip_var.h>
#include <netinet6/ip6_var.h>
#include <netinet/ip_icmp.h>
#ifdef INET6
#include <netinet/icmp6.h>
#endif
#include <netinet/tcp.h>
#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 <netinet6/tcp6_var.h>
#include <netinet/tcpip.h>
#ifdef TCPDEBUG
#include <netinet/tcp_debug.h>
#endif
#include <netinet6/ip6protosw.h>
#include <sys/md5.h>
#include <machine/smp.h>
#include <sys/msgport2.h>
#include <net/netmsg2.h>
#if !defined(KTR_TCP)
#define KTR_TCP KTR_ALL
#endif
#define TCP_IW_MAXSEGS_DFLT 4
#define TCP_IW_CAPSEGS_DFLT 4
struct tcp_reass_pcpu {
int draining;
struct netmsg_base drain_nmsg;
} __cachealign;
struct inpcbinfo tcbinfo[MAXCPU];
struct tcpcbackq tcpcbackq[MAXCPU];
struct tcp_reass_pcpu tcp_reassq[MAXCPU];
int tcp_mssdflt = TCP_MSS;
SYSCTL_INT(_net_inet_tcp, TCPCTL_MSSDFLT, mssdflt, CTLFLAG_RW,
&tcp_mssdflt, 0, "Default TCP Maximum Segment Size");
#ifdef INET6
int tcp_v6mssdflt = TCP6_MSS;
SYSCTL_INT(_net_inet_tcp, TCPCTL_V6MSSDFLT, v6mssdflt, CTLFLAG_RW,
&tcp_v6mssdflt, 0, "Default TCP Maximum Segment Size for IPv6");
#endif
int tcp_minmss = TCP_MINMSS;
SYSCTL_INT(_net_inet_tcp, OID_AUTO, minmss, CTLFLAG_RW,
&tcp_minmss , 0, "Minmum TCP Maximum Segment Size");
#if 0
static int tcp_rttdflt = TCPTV_SRTTDFLT / PR_SLOWHZ;
SYSCTL_INT(_net_inet_tcp, TCPCTL_RTTDFLT, rttdflt, CTLFLAG_RW,
&tcp_rttdflt, 0, "Default maximum TCP Round Trip Time");
#endif
int tcp_do_rfc1323 = 1;
SYSCTL_INT(_net_inet_tcp, TCPCTL_DO_RFC1323, rfc1323, CTLFLAG_RW,
&tcp_do_rfc1323, 0, "Enable rfc1323 (high performance TCP) extensions");
static int tcp_tcbhashsize = 0;
SYSCTL_INT(_net_inet_tcp, OID_AUTO, tcbhashsize, CTLFLAG_RD,
&tcp_tcbhashsize, 0, "Size of TCP control block hashtable");
static int do_tcpdrain = 1;
SYSCTL_INT(_net_inet_tcp, OID_AUTO, do_tcpdrain, CTLFLAG_RW, &do_tcpdrain, 0,
"Enable tcp_drain routine for extra help when low on mbufs");
static int icmp_may_rst = 1;
SYSCTL_INT(_net_inet_tcp, OID_AUTO, icmp_may_rst, CTLFLAG_RW, &icmp_may_rst, 0,
"Certain ICMP unreachable messages may abort connections in SYN_SENT");
static int tcp_isn_reseed_interval = 20;
SYSCTL_INT(_net_inet_tcp, OID_AUTO, isn_reseed_interval, CTLFLAG_RW,
&tcp_isn_reseed_interval, 0, "Seconds between reseeding of ISN secret");
static int tcp_inflight_enable = 1;
SYSCTL_INT(_net_inet_tcp, OID_AUTO, inflight_enable, CTLFLAG_RW,
&tcp_inflight_enable, 0, "Enable automatic TCP inflight data limiting");
static int tcp_inflight_debug = 0;
SYSCTL_INT(_net_inet_tcp, OID_AUTO, inflight_debug, CTLFLAG_RW,
&tcp_inflight_debug, 0, "Debug TCP inflight calculations");
static int tcp_inflight_start = 33792;
SYSCTL_INT(_net_inet_tcp, OID_AUTO, inflight_start, CTLFLAG_RW,
&tcp_inflight_start, 0, "Start value for TCP inflight window");
static int tcp_inflight_min = 6144;
SYSCTL_INT(_net_inet_tcp, OID_AUTO, inflight_min, CTLFLAG_RW,
&tcp_inflight_min, 0, "Lower bound for TCP inflight window");
static int tcp_inflight_max = TCP_MAXWIN << TCP_MAX_WINSHIFT;
SYSCTL_INT(_net_inet_tcp, OID_AUTO, inflight_max, CTLFLAG_RW,
&tcp_inflight_max, 0, "Upper bound for TCP inflight window");
static int tcp_inflight_stab = 50;
SYSCTL_INT(_net_inet_tcp, OID_AUTO, inflight_stab, CTLFLAG_RW,
&tcp_inflight_stab, 0, "Fudge bw 1/10% (50=5%)");
static int tcp_inflight_adjrtt = 2;
SYSCTL_INT(_net_inet_tcp, OID_AUTO, inflight_adjrtt, CTLFLAG_RW,
&tcp_inflight_adjrtt, 0, "Slop for rtt 1/(hz*32)");
static int tcp_do_rfc3390 = 1;
SYSCTL_INT(_net_inet_tcp, OID_AUTO, rfc3390, CTLFLAG_RW,
&tcp_do_rfc3390, 0,
"Enable RFC 3390 (Increasing TCP's Initial Congestion Window)");
static u_long tcp_iw_maxsegs = TCP_IW_MAXSEGS_DFLT;
SYSCTL_ULONG(_net_inet_tcp, OID_AUTO, iwmaxsegs, CTLFLAG_RW,
&tcp_iw_maxsegs, 0, "TCP IW segments max");
static u_long tcp_iw_capsegs = TCP_IW_CAPSEGS_DFLT;
SYSCTL_ULONG(_net_inet_tcp, OID_AUTO, iwcapsegs, CTLFLAG_RW,
&tcp_iw_capsegs, 0, "TCP IW segments");
int tcp_low_rtobase = 1;
SYSCTL_INT(_net_inet_tcp, OID_AUTO, low_rtobase, CTLFLAG_RW,
&tcp_low_rtobase, 0, "Lowering the Initial RTO (RFC 6298)");
static int tcp_do_ncr = 1;
SYSCTL_INT(_net_inet_tcp, OID_AUTO, ncr, CTLFLAG_RW,
&tcp_do_ncr, 0, "Non-Congestion Robustness (RFC 4653)");
int tcp_ncr_linklocal = 0;
SYSCTL_INT(_net_inet_tcp, OID_AUTO, ncr_linklocal, CTLFLAG_RW,
&tcp_ncr_linklocal, 0,
"Enable Non-Congestion Robustness (RFC 4653) on link local network");
int tcp_ncr_rxtthresh_max = 16;
SYSCTL_INT(_net_inet_tcp, OID_AUTO, ncr_rxtthresh_max, CTLFLAG_RW,
&tcp_ncr_rxtthresh_max, 0,
"Non-Congestion Robustness (RFC 4653), DupThresh upper limit");
static MALLOC_DEFINE(M_TCPTEMP, "tcptemp", "TCP Templates for Keepalives");
static struct malloc_pipe tcptemp_mpipe;
static void tcp_willblock(void);
static void tcp_notify (struct inpcb *, int);
struct tcp_stats tcpstats_percpu[MAXCPU] __cachealign;
struct tcp_state_count tcpstate_count[MAXCPU] __cachealign;
static void tcp_drain_dispatch(netmsg_t nmsg);
static int
sysctl_tcpstats(SYSCTL_HANDLER_ARGS)
{
int cpu, error = 0;
for (cpu = 0; cpu < netisr_ncpus; ++cpu) {
if ((error = SYSCTL_OUT(req, &tcpstats_percpu[cpu],
sizeof(struct tcp_stats))))
break;
if ((error = SYSCTL_IN(req, &tcpstats_percpu[cpu],
sizeof(struct tcp_stats))))
break;
}
return (error);
}
SYSCTL_PROC(_net_inet_tcp, TCPCTL_STATS, stats, (CTLTYPE_OPAQUE | CTLFLAG_RW),
0, 0, sysctl_tcpstats, "S,tcp_stats", "TCP statistics");
#ifndef TCBHASHSIZE
#define TCBHASHSIZE 512
#endif
CTASSERT(powerof2(TCBHASHSIZE));
#define ALIGNMENT 32
#define ALIGNM1 (ALIGNMENT - 1)
struct inp_tp {
union {
struct inpcb inp;
char align[(sizeof(struct inpcb) + ALIGNM1) & ~ALIGNM1];
} inp_tp_u;
struct tcpcb tcb;
struct tcp_callout inp_tp_rexmt;
struct tcp_callout inp_tp_persist;
struct tcp_callout inp_tp_keep;
struct tcp_callout inp_tp_2msl;
struct tcp_callout inp_tp_delack;
struct netmsg_tcp_timer inp_tp_timermsg;
struct netmsg_base inp_tp_sndmore;
};
#undef ALIGNMENT
#undef ALIGNM1
void
tcp_init(void)
{
struct inpcbportinfo *portinfo;
struct inpcbinfo *ticb;
int hashsize = TCBHASHSIZE, portinfo_hsize;
int cpu;
mpipe_init(&tcptemp_mpipe, M_TCPTEMP, sizeof(struct tcptemp),
25, -1, 0, NULL, NULL, NULL);
tcp_delacktime = TCPTV_DELACK;
tcp_keepinit = TCPTV_KEEP_INIT;
tcp_keepidle = TCPTV_KEEP_IDLE;
tcp_keepintvl = TCPTV_KEEPINTVL;
tcp_maxpersistidle = TCPTV_KEEP_IDLE;
tcp_msl = TCPTV_MSL;
tcp_rexmit_min = TCPTV_MIN;
if (tcp_rexmit_min < 1)
tcp_rexmit_min = 1;
tcp_rexmit_slop = TCPTV_CPU_VAR;
TUNABLE_INT_FETCH("net.inet.tcp.tcbhashsize", &hashsize);
if (!powerof2(hashsize)) {
kprintf("WARNING: TCB hash size not a power of 2\n");
hashsize = TCBHASHSIZE;
}
tcp_tcbhashsize = hashsize;
portinfo_hsize = 65536 / netisr_ncpus;
if (portinfo_hsize > hashsize)
portinfo_hsize = hashsize;
portinfo = kmalloc(sizeof(*portinfo) * netisr_ncpus, M_PCB,
M_WAITOK | M_CACHEALIGN);
for (cpu = 0; cpu < netisr_ncpus; cpu++) {
ticb = &tcbinfo[cpu];
in_pcbinfo_init(ticb, cpu, FALSE);
ticb->hashbase = hashinit(hashsize, M_PCB,
&ticb->hashmask);
in_pcbportinfo_init(&portinfo[cpu], portinfo_hsize, cpu);
in_pcbportinfo_set(ticb, portinfo, netisr_ncpus);
ticb->wildcardhashbase = hashinit(hashsize, M_PCB,
&ticb->wildcardhashmask);
ticb->localgrphashbase = hashinit(hashsize, M_PCB,
&ticb->localgrphashmask);
ticb->ipi_size = sizeof(struct inp_tp);
TAILQ_INIT(&tcpcbackq[cpu].head);
}
tcp_reass_maxseg = nmbclusters / 16;
TUNABLE_INT_FETCH("net.inet.tcp.reass.maxsegments", &tcp_reass_maxseg);
#ifdef INET6
#define TCP_MINPROTOHDR (sizeof(struct ip6_hdr) + sizeof(struct tcphdr))
#else
#define TCP_MINPROTOHDR (sizeof(struct tcpiphdr))
#endif
if (max_protohdr < TCP_MINPROTOHDR)
max_protohdr = TCP_MINPROTOHDR;
if (max_linkhdr + TCP_MINPROTOHDR > MHLEN)
panic("tcp_init");
#undef TCP_MINPROTOHDR
for (cpu = 0; cpu < netisr_ncpus; ++cpu)
bzero(&tcpstats_percpu[cpu], sizeof(struct tcp_stats));
for (cpu = 0; cpu < netisr_ncpus; ++cpu) {
netmsg_init(&tcp_reassq[cpu].drain_nmsg, NULL,
&netisr_adone_rport, MSGF_PRIORITY, tcp_drain_dispatch);
}
syncache_init();
netisr_register_rollup(tcp_willblock, NETISR_ROLLUP_PRIO_TCP);
}
static void
tcp_willblock(void)
{
struct tcpcb *tp;
int cpu = mycpuid;
while ((tp = TAILQ_FIRST(&tcpcbackq[cpu].head)) != NULL) {
KKASSERT(tp->t_flags & TF_ONOUTPUTQ);
tp->t_flags &= ~TF_ONOUTPUTQ;
TAILQ_REMOVE(&tcpcbackq[cpu].head, tp, t_outputq);
tcp_output(tp);
}
}
void
tcp_fillheaders(struct tcpcb *tp, void *ip_ptr, void *tcp_ptr, boolean_t tso)
{
struct inpcb *inp = tp->t_inpcb;
struct tcphdr *tcp_hdr = (struct tcphdr *)tcp_ptr;
#ifdef INET6
if (INP_ISIPV6(inp)) {
struct ip6_hdr *ip6;
ip6 = (struct ip6_hdr *)ip_ptr;
ip6->ip6_flow = (ip6->ip6_flow & ~IPV6_FLOWINFO_MASK) |
(inp->in6p_flowinfo & IPV6_FLOWINFO_MASK);
ip6->ip6_vfc = (ip6->ip6_vfc & ~IPV6_VERSION_MASK) |
(IPV6_VERSION & IPV6_VERSION_MASK);
ip6->ip6_nxt = IPPROTO_TCP;
ip6->ip6_plen = sizeof(struct tcphdr);
ip6->ip6_src = inp->in6p_laddr;
ip6->ip6_dst = inp->in6p_faddr;
tcp_hdr->th_sum = 0;
} else
#endif
{
struct ip *ip = (struct ip *) ip_ptr;
u_int plen;
ip->ip_vhl = IP_VHL_BORING;
ip->ip_tos = 0;
ip->ip_len = 0;
ip->ip_id = 0;
ip->ip_off = 0;
ip->ip_ttl = 0;
ip->ip_sum = 0;
ip->ip_p = IPPROTO_TCP;
ip->ip_src = inp->inp_laddr;
ip->ip_dst = inp->inp_faddr;
if (tso)
plen = htons(IPPROTO_TCP);
else
plen = htons(sizeof(struct tcphdr) + IPPROTO_TCP);
tcp_hdr->th_sum = in_pseudo(ip->ip_src.s_addr,
ip->ip_dst.s_addr, plen);
}
tcp_hdr->th_sport = inp->inp_lport;
tcp_hdr->th_dport = inp->inp_fport;
tcp_hdr->th_seq = 0;
tcp_hdr->th_ack = 0;
tcp_hdr->th_x2 = 0;
tcp_hdr->th_off = 5;
tcp_hdr->th_flags = 0;
tcp_hdr->th_win = 0;
tcp_hdr->th_urp = 0;
}
struct tcptemp *
tcp_maketemplate(struct tcpcb *tp)
{
struct tcptemp *tmp;
if ((tmp = mpipe_alloc_nowait(&tcptemp_mpipe)) == NULL)
return (NULL);
tcp_fillheaders(tp, &tmp->tt_ipgen, &tmp->tt_t, FALSE);
return (tmp);
}
void
tcp_freetemplate(struct tcptemp *tmp)
{
mpipe_free(&tcptemp_mpipe, tmp);
}
void
tcp_respond(struct tcpcb *tp, void *ipgen, struct tcphdr *th, struct mbuf *m,
tcp_seq ack, tcp_seq seq, int flags)
{
int tlen;
long win = 0;
struct route *ro = NULL;
struct route sro;
struct ip *ip = ipgen;
struct tcphdr *nth;
int ipflags = 0;
struct route_in6 *ro6 = NULL;
struct route_in6 sro6;
struct ip6_hdr *ip6 = ipgen;
struct inpcb *inp = NULL;
boolean_t use_tmpro = TRUE;
#ifdef INET6
boolean_t isipv6 = (IP_VHL_V(ip->ip_vhl) == 6);
#else
const boolean_t isipv6 = FALSE;
#endif
if (tp != NULL) {
inp = tp->t_inpcb;
if (!(flags & TH_RST)) {
win = ssb_space(&inp->inp_socket->so_rcv);
if (win < 0)
win = 0;
if (win > (long)TCP_MAXWIN << tp->rcv_scale)
win = (long)TCP_MAXWIN << tp->rcv_scale;
}
if (tp->t_state != TCPS_LISTEN) {
if (isipv6)
ro6 = &inp->in6p_route;
else
ro = &inp->inp_route;
use_tmpro = FALSE;
}
}
if (use_tmpro) {
if (isipv6) {
ro6 = &sro6;
bzero(ro6, sizeof *ro6);
} else {
ro = &sro;
bzero(ro, sizeof *ro);
}
}
if (m == NULL) {
m = m_gethdr(M_NOWAIT, MT_HEADER);
if (m == NULL)
return;
tlen = 0;
m->m_data += max_linkhdr;
if (isipv6) {
bcopy(ip6, mtod(m, caddr_t), sizeof(struct ip6_hdr));
ip6 = mtod(m, struct ip6_hdr *);
nth = (struct tcphdr *)(ip6 + 1);
} else {
bcopy(ip, mtod(m, caddr_t), sizeof(struct ip));
ip = mtod(m, struct ip *);
nth = (struct tcphdr *)(ip + 1);
}
bcopy(th, nth, sizeof(struct tcphdr));
flags = TH_ACK;
} else {
m_freem(m->m_next);
m->m_next = NULL;
m->m_data = (caddr_t)ipgen;
tlen = 0;
#define xchg(a, b, type) { type t; t = a; a = b; b = t; }
if (isipv6) {
xchg(ip6->ip6_dst, ip6->ip6_src, struct in6_addr);
nth = (struct tcphdr *)(ip6 + 1);
} else {
xchg(ip->ip_dst.s_addr, ip->ip_src.s_addr, n_long);
nth = (struct tcphdr *)(ip + 1);
}
if (th != nth) {
nth->th_sport = th->th_sport;
nth->th_dport = th->th_dport;
}
xchg(nth->th_dport, nth->th_sport, n_short);
#undef xchg
}
if (isipv6) {
ip6->ip6_flow = 0;
ip6->ip6_vfc = IPV6_VERSION;
ip6->ip6_nxt = IPPROTO_TCP;
ip6->ip6_plen = htons((u_short)(sizeof(struct tcphdr) + tlen));
tlen += sizeof(struct ip6_hdr) + sizeof(struct tcphdr);
} else {
tlen += sizeof(struct tcpiphdr);
ip->ip_len = htons(tlen);
ip->ip_ttl = ip_defttl;
}
m->m_len = tlen;
m->m_pkthdr.len = tlen;
m->m_pkthdr.rcvif = NULL;
nth->th_seq = htonl(seq);
nth->th_ack = htonl(ack);
nth->th_x2 = 0;
nth->th_off = sizeof(struct tcphdr) >> 2;
nth->th_flags = flags;
if (tp != NULL)
nth->th_win = htons((u_short) (win >> tp->rcv_scale));
else
nth->th_win = htons((u_short)win);
nth->th_urp = 0;
if (isipv6) {
nth->th_sum = 0;
nth->th_sum = in6_cksum(m, IPPROTO_TCP,
sizeof(struct ip6_hdr),
tlen - sizeof(struct ip6_hdr));
ip6->ip6_hlim = in6_selecthlim(inp,
(ro6 && ro6->ro_rt) ? ro6->ro_rt->rt_ifp : NULL);
} else {
nth->th_sum = in_pseudo(ip->ip_src.s_addr, ip->ip_dst.s_addr,
htons((u_short)(tlen - sizeof(struct ip) + ip->ip_p)));
m->m_pkthdr.csum_flags = CSUM_TCP;
m->m_pkthdr.csum_data = offsetof(struct tcphdr, th_sum);
m->m_pkthdr.csum_thlen = sizeof(struct tcphdr);
}
#ifdef TCPDEBUG
if (tp == NULL || (inp->inp_socket->so_options & SO_DEBUG))
tcp_trace(TA_OUTPUT, 0, tp, mtod(m, void *), th, 0);
#endif
if (isipv6) {
ip6_output(m, NULL, ro6, ipflags, NULL, NULL, inp);
if ((ro6 == &sro6) && (ro6->ro_rt != NULL)) {
RTFREE(ro6->ro_rt);
ro6->ro_rt = NULL;
}
} else {
if (inp != NULL && (inp->inp_flags & INP_HASH))
m_sethash(m, inp->inp_hashval);
ipflags |= IP_DEBUGROUTE;
ip_output(m, NULL, ro, ipflags, NULL, inp);
if ((ro == &sro) && (ro->ro_rt != NULL)) {
RTFREE(ro->ro_rt);
ro->ro_rt = NULL;
}
}
}
void
tcp_newtcpcb(struct inpcb *inp)
{
struct inp_tp *it;
struct tcpcb *tp;
#ifdef INET6
boolean_t isipv6 = INP_ISIPV6(inp);
#else
const boolean_t isipv6 = FALSE;
#endif
it = (struct inp_tp *)inp;
tp = &it->tcb;
bzero(tp, sizeof(struct tcpcb));
TAILQ_INIT(&tp->t_segq);
tp->t_maxseg = tp->t_maxopd = isipv6 ? tcp_v6mssdflt : tcp_mssdflt;
tp->t_rxtthresh = tcprexmtthresh;
tp->tt_rexmt = &it->inp_tp_rexmt;
tp->tt_persist = &it->inp_tp_persist;
tp->tt_keep = &it->inp_tp_keep;
tp->tt_2msl = &it->inp_tp_2msl;
tp->tt_delack = &it->inp_tp_delack;
tcp_inittimers(tp);
tp->tt_msg = &it->inp_tp_timermsg;
bzero(tp->tt_msg, sizeof(*tp->tt_msg));
tp->t_keepinit = tcp_keepinit;
tp->t_keepidle = tcp_keepidle;
tp->t_keepintvl = tcp_keepintvl;
tp->t_keepcnt = tcp_keepcnt;
tp->t_maxidle = tp->t_keepintvl * tp->t_keepcnt;
if (tcp_do_ncr)
tp->t_flags |= TF_NCR;
if (tcp_do_rfc1323)
tp->t_flags |= (TF_REQ_SCALE | TF_REQ_TSTMP);
tp->t_inpcb = inp;
TCP_STATE_INIT(tp);
tp->t_srtt = TCPTV_SRTTBASE;
tp->t_rttvar =
((TCPTV_RTOBASE - TCPTV_SRTTBASE) << TCP_RTTVAR_SHIFT) / 4;
tp->t_rttmin = tcp_rexmit_min;
tp->t_rxtcur = TCPTV_RTOBASE;
tp->snd_cwnd = TCP_MAXWIN << TCP_MAX_WINSHIFT;
tp->snd_bwnd = TCP_MAXWIN << TCP_MAX_WINSHIFT;
tp->snd_ssthresh = TCP_MAXWIN << TCP_MAX_WINSHIFT;
tp->snd_last = ticks;
tp->t_rcvtime = ticks;
inp->inp_ip_ttl = ip_defttl;
inp->inp_ppcb = tp;
tcp_sack_tcpcb_init(tp);
tp->tt_sndmore = &it->inp_tp_sndmore;
tcp_output_init(tp);
}
struct tcpcb *
tcp_drop(struct tcpcb *tp, int error)
{
struct socket *so = tp->t_inpcb->inp_socket;
if (TCPS_HAVERCVDSYN(tp->t_state)) {
TCP_STATE_CHANGE(tp, TCPS_CLOSED);
tcp_output(tp);
tcpstat.tcps_drops++;
} else
tcpstat.tcps_conndrops++;
if (error == ETIMEDOUT && tp->t_softerror)
error = tp->t_softerror;
so->so_error = error;
return (tcp_close(tp));
}
struct netmsg_listen_detach {
struct netmsg_base base;
struct tcpcb *nm_tp;
struct tcpcb *nm_tp_inh;
};
static void
tcp_listen_detach_handler(netmsg_t msg)
{
struct netmsg_listen_detach *nmsg = (struct netmsg_listen_detach *)msg;
struct tcpcb *tp = nmsg->nm_tp;
int cpu = mycpuid, nextcpu;
if (tp->t_flags & TF_LISTEN) {
syncache_destroy(tp, nmsg->nm_tp_inh);
tcp_pcbport_merge_oncpu(tp);
}
in_pcbremwildcardhash_oncpu(tp->t_inpcb, &tcbinfo[cpu]);
nextcpu = cpu + 1;
if (nextcpu < netisr_ncpus)
lwkt_forwardmsg(netisr_cpuport(nextcpu), &nmsg->base.lmsg);
else
lwkt_replymsg(&nmsg->base.lmsg, 0);
}
struct tcpcb *
tcp_close(struct tcpcb *tp)
{
struct tseg_qent *q;
struct inpcb *inp = tp->t_inpcb;
struct inpcb *inp_inh = NULL;
struct tcpcb *tp_inh = NULL;
struct socket *so = inp->inp_socket;
struct rtentry *rt;
boolean_t dosavessthresh;
#ifdef INET6
boolean_t isipv6 = INP_ISIPV6(inp);
#else
const boolean_t isipv6 = FALSE;
#endif
if (tp->t_flags & TF_LISTEN) {
ASSERT_NETISR0;
inp_inh = in_pcblocalgroup_last(&tcbinfo[0], inp);
if (inp_inh != NULL)
tp_inh = intotcpcb(inp_inh);
}
if ((inp->inp_flags & INP_WILDCARD) && netisr_ncpus > 1) {
struct netmsg_listen_detach nmsg;
KKASSERT(so->so_port == netisr_cpuport(0));
ASSERT_NETISR0;
KKASSERT(inp->inp_pcbinfo == &tcbinfo[0]);
netmsg_init(&nmsg.base, NULL, &curthread->td_msgport,
MSGF_PRIORITY, tcp_listen_detach_handler);
nmsg.nm_tp = tp;
nmsg.nm_tp_inh = tp_inh;
lwkt_domsg(netisr_cpuport(1), &nmsg.base.lmsg, 0);
}
TCP_STATE_TERM(tp);
if (tp->tt_msg != NULL && tp->tt_msg->tt_tcb != NULL) {
tcp_callout_terminate(tp, tp->tt_rexmt);
tcp_callout_terminate(tp, tp->tt_persist);
tcp_callout_terminate(tp, tp->tt_keep);
tcp_callout_terminate(tp, tp->tt_2msl);
tcp_callout_terminate(tp, tp->tt_delack);
}
if (tp->t_flags & TF_ONOUTPUTQ) {
KKASSERT(tp->tt_cpu == mycpu->gd_cpuid);
TAILQ_REMOVE(&tcpcbackq[tp->tt_cpu].head, tp, t_outputq);
tp->t_flags &= ~TF_ONOUTPUTQ;
}
if (tp->t_rttupdated >= 16) {
u_long i = 0;
if (isipv6) {
struct sockaddr_in6 *sin6;
if ((rt = inp->in6p_route.ro_rt) == NULL)
goto no_valid_rt;
sin6 = (struct sockaddr_in6 *)rt_key(rt);
if (IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr))
goto no_valid_rt;
} else
if ((rt = inp->inp_route.ro_rt) == NULL ||
((struct sockaddr_in *)rt_key(rt))->
sin_addr.s_addr == INADDR_ANY)
goto no_valid_rt;
if (!(rt->rt_rmx.rmx_locks & RTV_RTT)) {
i = tp->t_srtt * (RTM_RTTUNIT / (hz * TCP_RTT_SCALE));
if (rt->rt_rmx.rmx_rtt && i)
rt->rt_rmx.rmx_rtt =
(rt->rt_rmx.rmx_rtt + i) / 2;
else
rt->rt_rmx.rmx_rtt = i;
tcpstat.tcps_cachedrtt++;
}
if (!(rt->rt_rmx.rmx_locks & RTV_RTTVAR)) {
i = tp->t_rttvar *
(RTM_RTTUNIT / (hz * TCP_RTTVAR_SCALE));
if (rt->rt_rmx.rmx_rttvar && i)
rt->rt_rmx.rmx_rttvar =
(rt->rt_rmx.rmx_rttvar + i) / 2;
else
rt->rt_rmx.rmx_rttvar = i;
tcpstat.tcps_cachedrttvar++;
}
i = tp->snd_ssthresh;
if (rt->rt_rmx.rmx_sendpipe != 0)
dosavessthresh = (i < rt->rt_rmx.rmx_sendpipe/2);
else
dosavessthresh = (i < so->so_snd.ssb_hiwat/2);
if (dosavessthresh ||
(!(rt->rt_rmx.rmx_locks & RTV_SSTHRESH) && (i != 0) &&
(rt->rt_rmx.rmx_ssthresh != 0))) {
i = (i + tp->t_maxseg / 2) / tp->t_maxseg;
if (i < 2)
i = 2;
i *= tp->t_maxseg +
(isipv6 ?
sizeof(struct ip6_hdr) + sizeof(struct tcphdr) :
sizeof(struct tcpiphdr));
if (rt->rt_rmx.rmx_ssthresh)
rt->rt_rmx.rmx_ssthresh =
(rt->rt_rmx.rmx_ssthresh + i) / 2;
else
rt->rt_rmx.rmx_ssthresh = i;
tcpstat.tcps_cachedssthresh++;
}
}
no_valid_rt:
while((q = TAILQ_FIRST(&tp->t_segq)) != NULL) {
TAILQ_REMOVE(&tp->t_segq, q, tqe_q);
m_freem(q->tqe_m);
kfree(q, M_TSEGQ);
atomic_add_int(&tcp_reass_qsize, -1);
}
if (TCP_DO_SACK(tp))
tcp_sack_destroy(&tp->scb);
inp->inp_ppcb = NULL;
soisdisconnected(so);
tcp_destroy_timermsg(tp);
tcp_output_cancel(tp);
if (tp->t_flags & TF_LISTEN) {
syncache_destroy(tp, tp_inh);
tcp_pcbport_merge_oncpu(tp);
tcp_pcbport_destroy(tp);
if (inp_inh != NULL && inp_inh->inp_socket != NULL) {
soinherit(so, inp_inh->inp_socket);
}
}
KASSERT(tp->t_pcbport == NULL, ("tcpcb port cache is not destroyed"));
so_async_rcvd_drop(so);
sofree(so);
tcp_pcbport_remove(inp);
#ifdef INET6
if (isipv6)
in6_pcbdetach(inp);
else
#endif
in_pcbdetach(inp);
tcpstat.tcps_closed++;
return (NULL);
}
static void
tcp_drain_oncpu(struct inpcbinfo *pcbinfo)
{
struct inpcbhead *head = &pcbinfo->pcblisthead;
struct inpcb *inpb;
ASSERT_NETISR_NCPUS(pcbinfo->cpu);
LIST_FOREACH(inpb, head, inp_list) {
struct tcpcb *tcpb;
struct tseg_qent *te;
if (inpb->inp_flags & INP_PLACEMARKER)
continue;
tcpb = intotcpcb(inpb);
KASSERT(tcpb != NULL, ("tcp_drain_oncpu: tcpb is NULL"));
if ((te = TAILQ_FIRST(&tcpb->t_segq)) != NULL) {
TAILQ_REMOVE(&tcpb->t_segq, te, tqe_q);
if (te->tqe_th->th_flags & TH_FIN)
tcpb->t_flags &= ~TF_QUEDFIN;
m_freem(te->tqe_m);
kfree(te, M_TSEGQ);
atomic_add_int(&tcp_reass_qsize, -1);
}
}
}
static void
tcp_drain_dispatch(netmsg_t nmsg)
{
crit_enter();
lwkt_replymsg(&nmsg->lmsg, 0);
crit_exit();
tcp_drain_oncpu(&tcbinfo[mycpuid]);
tcp_reassq[mycpuid].draining = 0;
}
static void
tcp_drain_ipi(void *arg __unused)
{
int cpu = mycpuid;
struct lwkt_msg *msg = &tcp_reassq[cpu].drain_nmsg.lmsg;
crit_enter();
if (msg->ms_flags & MSGF_DONE)
lwkt_sendmsg_oncpu(netisr_cpuport(cpu), msg);
crit_exit();
}
void
tcp_drain(void)
{
cpumask_t mask;
int cpu;
if (!do_tcpdrain)
return;
if (tcp_reass_qsize == 0)
return;
CPUMASK_ASSBMASK(mask, netisr_ncpus);
CPUMASK_ANDMASK(mask, smp_active_mask);
cpu = mycpuid;
if (IN_NETISR_NCPUS(cpu)) {
tcp_drain_oncpu(&tcbinfo[cpu]);
CPUMASK_NANDBIT(mask, cpu);
}
if (tcp_reass_qsize < netisr_ncpus) {
return;
}
for (cpu = 0; cpu < netisr_ncpus; ++cpu) {
if (!CPUMASK_TESTBIT(mask, cpu))
continue;
if (tcp_reassq[cpu].draining) {
CPUMASK_NANDBIT(mask, cpu);
continue;
}
tcp_reassq[cpu].draining = 1;
}
if (CPUMASK_TESTNZERO(mask))
lwkt_send_ipiq_mask(mask, tcp_drain_ipi, NULL);
}
static void
tcp_notify(struct inpcb *inp, int error)
{
struct tcpcb *tp = intotcpcb(inp);
if (tp->t_state == TCPS_ESTABLISHED &&
(error == EHOSTUNREACH || error == ENETUNREACH ||
error == EHOSTDOWN)) {
return;
} else if (tp->t_state < TCPS_ESTABLISHED && tp->t_rxtshift > 3 &&
tp->t_softerror)
tcp_drop(tp, error);
else
tp->t_softerror = error;
#if 0
wakeup(&so->so_timeo);
sorwakeup(so);
sowwakeup(so);
#endif
}
static int
tcp_pcblist(SYSCTL_HANDLER_ARGS)
{
int error, i, n;
struct inpcb *marker;
struct inpcb *inp;
int origcpu, ccpu;
error = 0;
n = 0;
if (req->oldptr == NULL) {
for (ccpu = 0; ccpu < netisr_ncpus; ++ccpu)
n += tcbinfo[ccpu].ipi_count;
req->oldidx = (n + n/8 + 10) * sizeof(struct xtcpcb);
return (0);
}
if (req->newptr != NULL)
return (EPERM);
marker = kmalloc(sizeof(struct inpcb), M_TEMP, M_WAITOK|M_ZERO);
marker->inp_flags |= INP_PLACEMARKER;
origcpu = mycpu->gd_cpuid;
for (ccpu = 0; ccpu < netisr_ncpus && error == 0; ++ccpu) {
caddr_t inp_ppcb;
struct xtcpcb xt;
lwkt_migratecpu(ccpu);
n = tcbinfo[ccpu].ipi_count;
LIST_INSERT_HEAD(&tcbinfo[ccpu].pcblisthead, marker, inp_list);
i = 0;
while ((inp = LIST_NEXT(marker, inp_list)) != NULL && i < n) {
LIST_REMOVE(marker, inp_list);
LIST_INSERT_AFTER(inp, marker, inp_list);
if (inp->inp_flags & INP_PLACEMARKER)
continue;
if (prison_xinpcb(req->td, inp))
continue;
xt.xt_len = sizeof xt;
bcopy(inp, &xt.xt_inp, sizeof *inp);
inp_ppcb = inp->inp_ppcb;
if (inp_ppcb != NULL)
bcopy(inp_ppcb, &xt.xt_tp, sizeof xt.xt_tp);
else
bzero(&xt.xt_tp, sizeof xt.xt_tp);
if (inp->inp_socket)
sotoxsocket(inp->inp_socket, &xt.xt_socket);
if ((error = SYSCTL_OUT(req, &xt, sizeof xt)) != 0)
break;
++i;
}
LIST_REMOVE(marker, inp_list);
if (error == 0 && i < n) {
bzero(&xt, sizeof xt);
xt.xt_len = sizeof xt;
while (i < n) {
error = SYSCTL_OUT(req, &xt, sizeof xt);
if (error)
break;
++i;
}
}
}
lwkt_migratecpu(origcpu);
kfree(marker, M_TEMP);
return (error);
}
SYSCTL_PROC(_net_inet_tcp, TCPCTL_PCBLIST, pcblist, CTLFLAG_RD, 0, 0,
tcp_pcblist, "S,xtcpcb", "List of active TCP connections");
static int
tcp_getcred(SYSCTL_HANDLER_ARGS)
{
struct sockaddr_in addrs[2];
struct ucred cred0, *cred = NULL;
struct inpcb *inp;
int cpu, origcpu, error;
error = caps_priv_check_td(req->td, SYSCAP_RESTRICTEDROOT);
if (error != 0)
return (error);
error = SYSCTL_IN(req, addrs, sizeof addrs);
if (error != 0)
return (error);
origcpu = mycpuid;
cpu = tcp_addrcpu(addrs[1].sin_addr.s_addr, addrs[1].sin_port,
addrs[0].sin_addr.s_addr, addrs[0].sin_port);
lwkt_migratecpu(cpu);
inp = in_pcblookup_hash(&tcbinfo[cpu], addrs[1].sin_addr,
addrs[1].sin_port, addrs[0].sin_addr, addrs[0].sin_port, 0, NULL);
if (inp == NULL || inp->inp_socket == NULL) {
error = ENOENT;
} else if (inp->inp_socket->so_cred != NULL) {
cred0 = *(inp->inp_socket->so_cred);
cred = &cred0;
}
lwkt_migratecpu(origcpu);
if (error)
return (error);
return SYSCTL_OUT(req, cred, sizeof(struct ucred));
}
SYSCTL_PROC(_net_inet_tcp, OID_AUTO, getcred, (CTLTYPE_OPAQUE | CTLFLAG_RW),
0, 0, tcp_getcred, "S,ucred", "Get the ucred of a TCP connection");
#ifdef INET6
static int
tcp6_getcred(SYSCTL_HANDLER_ARGS)
{
struct sockaddr_in6 addrs[2];
struct inpcb *inp;
int error;
error = caps_priv_check_td(req->td, SYSCAP_RESTRICTEDROOT);
if (error != 0)
return (error);
error = SYSCTL_IN(req, addrs, sizeof addrs);
if (error != 0)
return (error);
crit_enter();
inp = in6_pcblookup_hash(&tcbinfo[0],
&addrs[1].sin6_addr, addrs[1].sin6_port,
&addrs[0].sin6_addr, addrs[0].sin6_port, 0, NULL);
if (inp == NULL || inp->inp_socket == NULL) {
error = ENOENT;
goto out;
}
error = SYSCTL_OUT(req, inp->inp_socket->so_cred, sizeof(struct ucred));
out:
crit_exit();
return (error);
}
SYSCTL_PROC(_net_inet6_tcp6, OID_AUTO, getcred, (CTLTYPE_OPAQUE | CTLFLAG_RW),
0, 0,
tcp6_getcred, "S,ucred", "Get the ucred of a TCP6 connection");
#endif
struct netmsg_tcp_notify {
struct netmsg_base base;
inp_notify_t nm_notify;
struct in_addr nm_faddr;
int nm_arg;
};
static void
tcp_notifyall_oncpu(netmsg_t msg)
{
struct netmsg_tcp_notify *nm = (struct netmsg_tcp_notify *)msg;
int nextcpu;
ASSERT_NETISR_NCPUS(mycpuid);
in_pcbnotifyall(&tcbinfo[mycpuid], nm->nm_faddr,
nm->nm_arg, nm->nm_notify);
nextcpu = mycpuid + 1;
if (nextcpu < netisr_ncpus)
lwkt_forwardmsg(netisr_cpuport(nextcpu), &nm->base.lmsg);
else
lwkt_replymsg(&nm->base.lmsg, 0);
}
inp_notify_t
tcp_get_inpnotify(int cmd, const struct sockaddr *sa,
int *arg, struct ip **ip0, int *cpuid)
{
struct ip *ip = *ip0;
struct in_addr faddr;
inp_notify_t notify = tcp_notify;
faddr = ((const struct sockaddr_in *)sa)->sin_addr;
if (sa->sa_family != AF_INET || faddr.s_addr == INADDR_ANY)
return NULL;
*arg = inetctlerrmap[cmd];
if (cmd == PRC_QUENCH) {
notify = tcp_quench;
} else if (icmp_may_rst &&
(cmd == PRC_UNREACH_ADMIN_PROHIB ||
cmd == PRC_UNREACH_PORT ||
cmd == PRC_TIMXCEED_INTRANS) &&
ip != NULL) {
notify = tcp_drop_syn_sent;
} else if (cmd == PRC_MSGSIZE) {
const struct icmp *icmp = (const struct icmp *)
((caddr_t)ip - offsetof(struct icmp, icmp_ip));
*arg = ntohs(icmp->icmp_nextmtu);
notify = tcp_mtudisc;
} else if (PRC_IS_REDIRECT(cmd)) {
ip = NULL;
notify = in_rtchange;
} else if (cmd == PRC_HOSTDEAD) {
ip = NULL;
} else if ((unsigned)cmd >= PRC_NCMDS || inetctlerrmap[cmd] == 0) {
return NULL;
}
if (cpuid != NULL) {
if (ip == NULL) {
*cpuid = netisr_ncpus;
} else {
const struct tcphdr *th;
th = (const struct tcphdr *)
((caddr_t)ip + (IP_VHL_HL(ip->ip_vhl) << 2));
*cpuid = tcp_addrcpu(faddr.s_addr, th->th_dport,
ip->ip_src.s_addr, th->th_sport);
}
}
*ip0 = ip;
return notify;
}
void
tcp_ctlinput(netmsg_t msg)
{
int cmd = msg->ctlinput.nm_cmd;
struct sockaddr *sa = msg->ctlinput.nm_arg;
struct ip *ip = msg->ctlinput.nm_extra;
struct in_addr faddr;
inp_notify_t notify;
int arg, cpuid;
ASSERT_NETISR_NCPUS(mycpuid);
notify = tcp_get_inpnotify(cmd, sa, &arg, &ip, &cpuid);
if (notify == NULL)
goto done;
faddr = ((struct sockaddr_in *)sa)->sin_addr;
if (ip != NULL) {
const struct tcphdr *th;
struct inpcb *inp;
if (cpuid != mycpuid)
goto done;
th = (const struct tcphdr *)
((caddr_t)ip + (IP_VHL_HL(ip->ip_vhl) << 2));
inp = in_pcblookup_hash(&tcbinfo[mycpuid], faddr, th->th_dport,
ip->ip_src, th->th_sport, 0, NULL);
if (inp != NULL && inp->inp_socket != NULL) {
tcp_seq icmpseq = htonl(th->th_seq);
struct tcpcb *tp = intotcpcb(inp);
if (SEQ_GEQ(icmpseq, tp->snd_una) &&
SEQ_LT(icmpseq, tp->snd_max))
notify(inp, arg);
} else {
struct in_conninfo inc;
inc.inc_fport = th->th_dport;
inc.inc_lport = th->th_sport;
inc.inc_faddr = faddr;
inc.inc_laddr = ip->ip_src;
#ifdef INET6
inc.inc_isipv6 = 0;
#endif
syncache_unreach(&inc, th);
}
} else if (msg->ctlinput.nm_direct) {
if (cpuid != netisr_ncpus && cpuid != mycpuid)
goto done;
in_pcbnotifyall(&tcbinfo[mycpuid], faddr, arg, notify);
} else {
struct netmsg_tcp_notify *nm;
ASSERT_NETISR0;
nm = kmalloc(sizeof(*nm), M_LWKTMSG, M_INTWAIT);
netmsg_init(&nm->base, NULL, &netisr_afree_rport,
0, tcp_notifyall_oncpu);
nm->nm_faddr = faddr;
nm->nm_arg = arg;
nm->nm_notify = notify;
lwkt_sendmsg(netisr_cpuport(0), &nm->base.lmsg);
}
done:
lwkt_replymsg(&msg->lmsg, 0);
}
#ifdef INET6
void
tcp6_ctlinput(netmsg_t msg)
{
int cmd = msg->ctlinput.nm_cmd;
struct sockaddr *sa = msg->ctlinput.nm_arg;
void *d = msg->ctlinput.nm_extra;
struct tcphdr th;
inp_notify_t notify = tcp_notify;
struct ip6_hdr *ip6;
struct mbuf *m;
struct ip6ctlparam *ip6cp = NULL;
const struct sockaddr_in6 *sa6_src = NULL;
int off;
struct tcp_portonly {
u_int16_t th_sport;
u_int16_t th_dport;
} *thp;
int arg;
if (sa->sa_family != AF_INET6 ||
sa->sa_len != sizeof(struct sockaddr_in6)) {
goto out;
}
arg = 0;
if (cmd == PRC_QUENCH)
notify = tcp_quench;
else if (cmd == PRC_MSGSIZE) {
struct ip6ctlparam *ip6cp = d;
if (ip6cp->ip6c_icmp6) {
struct icmp6_hdr *icmp6 = ip6cp->ip6c_icmp6;
arg = ntohl(icmp6->icmp6_mtu);
} else if (ip6cp->ip6c_cmdarg) {
arg = *(uint32_t *)ip6cp->ip6c_cmdarg;
} else {
goto out;
}
notify = tcp_mtudisc;
} else if (!PRC_IS_REDIRECT(cmd) &&
((unsigned)cmd > PRC_NCMDS || inet6ctlerrmap[cmd] == 0)) {
goto out;
}
if (d != NULL) {
ip6cp = (struct ip6ctlparam *)d;
m = ip6cp->ip6c_m;
ip6 = ip6cp->ip6c_ip6;
off = ip6cp->ip6c_off;
sa6_src = ip6cp->ip6c_src;
} else {
m = NULL;
ip6 = NULL;
off = 0;
sa6_src = &sa6_any;
}
if (ip6 != NULL) {
struct in_conninfo inc;
if (m->m_pkthdr.len < off + sizeof *thp)
goto out;
bzero(&th, sizeof th);
m_copydata(m, off, sizeof *thp, &th);
in6_pcbnotify(&tcbinfo[0], sa, th.th_dport,
(struct sockaddr *)ip6cp->ip6c_src,
th.th_sport, cmd, arg, notify);
inc.inc_fport = th.th_dport;
inc.inc_lport = th.th_sport;
inc.inc6_faddr = ((struct sockaddr_in6 *)sa)->sin6_addr;
inc.inc6_laddr = ip6cp->ip6c_src->sin6_addr;
inc.inc_isipv6 = 1;
syncache_unreach(&inc, &th);
} else {
in6_pcbnotify(&tcbinfo[0], sa, 0,
(const struct sockaddr *)sa6_src, 0, cmd, arg, notify);
}
out:
lwkt_replymsg(&msg->ctlinput.base.lmsg, 0);
}
#endif
struct tcp_isn {
u_char secret[16];
MD5_CTX ctx;
int last_reseed;
int last_offset;
} __cachealign;
struct tcp_isn tcp_isn_ary[MAXCPU];
tcp_seq
tcp_new_isn(struct tcpcb *tp)
{
struct tcp_isn *isn;
tcp_seq new_isn;
tcp_seq digest[16 / sizeof(tcp_seq)];
int n;
isn = &tcp_isn_ary[mycpuid];
if (isn->last_reseed == 0 ||
(u_int)(ticks - isn->last_reseed) > tcp_isn_reseed_interval * hz) {
if (isn->last_reseed == 0) {
read_random(&isn->last_offset,
sizeof(isn->last_offset), 1);
}
read_random(&isn->secret, sizeof(isn->secret), 1);
isn->last_reseed = ticks;
isn->last_offset += 0x10000000;
}
MD5Init(&isn->ctx);
MD5Update(&isn->ctx, isn->secret, sizeof(isn->secret));
MD5Update(&isn->ctx, (u_char *)&tp->t_inpcb->inp_fport, 2);
MD5Update(&isn->ctx, (u_char *)&tp->t_inpcb->inp_lport, 2);
#ifdef INET6
if (INP_ISIPV6(tp->t_inpcb)) {
MD5Update(&isn->ctx, (u_char *)&tp->t_inpcb->in6p_faddr,
sizeof(struct in6_addr));
MD5Update(&isn->ctx, (u_char *)&tp->t_inpcb->in6p_laddr,
sizeof(struct in6_addr));
} else
#endif
{
MD5Update(&isn->ctx, (u_char *)&tp->t_inpcb->inp_faddr,
sizeof(struct in_addr));
MD5Update(&isn->ctx, (u_char *)&tp->t_inpcb->inp_laddr,
sizeof(struct in_addr));
}
MD5Final((char *)digest, &isn->ctx);
read_random(&n, sizeof(n), 1);
isn->last_offset += (n & 0x000FFFFF) + 0x00100000;
new_isn = (digest[0] & 0x0FFFFFFF) + isn->last_offset;
return (new_isn);
}
void
tcp_quench(struct inpcb *inp, int error)
{
struct tcpcb *tp = intotcpcb(inp);
KASSERT(tp != NULL, ("tcp_quench: tp is NULL"));
tp->snd_cwnd = tp->t_maxseg;
tp->snd_wacked = 0;
}
void
tcp_drop_syn_sent(struct inpcb *inp, int error)
{
struct tcpcb *tp = intotcpcb(inp);
KASSERT(tp != NULL, ("tcp_drop_syn_sent: tp is NULL"));
if (tp->t_state == TCPS_SYN_SENT)
tcp_drop(tp, error);
}
void
tcp_mtudisc(struct inpcb *inp, int mtu)
{
struct tcpcb *tp = intotcpcb(inp);
struct rtentry *rt;
struct socket *so = inp->inp_socket;
int maxopd, mss;
#ifdef INET6
boolean_t isipv6 = INP_ISIPV6(inp);
#else
const boolean_t isipv6 = FALSE;
#endif
KASSERT(tp != NULL, ("tcp_mtudisc: tp is NULL"));
if (mtu == 0) {
int oldmtu;
oldmtu = tp->t_maxopd +
(isipv6 ?
sizeof(struct ip6_hdr) + sizeof(struct tcphdr) :
sizeof(struct tcpiphdr));
mtu = ip_next_mtu(oldmtu, 0);
}
if (isipv6)
rt = tcp_rtlookup6(&inp->inp_inc);
else
rt = tcp_rtlookup(&inp->inp_inc);
if (rt != NULL) {
if (rt->rt_rmx.rmx_mtu != 0 && rt->rt_rmx.rmx_mtu < mtu)
mtu = rt->rt_rmx.rmx_mtu;
maxopd = mtu -
(isipv6 ?
sizeof(struct ip6_hdr) + sizeof(struct tcphdr) :
sizeof(struct tcpiphdr));
if (rt->rt_rmx.rmx_mssopt && rt->rt_rmx.rmx_mssopt < maxopd)
maxopd = rt->rt_rmx.rmx_mssopt;
} else
maxopd = mtu -
(isipv6 ?
sizeof(struct ip6_hdr) + sizeof(struct tcphdr) :
sizeof(struct tcpiphdr));
if (tp->t_maxopd <= maxopd)
return;
tp->t_maxopd = maxopd;
mss = maxopd;
if ((tp->t_flags & (TF_REQ_TSTMP | TF_RCVD_TSTMP | TF_NOOPT)) ==
(TF_REQ_TSTMP | TF_RCVD_TSTMP))
mss -= TCPOLEN_TSTAMP_APPA;
#if (MCLBYTES & (MCLBYTES - 1)) == 0
if (mss > MCLBYTES)
mss &= ~(MCLBYTES - 1);
#else
if (mss > MCLBYTES)
mss = rounddown(mss, MCLBYTES);
#endif
if (so->so_snd.ssb_hiwat < mss)
mss = so->so_snd.ssb_hiwat;
tp->t_maxseg = mss;
tp->t_rtttime = 0;
tp->snd_nxt = tp->snd_una;
tcp_output(tp);
tcpstat.tcps_mturesent++;
}
struct rtentry *
tcp_rtlookup(struct in_conninfo *inc)
{
struct route *ro = &inc->inc_route;
if (ro->ro_rt == NULL || !(ro->ro_rt->rt_flags & RTF_UP)) {
if (inc->inc_faddr.s_addr != INADDR_ANY) {
bzero(&ro->ro_dst, sizeof(struct sockaddr_in));
ro->ro_dst.sa_family = AF_INET;
ro->ro_dst.sa_len = sizeof(struct sockaddr_in);
((struct sockaddr_in *) &ro->ro_dst)->sin_addr =
inc->inc_faddr;
rtalloc(ro);
}
}
return (ro->ro_rt);
}
#ifdef INET6
struct rtentry *
tcp_rtlookup6(struct in_conninfo *inc)
{
struct route_in6 *ro6 = &inc->inc6_route;
if (ro6->ro_rt == NULL || !(ro6->ro_rt->rt_flags & RTF_UP)) {
if (!IN6_IS_ADDR_UNSPECIFIED(&inc->inc6_faddr)) {
bzero(&ro6->ro_dst, sizeof(struct sockaddr_in6));
ro6->ro_dst.sin6_family = AF_INET6;
ro6->ro_dst.sin6_len = sizeof(struct sockaddr_in6);
ro6->ro_dst.sin6_addr = inc->inc6_faddr;
rtalloc((struct route *)ro6);
}
}
return (ro6->ro_rt);
}
#endif
void
tcp_xmit_bandwidth_limit(struct tcpcb *tp, tcp_seq ack_seq)
{
u_long bw;
u_long ibw;
u_long bwnd;
int save_ticks;
int delta_ticks;
if (!tcp_inflight_enable) {
tp->snd_bwnd = TCP_MAXWIN << TCP_MAX_WINSHIFT;
tp->snd_bandwidth = 0;
return;
}
save_ticks = ticks;
cpu_ccfence();
delta_ticks = save_ticks - tp->t_bw_rtttime;
if (tp->t_bw_rtttime == 0 || delta_ticks < 0 || delta_ticks > hz * 10) {
tp->t_bw_rtttime = save_ticks;
tp->t_bw_rtseq = ack_seq;
if (tp->snd_bandwidth == 0)
tp->snd_bandwidth = tcp_inflight_start;
return;
}
if (delta_ticks == 0 || delta_ticks == 1)
return;
if ((int)(ack_seq - tp->t_bw_rtseq) <= 0)
return;
ibw = (int64_t)(ack_seq - tp->t_bw_rtseq) * hz / delta_ticks;
tp->t_bw_rtttime = save_ticks;
tp->t_bw_rtseq = ack_seq;
bw = ((int64_t)tp->snd_bandwidth * 15 + ibw) >> 4;
tp->snd_bandwidth = bw;
#define USERTT ((tp->t_srtt + tp->t_rttvar) + tcp_inflight_adjrtt)
bw += bw * tcp_inflight_stab / 1000;
bwnd = (int64_t)bw * USERTT / (hz << TCP_RTT_SHIFT) +
(int)tp->t_maxseg * 2;
#undef USERTT
if (tcp_inflight_debug > 0) {
static int ltime;
if ((u_int)(save_ticks - ltime) >= hz / tcp_inflight_debug) {
ltime = save_ticks;
kprintf("%p ibw %ld bw %ld rttvar %d srtt %d "
"bwnd %ld delta %d snd_win %ld\n",
tp, ibw, bw, tp->t_rttvar, tp->t_srtt,
bwnd, delta_ticks, tp->snd_wnd);
}
}
if ((long)bwnd < tcp_inflight_min)
bwnd = tcp_inflight_min;
if (bwnd > tcp_inflight_max)
bwnd = tcp_inflight_max;
if ((long)bwnd < tp->t_maxseg * 2)
bwnd = tp->t_maxseg * 2;
tp->snd_bwnd = bwnd;
}
static void
tcp_rmx_iwsegs(struct tcpcb *tp, u_long *maxsegs, u_long *capsegs)
{
struct rtentry *rt;
struct inpcb *inp = tp->t_inpcb;
#ifdef INET6
boolean_t isipv6 = INP_ISIPV6(inp);
#else
const boolean_t isipv6 = FALSE;
#endif
if (tcp_iw_maxsegs < TCP_IW_MAXSEGS_DFLT)
tcp_iw_maxsegs = TCP_IW_MAXSEGS_DFLT;
if (tcp_iw_capsegs < TCP_IW_CAPSEGS_DFLT)
tcp_iw_capsegs = TCP_IW_CAPSEGS_DFLT;
if (isipv6)
rt = tcp_rtlookup6(&inp->inp_inc);
else
rt = tcp_rtlookup(&inp->inp_inc);
if (rt == NULL ||
rt->rt_rmx.rmx_iwmaxsegs < TCP_IW_MAXSEGS_DFLT ||
rt->rt_rmx.rmx_iwcapsegs < TCP_IW_CAPSEGS_DFLT) {
*maxsegs = tcp_iw_maxsegs;
*capsegs = tcp_iw_capsegs;
return;
}
*maxsegs = rt->rt_rmx.rmx_iwmaxsegs;
*capsegs = rt->rt_rmx.rmx_iwcapsegs;
}
u_long
tcp_initial_window(struct tcpcb *tp)
{
if (tcp_do_rfc3390) {
if (tp->t_rxtsyn >= TCPTV_RTOBASE3) {
return (2 * tp->t_maxseg);
} else {
u_long maxsegs, capsegs;
tcp_rmx_iwsegs(tp, &maxsegs, &capsegs);
return min(maxsegs * tp->t_maxseg,
max(2 * tp->t_maxseg, capsegs * 1460));
}
} else {
return (2 * tp->t_maxseg);
}
}
#ifdef TCP_SIGNATURE
int
tcpsignature_compute(
struct mbuf *m,
int len,
int optlen,
u_char *buf,
u_int direction)
{
struct ippseudo ippseudo;
MD5_CTX ctx;
int doff;
struct ip *ip;
struct ipovly *ipovly;
struct secasvar *sav;
struct tcphdr *th;
#ifdef INET6
struct ip6_hdr *ip6;
struct in6_addr in6;
uint32_t plen;
uint16_t nhdr;
#endif
u_short savecsum;
KASSERT(m != NULL, ("passed NULL mbuf. Game over."));
KASSERT(buf != NULL, ("passed NULL storage pointer for MD5 signature"));
ip = mtod(m, struct ip *);
#ifdef INET6
ip6 = NULL;
#endif
switch (IP_VHL_V(ip->ip_vhl)) {
case IPVERSION:
sav = key_allocsa(AF_INET, (caddr_t)&ip->ip_src, (caddr_t)&ip->ip_dst,
IPPROTO_TCP, htonl(TCP_SIG_SPI));
break;
#ifdef INET6
case (IPV6_VERSION >> 4):
ip6 = mtod(m, struct ip6_hdr *);
sav = key_allocsa(AF_INET6, (caddr_t)&ip6->ip6_src, (caddr_t)&ip6->ip6_dst,
IPPROTO_TCP, htonl(TCP_SIG_SPI));
break;
#endif
default:
return (EINVAL);
break;
}
if (sav == NULL) {
kprintf("%s: SADB lookup failed\n", __func__);
return (EINVAL);
}
MD5Init(&ctx);
switch (IP_VHL_V(ip->ip_vhl)) {
case IPVERSION:
ipovly = (struct ipovly *)ip;
ippseudo.ippseudo_src = ipovly->ih_src;
ippseudo.ippseudo_dst = ipovly->ih_dst;
ippseudo.ippseudo_pad = 0;
ippseudo.ippseudo_p = IPPROTO_TCP;
ippseudo.ippseudo_len = htons(len + sizeof(struct tcphdr) + optlen);
MD5Update(&ctx, (char *)&ippseudo, sizeof(struct ippseudo));
th = (struct tcphdr *)((u_char *)ip + sizeof(struct ip));
doff = sizeof(struct ip) + sizeof(struct tcphdr) + optlen;
break;
#ifdef INET6
case (IPV6_VERSION >> 4):
in6 = ip6->ip6_src;
in6_clearscope(&in6);
MD5Update(&ctx, (char *)&in6, sizeof(struct in6_addr));
in6 = ip6->ip6_dst;
in6_clearscope(&in6);
MD5Update(&ctx, (char *)&in6, sizeof(struct in6_addr));
plen = htonl(len + sizeof(struct tcphdr) + optlen);
MD5Update(&ctx, (char *)&plen, sizeof(uint32_t));
nhdr = 0;
MD5Update(&ctx, (char *)&nhdr, sizeof(uint8_t));
MD5Update(&ctx, (char *)&nhdr, sizeof(uint8_t));
MD5Update(&ctx, (char *)&nhdr, sizeof(uint8_t));
nhdr = IPPROTO_TCP;
MD5Update(&ctx, (char *)&nhdr, sizeof(uint8_t));
th = (struct tcphdr *)((u_char *)ip6 + sizeof(struct ip6_hdr));
doff = sizeof(struct ip6_hdr) + sizeof(struct tcphdr) + optlen;
break;
#endif
default:
return (EINVAL);
break;
}
savecsum = th->th_sum;
th->th_sum = 0;
MD5Update(&ctx, (char *)th, sizeof(struct tcphdr));
th->th_sum = savecsum;
if (len > 0)
m_apply(m, doff, len, tcpsignature_apply, &ctx);
MD5Update(&ctx, _KEYBUF(sav->key_auth), _KEYLEN(sav->key_auth));
MD5Final(buf, &ctx);
key_sa_recordxfer(sav, m);
key_freesav(sav);
return (0);
}
int
tcpsignature_apply(void *fstate, void *data, unsigned int len)
{
MD5Update((MD5_CTX *)fstate, (unsigned char *)data, len);
return (0);
}
#endif
static void
tcp_drop_sysctl_dispatch(netmsg_t nmsg)
{
struct lwkt_msg *lmsg = &nmsg->lmsg;
struct sockaddr_storage *addrs = lmsg->u.ms_resultp;
int error;
struct sockaddr_in *fin, *lin;
#ifdef INET6
struct sockaddr_in6 *fin6, *lin6;
struct in6_addr f6, l6;
#endif
struct inpcb *inp;
switch (addrs[0].ss_family) {
#ifdef INET6
case AF_INET6:
fin6 = (struct sockaddr_in6 *)&addrs[0];
lin6 = (struct sockaddr_in6 *)&addrs[1];
error = in6_embedscope(&f6, fin6, NULL, NULL);
if (error)
goto done;
error = in6_embedscope(&l6, lin6, NULL, NULL);
if (error)
goto done;
inp = in6_pcblookup_hash(&tcbinfo[mycpuid], &f6,
fin6->sin6_port, &l6, lin6->sin6_port, FALSE, NULL);
break;
#endif
#ifdef INET
case AF_INET:
fin = (struct sockaddr_in *)&addrs[0];
lin = (struct sockaddr_in *)&addrs[1];
inp = in_pcblookup_hash(&tcbinfo[mycpuid], fin->sin_addr,
fin->sin_port, lin->sin_addr, lin->sin_port, FALSE, NULL);
break;
#endif
default:
panic("unknown address family %d", addrs[0].ss_family);
}
if (inp != NULL) {
struct tcpcb *tp = intotcpcb(inp);
KASSERT((inp->inp_flags & INP_WILDCARD) == 0,
("in wildcard hash"));
KASSERT(tp != NULL, ("tcp_drop_sysctl_dispatch: tp is NULL"));
KASSERT((tp->t_flags & TF_LISTEN) == 0, ("listen socket"));
tcp_drop(tp, ECONNABORTED);
error = 0;
} else {
error = ESRCH;
}
#ifdef INET6
done:
#endif
lwkt_replymsg(lmsg, error);
}
static int
sysctl_tcp_drop(SYSCTL_HANDLER_ARGS)
{
struct sockaddr_storage addrs[2];
struct sockaddr_in *fin, *lin;
#ifdef INET6
struct sockaddr_in6 *fin6, *lin6;
#endif
struct netmsg_base nmsg;
struct lwkt_msg *lmsg = &nmsg.lmsg;
struct lwkt_port *port = NULL;
int error;
fin = lin = NULL;
#ifdef INET6
fin6 = lin6 = NULL;
#endif
error = 0;
if (req->oldptr != NULL || req->oldlen != 0)
return (EINVAL);
if (req->newptr == NULL)
return (EPERM);
if (req->newlen < sizeof(addrs))
return (ENOMEM);
error = SYSCTL_IN(req, &addrs, sizeof(addrs));
if (error)
return (error);
switch (addrs[0].ss_family) {
#ifdef INET6
case AF_INET6:
fin6 = (struct sockaddr_in6 *)&addrs[0];
lin6 = (struct sockaddr_in6 *)&addrs[1];
if (fin6->sin6_len != sizeof(struct sockaddr_in6) ||
lin6->sin6_len != sizeof(struct sockaddr_in6))
return (EINVAL);
if (IN6_IS_ADDR_V4MAPPED(&fin6->sin6_addr) ||
IN6_IS_ADDR_V4MAPPED(&lin6->sin6_addr))
return (EADDRNOTAVAIL);
#if 0
error = sa6_embedscope(fin6, V_ip6_use_defzone);
if (error)
return (error);
error = sa6_embedscope(lin6, V_ip6_use_defzone);
if (error)
return (error);
#endif
port = tcp6_addrport();
break;
#endif
#ifdef INET
case AF_INET:
fin = (struct sockaddr_in *)&addrs[0];
lin = (struct sockaddr_in *)&addrs[1];
if (fin->sin_len != sizeof(struct sockaddr_in) ||
lin->sin_len != sizeof(struct sockaddr_in))
return (EINVAL);
port = tcp_addrport(fin->sin_addr.s_addr, fin->sin_port,
lin->sin_addr.s_addr, lin->sin_port);
break;
#endif
default:
return (EINVAL);
}
netmsg_init(&nmsg, NULL, &curthread->td_msgport, 0,
tcp_drop_sysctl_dispatch);
lmsg->u.ms_resultp = addrs;
return lwkt_domsg(port, lmsg, 0);
}
SYSCTL_PROC(_net_inet_tcp, OID_AUTO, drop,
CTLTYPE_STRUCT | CTLFLAG_WR | CTLFLAG_SKIP, NULL,
0, sysctl_tcp_drop, "", "Drop TCP connection");
static int
sysctl_tcps_count(SYSCTL_HANDLER_ARGS)
{
u_long state_count[TCP_NSTATES];
int cpu;
memset(state_count, 0, sizeof(state_count));
for (cpu = 0; cpu < netisr_ncpus; ++cpu) {
int i;
for (i = 0; i < TCP_NSTATES; ++i)
state_count[i] += tcpstate_count[cpu].tcps_count[i];
}
return sysctl_handle_opaque(oidp, state_count, sizeof(state_count), req);
}
SYSCTL_PROC(_net_inet_tcp, OID_AUTO, state_count,
CTLTYPE_OPAQUE | CTLFLAG_RD, NULL, 0,
sysctl_tcps_count, "LU", "TCP connection counts by state");
void
tcp_pcbport_create(struct tcpcb *tp)
{
int cpu;
KASSERT((tp->t_flags & TF_LISTEN) && tp->t_state == TCPS_LISTEN,
("not a listen tcpcb"));
KASSERT(tp->t_pcbport == NULL, ("tcpcb port cache was created"));
tp->t_pcbport =
kmalloc(sizeof(struct tcp_pcbport) * netisr_ncpus,
M_PCB,
M_WAITOK | M_CACHEALIGN);
for (cpu = 0; cpu < netisr_ncpus; ++cpu) {
struct inpcbport *phd;
phd = &tp->t_pcbport[cpu].t_phd;
LIST_INIT(&phd->phd_pcblist);
phd->phd_port = tp->t_inpcb->inp_lport;
}
}
void
tcp_pcbport_merge_oncpu(struct tcpcb *tp)
{
struct inpcbport *phd;
struct inpcb *inp;
int cpu = mycpuid;
KASSERT(cpu < netisr_ncpus, ("invalid cpu%d", cpu));
phd = &tp->t_pcbport[cpu].t_phd;
while ((inp = LIST_FIRST(&phd->phd_pcblist)) != NULL) {
KASSERT(inp->inp_phd == phd && inp->inp_porthash == NULL,
("not on tcpcb port cache"));
LIST_REMOVE(inp, inp_portlist);
in_pcbinsporthash_lport(inp);
KASSERT(inp->inp_phd == tp->t_inpcb->inp_phd &&
inp->inp_porthash == tp->t_inpcb->inp_porthash,
("tcpcb port cache merge failed"));
}
}
void
tcp_pcbport_destroy(struct tcpcb *tp)
{
#ifdef INVARIANTS
int cpu;
for (cpu = 0; cpu < netisr_ncpus; ++cpu) {
KASSERT(LIST_EMPTY(&tp->t_pcbport[cpu].t_phd.phd_pcblist),
("tcpcb port cache is not empty"));
}
#endif
kfree(tp->t_pcbport, M_PCB);
tp->t_pcbport = NULL;
}