Index: netinet/tcp_subr.c =================================================================== --- netinet/tcp_subr.c (revision 212549) +++ netinet/tcp_subr.c (working copy) @@ -876,6 +876,27 @@ } /* + * Remove the fusion of two socket buffer to let TCP + * handle the remainder of the connection shutdown + * phase. + */ +void +tcp_defuse(struct socket *so, struct inpcb *inp) +{ + struct inpcb *finp = (struct inpcb *)inp->inp_pspare[0]; + + INP_WLOCK(finp); + if (finp->inp_socket != NULL) + finp->inp_socket->so_snd.sb_cc = 0; + so->so_snd.sb_cc = 0; + finp->inp_flags &= ~INP_FUSED; + inp->inp_flags &= ~INP_FUSED; + finp->inp_pspare[0] = NULL; + inp->inp_pspare[0] = NULL; + INP_WUNLOCK(finp); +} + +/* * Attempt to close a TCP control block, marking it as dropped, and freeing * the socket if we hold the only reference. */ @@ -896,6 +917,10 @@ KASSERT(inp->inp_socket != NULL, ("tcp_close: inp_socket NULL")); so = inp->inp_socket; soisdisconnected(so); + + if (inp->inp_flags & INP_FUSED) + tcp_defuse(so, inp); + if (inp->inp_flags & INP_SOCKREF) { KASSERT(so->so_state & SS_PROTOREF, ("tcp_close: !SS_PROTOREF")); Index: netinet/in_pcb.h =================================================================== --- netinet/in_pcb.h (revision 212549) +++ netinet/in_pcb.h (working copy) @@ -414,6 +414,7 @@ #define INP_DONTFRAG 0x00000800 /* don't fragment packet */ #define INP_BINDANY 0x00001000 /* allow bind to any address */ #define INP_INHASHLIST 0x00002000 /* in_pcbinshash() has been called */ +#define INP_FUSED 0x00004000 /* inp's fused together for IPC */ #define IN6P_IPV6_V6ONLY 0x00008000 /* restrict AF_INET6 socket for v6 */ #define IN6P_PKTINFO 0x00010000 /* receive IP6 dst and I/F */ #define IN6P_HOPLIMIT 0x00020000 /* receive hoplimit */ Index: netinet/tcp_var.h =================================================================== --- netinet/tcp_var.h (revision 212549) +++ netinet/tcp_var.h (working copy) @@ -595,6 +595,7 @@ int tcp_addoptions(struct tcpopt *, u_char *); struct tcpcb * tcp_close(struct tcpcb *); +void tcp_defuse(struct socket *, struct inpcb *); void tcp_discardcb(struct tcpcb *); void tcp_twstart(struct tcpcb *); #if 0 Index: netinet/tcp_syncache.c =================================================================== --- netinet/tcp_syncache.c (revision 212549) +++ netinet/tcp_syncache.c (working copy) @@ -109,6 +109,10 @@ &VNET_NAME(tcp_syncookiesonly), 0, "Use only TCP SYN cookies"); +static int tcp_loopfuse = 1; +SYSCTL_INT(_net_inet_tcp, OID_AUTO, loopfuse, CTLFLAG_RW, + &tcp_loopfuse, 0, "Allow looped back connections to fuse sockets"); + #ifdef TCP_OFFLOAD_DISABLE #define TOEPCB_ISSET(sc) (0) #else @@ -829,6 +833,31 @@ tp->snd_cwnd = tp->t_maxseg; tcp_timer_activate(tp, TT_KEEP, tcp_keepinit); + /* + * For connections that go are looped back the socket + * buffers can be directly merged together instead of + * sending TCP segments through the loopback interface. + * Perform a lookup on the other INP and if found, set + * up crossover pointers to the sockets and set the flag. + */ + if (tcp_loopfuse && in_localaddr(sc->sc_inc.inc_faddr)) { + struct inpcb *finp; + + finp = in_pcblookup_hash(&V_tcbinfo, + sc->sc_inc.inc_laddr, sc->sc_inc.inc_lport, + sc->sc_inc.inc_faddr, sc->sc_inc.inc_fport, + 0, m->m_pkthdr.rcvif); + + if (finp != NULL) { + INP_WLOCK(finp); + finp->inp_pspare[0] = (void *)inp; + inp->inp_pspare[0] = (void *)finp; + finp->inp_flags |= INP_FUSED; + inp->inp_flags |= INP_FUSED; + INP_WUNLOCK(finp); + } + } + INP_WUNLOCK(inp); TCPSTAT_INC(tcps_accepts); Index: netinet/tcp_usrreq.c =================================================================== --- netinet/tcp_usrreq.c (revision 212549) +++ netinet/tcp_usrreq.c (working copy) @@ -718,8 +718,21 @@ } tp = intotcpcb(inp); TCPDEBUG1(); + + /* + * When fused directly inform the other socket buffer + * that we dequeued some data. + */ + if (inp->inp_flags & INP_FUSED) { + struct inpcb *finp = (struct inpcb *)inp->inp_pspare[0]; + + SOCKBUF_LOCK(&finp->inp_socket->so_snd); + finp->inp_socket->so_snd.sb_cc = so->so_rcv.sb_cc; + sowwakeup_locked(finp->inp_socket); + goto out; + } + tcp_output_rcvd(tp); - out: TCPDEBUG2(PRU_RCVD); INP_WUNLOCK(inp); @@ -774,6 +787,20 @@ isipv6 = nam && nam->sa_family == AF_INET6; #endif /* INET6 */ tp = intotcpcb(inp); + + /* + * Drop it directly into the receive buffer on the other side. + */ + if (inp->inp_flags & INP_FUSED) { + struct inpcb *finp = (struct inpcb *)inp->inp_pspare[0]; + + SOCKBUF_LOCK(&finp->inp_socket->so_rcv); + sbappendstream_locked(&finp->inp_socket->so_rcv, m); + inp->inp_socket->so_snd.sb_cc = finp->inp_socket->so_rcv.sb_cc; + sorwakeup_locked(finp->inp_socket); + goto out; + } + TCPDEBUG1(); if (control) { /* TCP doesn't do control messages (rights, creds, etc) */ @@ -1327,7 +1354,8 @@ tp->t_flags |= TF_NOPUSH; else { tp->t_flags &= ~TF_NOPUSH; - error = tcp_output(tp); + if (!(inp->inp_flags & INP_FUSED)) + error = tcp_output(tp); } INP_WUNLOCK(inp); break; @@ -1499,8 +1527,11 @@ soisdisconnecting(so); sbflush(&so->so_rcv); tcp_usrclosed(tp); - if (!(inp->inp_flags & INP_DROPPED)) + if (!(inp->inp_flags & INP_DROPPED)) { + if (inp->inp_flags & INP_FUSED) + tcp_defuse(so, inp); tcp_output_disconnect(tp); + } } }