git: 5bba2728079e - main - sockets: make pr_shutdown fully protocol specific method
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Tue, 16 Jan 2024 18:31:34 UTC
The branch main has been updated by glebius: URL: https://cgit.FreeBSD.org/src/commit/?id=5bba2728079ed4da33f727dbc2b6ae1de02ba897 commit 5bba2728079ed4da33f727dbc2b6ae1de02ba897 Author: Gleb Smirnoff <glebius@FreeBSD.org> AuthorDate: 2024-01-16 18:26:10 +0000 Commit: Gleb Smirnoff <glebius@FreeBSD.org> CommitDate: 2024-01-16 18:30:37 +0000 sockets: make pr_shutdown fully protocol specific method Disassemble a one-for-all soshutdown() into protocol specific methods. This creates a small amount of copy & paste, but makes code a lot more self documented, as protocol specific method would execute only the code that is relevant to that protocol and nothing else. This also fixes a couple recent regressions and reduces risk of future regressions. The extended KPI for the new pr_shutdown removes need for the extra pr_flush which was added for the sake of SCTP which could not perform its shutdown properly with the old one. Particularly for SCTP this change streamlines a lot of code. Some notes on why certain parts of code were copied or were not to certain protocols: * The (SS_ISCONNECTED | SS_ISCONNECTING | SS_ISDISCONNECTING) check is needed only for those protocols that may be connected or disconnected. * The above reduces into only SS_ISCONNECTED for those protocols that always connect instantly. * The ENOTCONN and continue processing hack is left only for datagram protocols. * The SOLISTENING(so) block is copied to those protocols that listen(2). * sorflush() on SHUT_RD is copied almost to every protocol, but that will be refactored later. * wakeup(&so->so_timeo) is copied to protocols that can make a non-instant connect(2), can SO_LINGER or can accept(2). There are three protocols (netgraph(4), Bluetooth, SDP) that did not have pr_shutdown, but old soshutdown() would still perform sorflush() on SHUT_RD for them and also wakeup(9). Those protocols partially supported shutdown(2) returning EOPNOTSUP for SHUT_WR/SHUT_RDWR, now they fully lost shutdown(2) support. I'm pretty sure netgraph(4) and Bluetooth are okay about that and SDP is almost abandoned anyway. Reviewed by: tuexen Differential Revision: https://reviews.freebsd.org/D43413 --- sys/dev/hyperv/hvsock/hv_sock.c | 46 +++++----- sys/dev/hyperv/hvsock/hv_sock.h | 2 +- sys/kern/uipc_domain.c | 2 +- sys/kern/uipc_socket.c | 53 ++--------- sys/kern/uipc_usrreq.c | 65 ++++++++++++-- sys/net/rtsock.c | 16 +++- sys/netinet/raw_ip.c | 25 ++++-- sys/netinet/sctp_usrreq.c | 195 +++++++++++++++++++--------------------- sys/netinet/sctp_var.h | 2 +- sys/netinet/tcp_usrreq.c | 64 +++++++++---- sys/netinet/udp_usrreq.c | 42 +++++++-- sys/netinet/udp_var.h | 2 +- sys/netinet6/raw_ip6.c | 25 ++++-- sys/netinet6/sctp6_usrreq.c | 1 - sys/sys/protosw.h | 5 +- sys/sys/socket.h | 8 -- 16 files changed, 313 insertions(+), 240 deletions(-) diff --git a/sys/dev/hyperv/hvsock/hv_sock.c b/sys/dev/hyperv/hvsock/hv_sock.c index df6f58f6fcb6..8072765f2d5b 100644 --- a/sys/dev/hyperv/hvsock/hv_sock.c +++ b/sys/dev/hyperv/hvsock/hv_sock.c @@ -978,43 +978,43 @@ hvs_trans_abort(struct socket *so) } int -hvs_trans_shutdown(struct socket *so) +hvs_trans_shutdown(struct socket *so, enum shutdown_how how) { struct hvs_pcb *pcb = so2hvspcb(so); - struct sockbuf *sb; HVSOCK_DBG(HVSOCK_DBG_VERBOSE, "%s: HyperV Socket hvs_trans_shutdown called\n", __func__); + SOCK_LOCK(so); + if ((so->so_state & + (SS_ISCONNECTED | SS_ISCONNECTING | SS_ISDISCONNECTING)) == 0) { + SOCK_UNLOCK(so); + return (ENOTCONN); + } + SOCK_UNLOCK(so); + if (pcb == NULL) return (EINVAL); - /* - * Only get called with the shutdown method is SHUT_WR or - * SHUT_RDWR. - * When the method is SHUT_RD or SHUT_RDWR, the caller - * already set the SBS_CANTRCVMORE on receive side socket - * buffer. - */ - if ((so->so_rcv.sb_state & SBS_CANTRCVMORE) == 0) { - /* - * SHUT_WR only case. - * Receive side is still open. Just close - * the send side. - */ - socantsendmore(so); - } else { - /* SHUT_RDWR case */ + switch (how) { + case SHUT_RD: + socantrcvmore(so); + break; + case SHUT_RDWR: + socantrcvmore(so); if (so->so_state & SS_ISCONNECTED) { /* Send a FIN to peer */ - sb = &so->so_snd; - SOCKBUF_LOCK(sb); - (void) hvsock_send_data(pcb->chan, NULL, 0, sb); - SOCKBUF_UNLOCK(sb); - + SOCK_SENDBUF_LOCK(so); + (void) hvsock_send_data(pcb->chan, NULL, 0, + &so->so_snd); + SOCK_SENDBUF_UNLOCK(so); soisdisconnecting(so); } + /* FALLTHROUGH */ + case SHUT_WR: + socantsendmore(so); } + wakeup(&so->so_timeo); return (0); } diff --git a/sys/dev/hyperv/hvsock/hv_sock.h b/sys/dev/hyperv/hvsock/hv_sock.h index e11621d76dbc..32a6e71640a4 100644 --- a/sys/dev/hyperv/hvsock/hv_sock.h +++ b/sys/dev/hyperv/hvsock/hv_sock.h @@ -110,7 +110,7 @@ int hvs_trans_soreceive(struct socket *, struct sockaddr **, int hvs_trans_sosend(struct socket *, struct sockaddr *, struct uio *, struct mbuf *, struct mbuf *, int, struct thread *); int hvs_trans_disconnect(struct socket *); -int hvs_trans_shutdown(struct socket *); +int hvs_trans_shutdown(struct socket *, enum shutdown_how); int hvs_trans_lock(void); void hvs_trans_unlock(void); diff --git a/sys/kern/uipc_domain.c b/sys/kern/uipc_domain.c index 435b13842041..ab00bf2bc71f 100644 --- a/sys/kern/uipc_domain.c +++ b/sys/kern/uipc_domain.c @@ -151,7 +151,7 @@ pr_ready_notsupp(struct socket *so, struct mbuf *m, int count) } static int -pr_shutdown_notsupp(struct socket *so) +pr_shutdown_notsupp(struct socket *so, enum shutdown_how how) { return (EOPNOTSUPP); } diff --git a/sys/kern/uipc_socket.c b/sys/kern/uipc_socket.c index 919879e86e21..f61016d14e53 100644 --- a/sys/kern/uipc_socket.c +++ b/sys/kern/uipc_socket.c @@ -2966,59 +2966,18 @@ soreceive(struct socket *so, struct sockaddr **psa, struct uio *uio, int soshutdown(struct socket *so, enum shutdown_how how) { - struct protosw *pr; - int error, soerror_enotconn; - - soerror_enotconn = 0; - SOCK_LOCK(so); - if ((so->so_state & - (SS_ISCONNECTED | SS_ISCONNECTING | SS_ISDISCONNECTING)) == 0) { - /* - * POSIX mandates us to return ENOTCONN when shutdown(2) is - * invoked on a datagram sockets, however historically we would - * actually tear socket down. This is known to be leveraged by - * some applications to unblock process waiting in recvXXX(2) - * by other process that it shares that socket with. Try to meet - * both backward-compatibility and POSIX requirements by forcing - * ENOTCONN but still asking protocol to perform pru_shutdown(). - */ - if (so->so_type != SOCK_DGRAM && !SOLISTENING(so)) { - SOCK_UNLOCK(so); - return (ENOTCONN); - } - soerror_enotconn = 1; - } - - if (SOLISTENING(so)) { - if (how != SHUT_WR) { - so->so_error = ECONNABORTED; - solisten_wakeup(so); /* unlocks so */ - } else { - SOCK_UNLOCK(so); - } - goto done; - } - SOCK_UNLOCK(so); + int error; CURVNET_SET(so->so_vnet); - pr = so->so_proto; - if (pr->pr_flush != NULL) - pr->pr_flush(so, how); - if (how != SHUT_WR && !(pr->pr_flags & PR_SOCKBUF)) - sorflush(so); - if (how != SHUT_RD) { - error = pr->pr_shutdown(so); - wakeup(&so->so_timeo); - CURVNET_RESTORE(); - return ((error == 0 && soerror_enotconn) ? ENOTCONN : error); - } - wakeup(&so->so_timeo); + error = so->so_proto->pr_shutdown(so, how); CURVNET_RESTORE(); -done: - return (soerror_enotconn ? ENOTCONN : 0); + return (error); } +/* + * Used by several pr_shutdown implementations that use generic socket buffers. + */ void sorflush(struct socket *so) { diff --git a/sys/kern/uipc_usrreq.c b/sys/kern/uipc_usrreq.c index 8f5560e0f30b..0460d2761e7c 100644 --- a/sys/kern/uipc_usrreq.c +++ b/sys/kern/uipc_usrreq.c @@ -1660,18 +1660,65 @@ uipc_sense(struct socket *so, struct stat *sb) } static int -uipc_shutdown(struct socket *so) +uipc_shutdown(struct socket *so, enum shutdown_how how) { - struct unpcb *unp; + struct unpcb *unp = sotounpcb(so); + int error; - unp = sotounpcb(so); - KASSERT(unp != NULL, ("uipc_shutdown: unp == NULL")); + SOCK_LOCK(so); + if ((so->so_state & + (SS_ISCONNECTED | SS_ISCONNECTING | SS_ISDISCONNECTING)) == 0) { + /* + * POSIX mandates us to just return ENOTCONN when shutdown(2) is + * invoked on a datagram sockets, however historically we would + * actually tear socket down. This is known to be leveraged by + * some applications to unblock process waiting in recv(2) by + * other process that it shares that socket with. Try to meet + * both backward-compatibility and POSIX requirements by forcing + * ENOTCONN but still flushing buffers and performing wakeup(9). + * + * XXXGL: it remains unknown what applications expect this + * behavior and is this isolated to unix/dgram or inet/dgram or + * both. See: D10351, D3039. + */ + error = ENOTCONN; + if (so->so_type != SOCK_DGRAM) { + SOCK_UNLOCK(so); + return (error); + } + } else + error = 0; + if (SOLISTENING(so)) { + if (how != SHUT_WR) { + so->so_error = ECONNABORTED; + solisten_wakeup(so); /* unlocks so */ + } else + SOCK_UNLOCK(so); + return (0); + } + SOCK_UNLOCK(so); - UNP_PCB_LOCK(unp); - socantsendmore(so); - unp_shutdown(unp); - UNP_PCB_UNLOCK(unp); - return (0); + switch (how) { + case SHUT_RD: + /* + * XXXGL: so far it is safe to call sorflush() on unix/dgram, + * because PR_RIGHTS flag saves us from destructive sbrelease() + * on our protocol specific buffers. + */ + sorflush(so); + break; + case SHUT_RDWR: + sorflush(so); + /* FALLTHROUGH */ + case SHUT_WR: + UNP_PCB_LOCK(unp); + socantsendmore(so); + unp_shutdown(unp); + UNP_PCB_UNLOCK(unp); + } + wakeup(&so->so_timeo); + + return (error); } static int diff --git a/sys/net/rtsock.c b/sys/net/rtsock.c index dea6a8f23cad..94d5e9e4bccc 100644 --- a/sys/net/rtsock.c +++ b/sys/net/rtsock.c @@ -450,10 +450,22 @@ rts_disconnect(struct socket *so) } static int -rts_shutdown(struct socket *so) +rts_shutdown(struct socket *so, enum shutdown_how how) { + /* + * Note: route socket marks itself as connected through its lifetime. + */ + switch (how) { + case SHUT_RD: + sorflush(so); + break; + case SHUT_RDWR: + sorflush(so); + /* FALLTHROUGH */ + case SHUT_WR: + socantsendmore(so); + } - socantsendmore(so); return (0); } diff --git a/sys/netinet/raw_ip.c b/sys/netinet/raw_ip.c index 4a61e685d898..a6bef1c7e275 100644 --- a/sys/netinet/raw_ip.c +++ b/sys/netinet/raw_ip.c @@ -982,16 +982,27 @@ rip_connect(struct socket *so, struct sockaddr *nam, struct thread *td) } static int -rip_shutdown(struct socket *so) +rip_shutdown(struct socket *so, enum shutdown_how how) { - struct inpcb *inp; - inp = sotoinpcb(so); - KASSERT(inp != NULL, ("rip_shutdown: inp == NULL")); + SOCK_LOCK(so); + if (!(so->so_state & SS_ISCONNECTED)) { + SOCK_UNLOCK(so); + return (ENOTCONN); + } + SOCK_UNLOCK(so); + + switch (how) { + case SHUT_RD: + sorflush(so); + break; + case SHUT_RDWR: + sorflush(so); + /* FALLTHROUGH */ + case SHUT_WR: + socantsendmore(so); + } - INP_WLOCK(inp); - socantsendmore(so); - INP_WUNLOCK(inp); return (0); } #endif /* INET */ diff --git a/sys/netinet/sctp_usrreq.c b/sys/netinet/sctp_usrreq.c index 7fa4559108fd..ec9f211b519b 100644 --- a/sys/netinet/sctp_usrreq.c +++ b/sys/netinet/sctp_usrreq.c @@ -775,14 +775,39 @@ sctp_disconnect(struct socket *so) } int -sctp_flush(struct socket *so, int how) +sctp_shutdown(struct socket *so, enum shutdown_how how) { + struct sctp_inpcb *inp = (struct sctp_inpcb *)so->so_pcb; struct epoch_tracker et; struct sctp_tcb *stcb; + struct sctp_association *asoc; + struct sctp_nets *netp; struct sctp_queued_to_read *control, *ncontrol; - struct sctp_inpcb *inp; struct mbuf *m, *op_err; bool need_to_abort = false; + int error = 0; + + MPASS(inp); + + if (!((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) || + (inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL))) + return (EOPNOTSUPP); + + SOCK_LOCK(so); + if ((so->so_state & + (SS_ISCONNECTED | SS_ISCONNECTING | SS_ISDISCONNECTING)) == 0) { + SOCK_UNLOCK(so); + return (ENOTCONN); + } + if (SOLISTENING(so)) { + if (how != SHUT_WR) { + so->so_error = ECONNABORTED; + solisten_wakeup(so); /* unlocks so */ + } else + SOCK_UNLOCK(so); + return (0); + } + SOCK_UNLOCK(so); /* * For 1-to-1 style sockets, flush the read queue and trigger an @@ -790,106 +815,70 @@ sctp_flush(struct socket *so, int how) * messages are lost. Loosing notifications does not need to be * signalled to the peer. */ - if (how == PRU_FLUSH_WR) { - /* This function is only relevant for the read directions. */ - return (0); - } - inp = (struct sctp_inpcb *)so->so_pcb; - if (inp == NULL) { - SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL); - return (EINVAL); - } - SCTP_INP_WLOCK(inp); - if (inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) { - /* For 1-to-many style sockets this function does nothing. */ - SCTP_INP_WUNLOCK(inp); - return (0); - } - stcb = LIST_FIRST(&inp->sctp_asoc_list); - if (stcb != NULL) { - SCTP_TCB_LOCK(stcb); - } - SCTP_INP_READ_LOCK(inp); - inp->sctp_flags |= SCTP_PCB_FLAGS_SOCKET_CANT_READ; - SOCK_LOCK(so); - TAILQ_FOREACH_SAFE(control, &inp->read_queue, next, ncontrol) { - if ((control->spec_flags & M_NOTIFICATION) == 0) { - need_to_abort = true; - } - TAILQ_REMOVE(&inp->read_queue, control, next); - control->on_read_q = 0; - for (m = control->data; m; m = SCTP_BUF_NEXT(m)) { - sctp_sbfree(control, control->stcb, &so->so_rcv, m); - } - if (control->on_strm_q == 0) { - sctp_free_remote_addr(control->whoFrom); - if (control->data) { - sctp_m_freem(control->data); - control->data = NULL; - } - sctp_free_a_readq(stcb, control); - } else { - stcb->asoc.size_on_all_streams += control->length; + switch (how) { + case SHUT_RD: + case SHUT_RDWR: + SCTP_INP_WLOCK(inp); + stcb = LIST_FIRST(&inp->sctp_asoc_list); + if (stcb != NULL) { + SCTP_TCB_LOCK(stcb); } - } - SOCK_UNLOCK(so); - SCTP_INP_READ_UNLOCK(inp); - if (need_to_abort && (stcb != NULL)) { - inp->last_abort_code = SCTP_FROM_SCTP_USRREQ + SCTP_LOC_6; - SCTP_INP_WUNLOCK(inp); - op_err = sctp_generate_cause(SCTP_CAUSE_OUT_OF_RESC, ""); - NET_EPOCH_ENTER(et); - sctp_abort_an_association(inp, stcb, op_err, false, SCTP_SO_LOCKED); - NET_EPOCH_EXIT(et); - return (ECONNABORTED); - } - if (stcb != NULL) { - SCTP_TCB_UNLOCK(stcb); - } - SCTP_INP_WUNLOCK(inp); - return (0); -} - -int -sctp_shutdown(struct socket *so) -{ - struct sctp_inpcb *inp; + SCTP_INP_READ_LOCK(inp); + inp->sctp_flags |= SCTP_PCB_FLAGS_SOCKET_CANT_READ; + SOCK_LOCK(so); + TAILQ_FOREACH_SAFE(control, &inp->read_queue, next, ncontrol) { + if ((control->spec_flags & M_NOTIFICATION) == 0) { + need_to_abort = true; + } + TAILQ_REMOVE(&inp->read_queue, control, next); + control->on_read_q = 0; + for (m = control->data; m; m = SCTP_BUF_NEXT(m)) { + sctp_sbfree(control, control->stcb, + &so->so_rcv, m); + } + if (control->on_strm_q == 0) { + sctp_free_remote_addr(control->whoFrom); + if (control->data) { + sctp_m_freem(control->data); + control->data = NULL; + } + sctp_free_a_readq(stcb, control); + } else { + stcb->asoc.size_on_all_streams += + control->length; + } + } + SOCK_UNLOCK(so); + SCTP_INP_READ_UNLOCK(inp); + if (need_to_abort && (stcb != NULL)) { + inp->last_abort_code = SCTP_FROM_SCTP_USRREQ + + SCTP_LOC_6; + SCTP_INP_WUNLOCK(inp); + op_err = sctp_generate_cause(SCTP_CAUSE_OUT_OF_RESC, + ""); + NET_EPOCH_ENTER(et); + sctp_abort_an_association(inp, stcb, op_err, false, + SCTP_SO_LOCKED); + NET_EPOCH_EXIT(et); - inp = (struct sctp_inpcb *)so->so_pcb; - if (inp == NULL) { - SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL); - return (EINVAL); - } - SCTP_INP_RLOCK(inp); - /* For UDP model this is a invalid call */ - if (!((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) || - (inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL))) { - /* Restore the flags that the soshutdown took away. */ - SOCKBUF_LOCK(&so->so_rcv); - so->so_rcv.sb_state &= ~SBS_CANTRCVMORE; - SOCKBUF_UNLOCK(&so->so_rcv); - /* This proc will wakeup for read and do nothing (I hope) */ - SCTP_INP_RUNLOCK(inp); - SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EOPNOTSUPP); - return (EOPNOTSUPP); - } else { + error = ECONNABORTED; + goto out; + } + if (stcb != NULL) { + SCTP_TCB_UNLOCK(stcb); + } + SCTP_INP_WUNLOCK(inp); /* - * Ok, if we reach here its the TCP model and it is either a - * SHUT_WR or SHUT_RDWR. This means we put the shutdown flag - * against it. + * XXXGL: does SCTP need sorflush()? This is what old + * soshutdown() used to do for all kinds of sockets. */ - struct epoch_tracker et; - struct sctp_tcb *stcb; - struct sctp_association *asoc; - struct sctp_nets *netp; + sorflush(so); + if (how == SHUT_RD) + break; + /* FALLTHROUGH */ - if ((so->so_state & - (SS_ISCONNECTED | SS_ISCONNECTING | SS_ISDISCONNECTING)) == 0) { - SCTP_INP_RUNLOCK(inp); - return (ENOTCONN); - } + case SHUT_WR: socantsendmore(so); - stcb = LIST_FIRST(&inp->sctp_asoc_list); if (stcb == NULL) { /* @@ -898,14 +887,14 @@ sctp_shutdown(struct socket *so) * now. */ SCTP_INP_RUNLOCK(inp); - return (0); + goto out; } SCTP_TCB_LOCK(stcb); asoc = &stcb->asoc; if (asoc->state & SCTP_STATE_ABOUT_TO_BE_FREED) { SCTP_TCB_UNLOCK(stcb); SCTP_INP_RUNLOCK(inp); - return (0); + goto out; } if ((SCTP_GET_STATE(stcb) != SCTP_STATE_COOKIE_WAIT) && (SCTP_GET_STATE(stcb) != SCTP_STATE_COOKIE_ECHOED) && @@ -916,7 +905,7 @@ sctp_shutdown(struct socket *so) */ SCTP_TCB_UNLOCK(stcb); SCTP_INP_RUNLOCK(inp); - return (0); + goto out; } NET_EPOCH_ENTER(et); if (stcb->asoc.alternate) { @@ -961,7 +950,7 @@ sctp_shutdown(struct socket *so) sctp_abort_an_association(stcb->sctp_ep, stcb, op_err, false, SCTP_SO_LOCKED); NET_EPOCH_EXIT(et); - return (0); + goto out; } } /* @@ -972,8 +961,11 @@ sctp_shutdown(struct socket *so) SCTP_TCB_UNLOCK(stcb); SCTP_INP_RUNLOCK(inp); NET_EPOCH_EXIT(et); - return (0); } +out: + wakeup(&so->so_timeo); + + return (error); } /* @@ -7523,7 +7515,6 @@ sctp_peeraddr(struct socket *so, struct sockaddr *sa) .pr_close = sctp_close, \ .pr_detach = sctp_close, \ .pr_sopoll = sopoll_generic, \ - .pr_flush = sctp_flush, \ .pr_disconnect = sctp_disconnect, \ .pr_listen = sctp_listen, \ .pr_peeraddr = sctp_peeraddr, \ diff --git a/sys/netinet/sctp_var.h b/sys/netinet/sctp_var.h index 54566e9ac0df..9ec8bdd9ab5e 100644 --- a/sys/netinet/sctp_var.h +++ b/sys/netinet/sctp_var.h @@ -331,7 +331,7 @@ void sctp_notify(struct sctp_inpcb *, struct sctp_tcb *, struct sctp_nets *, uint8_t, uint8_t, uint16_t, uint32_t); int sctp_flush(struct socket *, int); -int sctp_shutdown(struct socket *); +int sctp_shutdown(struct socket *, enum shutdown_how); int sctp_bindx(struct socket *, int, struct sockaddr_storage *, int, int, struct proc *); diff --git a/sys/netinet/tcp_usrreq.c b/sys/netinet/tcp_usrreq.c index dad79374c08b..ccd6a6149dae 100644 --- a/sys/netinet/tcp_usrreq.c +++ b/sys/netinet/tcp_usrreq.c @@ -799,31 +799,57 @@ tcp6_usr_accept(struct socket *so, struct sockaddr *sa) * Mark the connection as being incapable of further output. */ static int -tcp_usr_shutdown(struct socket *so) +tcp_usr_shutdown(struct socket *so, enum shutdown_how how) { - int error = 0; - struct inpcb *inp; - struct tcpcb *tp; struct epoch_tracker et; + struct inpcb *inp = sotoinpcb(so); + struct tcpcb *tp = intotcpcb(inp); + int error = 0; - inp = sotoinpcb(so); - KASSERT(inp != NULL, ("inp == NULL")); - INP_WLOCK(inp); - if (inp->inp_flags & INP_DROPPED) { - INP_WUNLOCK(inp); - return (ECONNRESET); + SOCK_LOCK(so); + if ((so->so_state & + (SS_ISCONNECTED | SS_ISCONNECTING | SS_ISDISCONNECTING)) == 0) { + SOCK_UNLOCK(so); + return (ENOTCONN); } - tp = intotcpcb(inp); + if (SOLISTENING(so)) { + if (how != SHUT_WR) { + so->so_error = ECONNABORTED; + solisten_wakeup(so); /* unlocks so */ + } else + SOCK_UNLOCK(so); + return (0); + } + SOCK_UNLOCK(so); - NET_EPOCH_ENTER(et); - socantsendmore(so); - tcp_usrclosed(tp); - if (!(inp->inp_flags & INP_DROPPED)) + switch (how) { + case SHUT_RD: + sorflush(so); + break; + case SHUT_RDWR: + sorflush(so); + /* FALLTHROUGH */ + case SHUT_WR: + /* + * XXXGL: mimicing old soshutdown() here. But shouldn't we + * return ECONNRESEST for SHUT_RD as well? + */ + INP_WLOCK(inp); + if (inp->inp_flags & INP_DROPPED) { + INP_WUNLOCK(inp); + return (ECONNRESET); + } + + socantsendmore(so); + NET_EPOCH_ENTER(et); + tcp_usrclosed(tp); error = tcp_output_nodrop(tp); - tcp_bblog_pru(tp, PRU_SHUTDOWN, error); - TCP_PROBE2(debug__user, tp, PRU_SHUTDOWN); - error = tcp_unlock_or_drop(tp, error); - NET_EPOCH_EXIT(et); + tcp_bblog_pru(tp, PRU_SHUTDOWN, error); + TCP_PROBE2(debug__user, tp, PRU_SHUTDOWN); + error = tcp_unlock_or_drop(tp, error); + NET_EPOCH_EXIT(et); + } + wakeup(&so->so_timeo); return (error); } diff --git a/sys/netinet/udp_usrreq.c b/sys/netinet/udp_usrreq.c index affdb3b1f4c7..f91a96edeb68 100644 --- a/sys/netinet/udp_usrreq.c +++ b/sys/netinet/udp_usrreq.c @@ -1670,16 +1670,42 @@ udp_disconnect(struct socket *so) #endif /* INET */ int -udp_shutdown(struct socket *so) +udp_shutdown(struct socket *so, enum shutdown_how how) { - struct inpcb *inp; + int error; - inp = sotoinpcb(so); - KASSERT(inp != NULL, ("udp_shutdown: inp == NULL")); - INP_WLOCK(inp); - socantsendmore(so); - INP_WUNLOCK(inp); - return (0); + SOCK_LOCK(so); + if (!(so->so_state & SS_ISCONNECTED)) + /* + * POSIX mandates us to just return ENOTCONN when shutdown(2) is + * invoked on a datagram sockets, however historically we would + * actually tear socket down. This is known to be leveraged by + * some applications to unblock process waiting in recv(2) by + * other process that it shares that socket with. Try to meet + * both backward-compatibility and POSIX requirements by forcing + * ENOTCONN but still flushing buffers and performing wakeup(9). + * + * XXXGL: it remains unknown what applications expect this + * behavior and is this isolated to unix/dgram or inet/dgram or + * both. See: D10351, D3039. + */ + error = ENOTCONN; + else + error = 0; + SOCK_UNLOCK(so); + + switch (how) { + case SHUT_RD: + sorflush(so); + break; + case SHUT_RDWR: + sorflush(so); + /* FALLTHROUGH */ + case SHUT_WR: + socantsendmore(so); + } + + return (error); } #ifdef INET diff --git a/sys/netinet/udp_var.h b/sys/netinet/udp_var.h index c31db2d97b5f..a66d76845eb6 100644 --- a/sys/netinet/udp_var.h +++ b/sys/netinet/udp_var.h @@ -168,7 +168,7 @@ udp_get_inpcbinfo(int protocol) int udp_ctloutput(struct socket *, struct sockopt *); void udplite_input(struct mbuf *, int); struct inpcb *udp_notify(struct inpcb *inp, int errno); -int udp_shutdown(struct socket *so); +int udp_shutdown(struct socket *, enum shutdown_how); int udp_set_kernel_tunneling(struct socket *so, udp_tun_func_t f, udp_tun_icmp_t i, void *ctx); diff --git a/sys/netinet6/raw_ip6.c b/sys/netinet6/raw_ip6.c index 174cc29e6008..3264de331817 100644 --- a/sys/netinet6/raw_ip6.c +++ b/sys/netinet6/raw_ip6.c @@ -827,16 +827,27 @@ rip6_connect(struct socket *so, struct sockaddr *nam, struct thread *td) } static int -rip6_shutdown(struct socket *so) +rip6_shutdown(struct socket *so, enum shutdown_how how) { - struct inpcb *inp; - inp = sotoinpcb(so); - KASSERT(inp != NULL, ("rip6_shutdown: inp == NULL")); + SOCK_LOCK(so); + if (!(so->so_state & SS_ISCONNECTED)) { + SOCK_UNLOCK(so); + return (ENOTCONN); + } + SOCK_UNLOCK(so); + + switch (how) { + case SHUT_RD: + sorflush(so); + break; + case SHUT_RDWR: + sorflush(so); + /* FALLTHROUGH */ + case SHUT_WR: + socantsendmore(so); + } - INP_WLOCK(inp); - socantsendmore(so); - INP_WUNLOCK(inp); return (0); } diff --git a/sys/netinet6/sctp6_usrreq.c b/sys/netinet6/sctp6_usrreq.c index 1268e4990e90..e3ed37b53425 100644 --- a/sys/netinet6/sctp6_usrreq.c +++ b/sys/netinet6/sctp6_usrreq.c @@ -1095,7 +1095,6 @@ sctp6_getpeeraddr(struct socket *so, struct sockaddr *sa) .pr_close = sctp6_close, \ .pr_detach = sctp6_close, \ .pr_sopoll = sopoll_generic, \ - .pr_flush = sctp_flush, \ .pr_disconnect = sctp_disconnect, \ .pr_listen = sctp_listen, \ .pr_peeraddr = sctp6_getpeeraddr, \ diff --git a/sys/sys/protosw.h b/sys/sys/protosw.h index b512c60971ee..6fd21b947687 100644 --- a/sys/sys/protosw.h +++ b/sys/sys/protosw.h @@ -39,6 +39,7 @@ struct thread; struct sockaddr; struct socket; struct sockopt; +enum shutdown_how; /*#ifdef _KERNEL*/ /* @@ -84,8 +85,7 @@ typedef int pr_send_t(struct socket *, int, struct mbuf *, struct sockaddr *, struct mbuf *, struct thread *); typedef int pr_ready_t(struct socket *, struct mbuf *, int); typedef int pr_sense_t(struct socket *, struct stat *); -typedef int pr_shutdown_t(struct socket *); -typedef int pr_flush_t(struct socket *, int); +typedef int pr_shutdown_t(struct socket *, enum shutdown_how); typedef int pr_sockaddr_t(struct socket *, struct sockaddr *); typedef int pr_sosend_t(struct socket *, struct sockaddr *, struct uio *, struct mbuf *, struct mbuf *, int, struct thread *); @@ -137,7 +137,6 @@ struct protosw { pr_peeraddr_t *pr_peeraddr; /* getpeername(2) */ pr_sockaddr_t *pr_sockaddr; /* getsockname(2) */ pr_sense_t *pr_sense; /* stat(2) */ - pr_flush_t *pr_flush; /* XXXGL: merge with pr_shutdown_t! */ pr_sosetlabel_t *pr_sosetlabel; /* MAC, XXXGL: remove */ pr_setsbopt_t *pr_setsbopt; /* Socket buffer ioctls */ }; diff --git a/sys/sys/socket.h b/sys/sys/socket.h index 9e78281e5dd2..3e24db552618 100644 --- a/sys/sys/socket.h +++ b/sys/sys/socket.h @@ -633,14 +633,6 @@ enum shutdown_how { SHUT_RDWR /* shut down both sides */ }; -#if __BSD_VISIBLE -/* for SCTP */ -/* we cheat and use the SHUT_XX defines for these */ -#define PRU_FLUSH_RD SHUT_RD -#define PRU_FLUSH_WR SHUT_WR -#define PRU_FLUSH_RDWR SHUT_RDWR -#endif - #if __BSD_VISIBLE /* * sendfile(2) header/trailer struct