Subject: kern/17390: error handling in getpeername(2) is sub-optimal....
To: None <gnats-bugs@gnats.netbsd.org>
From: Greg A. Woods <woods@weird.com>
List: netbsd-bugs
Date: 06/25/2002 14:36:30
>Number: 17390
>Category: kern
>Synopsis: error handling in getpeername(2) is sub-optimal....
>Confidential: no
>Severity: non-critical
>Priority: medium
>Responsible: kern-bug-people
>State: open
>Class: change-request
>Submitter-Id: net
>Arrival-Date: Tue Jun 25 11:54:10 PDT 2002
>Closed-Date:
>Last-Modified:
>Originator: Greg A. Woods
>Release: NetBSD-current 2002/06/23
>Organization:
Planix, Inc.; Toronto, Ontario; Canada
>Environment:
System: NetBSD
>Description:
Due to the way a peer TCP reset is handled and the protocol
block is freed by this event and lot later when any socket open
to the connection is closed, getpeername() can fail while an
application still has a 'valid' socket. This can result in
severely confusing error messages from a server that accepts a
connection (without keeping track of the peer address returned
by accept()) and then tries to use getpeername() at some later
point, eg. when reporting a read() error.
The attached patch doesn't fix this, but it seems to be a
necessary prerequisite change to try to centralise some of the
error handling.... Note that this change has not yet been
extensively tested, but so far it seems to be functioning --
what I'm concerned about though is that some other PRU_* request
might now return an "unexpected" error (though in reality this
would probably be more of a documentation bug than a problem
with this change per se).
The real fix to my original problem is probably to copy the peer
address up from the Internet PCB to the transport specific PCB,
and to make sure to keep the latter around until the socket is
closed. Somewhere I saw discussion about this, either in the
FreeBSD change logs or code, or perhaps in Stevens' TCP/IP
Illust. Vol. 2
Related to all this is the "messy" way of using mbufs to pass
around struct sockaddr_in's, such as in the badly named
in_set*addr() functions. FreeBSD's cleanup in this area seems
to be quite desirable....
>How-To-Repeat:
desire to get results from getpeername() even after peer reset!
>Fix:
also note the DIAGNOSTIC panic() below....
Index: uipc_syscalls.c
===================================================================
RCS file: /cvs/master/m-NetBSD/main/syssrc/sys/kern/uipc_syscalls.c,v
retrieving revision 1.68
diff -c -c -r1.68 uipc_syscalls.c
*** uipc_syscalls.c 11 Feb 2002 18:11:43 -0000 1.68
--- uipc_syscalls.c 25 Jun 2002 17:58:08 -0000
***************
*** 975,984 ****
if ((error = getsock(p->p_fd, SCARG(uap, fdes), &fp)) != 0)
return (error);
so = (struct socket *)fp->f_data;
- if ((so->so_state & (SS_ISCONNECTED|SS_ISCONFIRMING)) == 0) {
- error = ENOTCONN;
- goto out;
- }
error = copyin((caddr_t)SCARG(uap, alen), (caddr_t)&len, sizeof(len));
if (error)
goto out;
--- 975,980 ----
Index: tcp_usrreq.c
===================================================================
RCS file: /cvs/master/m-NetBSD/main/syssrc/sys/netinet/tcp_usrreq.c,v
retrieving revision 1.70
diff -c -c -r1.70 tcp_usrreq.c
*** tcp_usrreq.c 11 Mar 2002 10:06:12 -0000 1.70
--- tcp_usrreq.c 25 Jun 2002 17:56:42 -0000
***************
*** 264,276 ****
if ((inp == 0 && in6p == 0) && req != PRU_ATTACH)
#endif
{
! error = EINVAL;
goto release;
}
#ifdef INET
if (inp) {
tp = intotcpcb(inp);
/* WHAT IF TP IS 0? */
#ifdef KPROF
tcp_acounts[tp->t_state][req]++;
#endif
--- 264,292 ----
if ((inp == 0 && in6p == 0) && req != PRU_ATTACH)
#endif
{
! /*
! * FreeBSD-4.x unconditionally returns ECONNRESET if the PCB is
! * gone, but that seems too cavalier. If the reason the PCB is
! * gone is because our peer reset the connection then so_error
! * will have been set to ECONNRESET. Can it ever be set to
! * anything else in this case? Otherwise we've not connected
! * yet so ENOTCONN seems to be the right thing to return (and
! * this will mimic the check that was used previously in
! * ../kern/uipc_syscalls.c:sys_getpeername().
! */
! if (so->so_error != 0)
! error = so->so_error;
! else if ((so->so_state & (SS_ISCONNECTED|SS_ISCONFIRMING)) == 0)
! error = ENOTCONN;
goto release;
}
#ifdef INET
if (inp) {
tp = intotcpcb(inp);
/* WHAT IF TP IS 0? */
+ # ifdef DIAGNOSTIC
+ panic("tcp_usrreq: intotcpcp() found NULL struct tcpcb in per-proto PCB");
+ # endif
#ifdef KPROF
tcp_acounts[tp->t_state][req]++;
#endif
>Release-Note:
>Audit-Trail:
>Unformatted: