Bug 146845 - [libc] close(2) returns error 54 (connection reset by peer) wrongly
Summary: [libc] close(2) returns error 54 (connection reset by peer) wrongly
Status: Open
Alias: None
Product: Base System
Classification: Unclassified
Component: kern (show other bugs)
Version: Unspecified
Hardware: Any Any
: Normal Affects Only Me
Assignee: freebsd-bugs (Nobody)
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2010-05-22 23:10 UTC by Anton Lavrentiev
Modified: 2024-04-03 15:21 UTC (History)
1 user (show)

See Also:


Attachments
file.txt (4.81 KB, text/plain)
2010-05-22 23:10 UTC, Anton Lavrentiev
no flags Details
tcp_close.c (2.42 KB, application/octet-stream)
2010-05-27 22:25 UTC, Mikolaj Golub
no flags Details
uipc_socket.c.econnreset.patch (808 bytes, patch)
2010-05-27 22:25 UTC, Mikolaj Golub
no flags Details | Diff
test_tcp_close.c (11.58 KB, application/octet-stream)
2010-05-30 09:05 UTC, Mikolaj Golub
no flags Details

Note You need to log in before you can comment on or make changes to this bug.
Description Anton Lavrentiev 2010-05-22 23:10:01 UTC
When an INET socket is being closed from two ends almost simultaneously,
with both sides using a series of SO_LINGER, shutdown and close, and one
side being slightly behind the other, the side closing last gets -1
from close() on such a socket, and errno 54, Connection reset by peer,
even though there was no data in the socket (so it more likely seems that
the FIN that the late shutdown() was assumed to send was counted towards
the actual data, but it should not have).  No other UNIX flavor (and we
have many here at NCBI) exhibits this kind of problem.  Also, the problem
is not 100% reproducible on FreeBSD (but is pretty frequent, nonetheless).
We have seen this very same problem on both version 6 and 8 of the OS
(reporting here for 8).

Fix: There are workarounds (as to not to use either setsockopt(SO_LINGER) or
shutdown(SHUT_WR), or both) but they do not fix the problem in general.


Patch attached with submission follows:
How-To-Repeat: In the attached, there is a server-client C program that reproduces the
problem (although, the program itself has to be started a few times before
the actual error occurs).  At the bottom of the attached C program,
a ktrace of the communicating processes, and errno 54 coming out of
close().  select(0,...) is used to slowdown one process to imitate some
of its midwork before it is ready to proceed with actual socket disposal.
If either shutdown() or setsockopt(SO_LINGER) are excluded from the code,
the problem in close() seems to be gone.

while ktrace -i ./a.out; do date; done
kdump
Comment 1 Gavin Atkinson freebsd_committer freebsd_triage 2010-05-23 15:03:41 UTC
Responsible Changed
From-To: freebsd-bugs->freebsd-net

Pass this over to maintainers.  Thanks for the good PR and test case!
Comment 2 Mikolaj Golub 2010-05-27 22:25:42 UTC
Hi,

We observed the same issue on our FreeBSD6 and 7 servers. I tried to reproduce
the problem writing a simple test case but failed -- I didn't come to the idea
of shutdown()/close() sequence (as Anton did). Although looking now at the
code we had the issue with I see that shutdown()/close() sequence was used
there too.

It looks like SO_LINGER is not important to reproduce ECONNRESET.
shutdown()/close() on one end and close() on the other is enough. Also,
slowdown of one the processes (done by Anton using select()) is not important
too. Taking this into consideration I have wrote a simplified version of a test
to reproduce the bug (may be it worth of including to tools/regression/sockets?).

I can easily reproduce the error with this test on FreeBSD7.1 and
8-STABLE. Adding some prints to the kernel code I localized the place where
the error appears and added panic() to get a backtrace.

So, the backtrace:

(kgdb) bt
#0  doadump () at pcpu.h:246
#1  0xc04ec829 in db_fncall (dummy1=-1064461270, dummy2=0, dummy3=-1, dummy4=0xe85e58b0 "ÄX^è")
    at /usr/src/sys/ddb/db_command.c:548
#2  0xc04ecc5f in db_command (last_cmdp=0xc0e0af9c, cmd_table=0x0, dopager=0)
    at /usr/src/sys/ddb/db_command.c:445
#3  0xc04ecd14 in db_command_script (command=0xc0e0bec4 "call doadump")
    at /usr/src/sys/ddb/db_command.c:516
#4  0xc04f0e50 in db_script_exec (scriptname=0xe85e59bc "kdb.enter.panic", warnifnotfound=Variable "warnifnotfound" is not available.
)
    at /usr/src/sys/ddb/db_script.c:302
#5  0xc04f0f37 in db_script_kdbenter (eventname=0xc0cc78ea "panic")
    at /usr/src/sys/ddb/db_script.c:324
#6  0xc04eec18 in db_trap (type=3, code=0) at /usr/src/sys/ddb/db_main.c:228
#7  0xc08d9aa6 in kdb_trap (type=3, code=0, tf=0xe85e5af8) at /usr/src/sys/kern/subr_kdb.c:535
#8  0xc0befecb in trap (frame=0xe85e5af8) at /usr/src/sys/i386/i386/trap.c:690
#9  0xc0bd15eb in calltrap () at /usr/src/sys/i386/i386/exception.s:165
#10 0xc08d9c2a in kdb_enter (why=0xc0cc78ea "panic", msg=0xc0cc78ea "panic") at cpufunc.h:71
#11 0xc08a95b6 in panic (fmt=0xc0ce6585 "ECONNRESET") at /usr/src/sys/kern/kern_shutdown.c:562
#12 0xc0a3d805 in tcp_usr_disconnect (so=0xc715c670) at /usr/src/sys/netinet/tcp_usrreq.c:552
#13 0xc09111bd in sodisconnect (so=0xc715c670) at /usr/src/sys/kern/uipc_socket.c:810
#14 0xc0914144 in soclose (so=0xc715c670) at /usr/src/sys/kern/uipc_socket.c:658
#15 0xc08f6459 in soo_close (fp=0xc743e230, td=0xc7023000)
    at /usr/src/sys/kern/sys_socket.c:291
#16 0xc086efc3 in _fdrop (fp=0xc743e230, td=0xc7023000) at file.h:293
#17 0xc0870cf0 in closef (fp=0xc743e230, td=0xc7023000)
    at /usr/src/sys/kern/kern_descrip.c:2117
#18 0xc0871097 in kern_close (td=0xc7023000, fd=4) at /usr/src/sys/kern/kern_descrip.c:1162
#19 0xc087123a in close (td=0xc7023000, uap=0xe85e5cf8)
    at /usr/src/sys/kern/kern_descrip.c:1114
#20 0xc0bef600 in syscall (frame=0xe85e5d38) at /usr/src/sys/i386/i386/trap.c:1111
#21 0xc0bd1680 in Xint0x80_syscall () at /usr/src/sys/i386/i386/exception.s:261
#22 0x00000033 in ?? ()
Previous frame inner to this frame (corrupt stack?)
(kgdb) fr 12
#12 0xc0a3d805 in tcp_usr_disconnect (so=0xc715c670) at /usr/src/sys/netinet/tcp_usrreq.c:552
552                     panic("ECONNRESET");
(kgdb) list
547             inp = sotoinpcb(so);
548             KASSERT(inp != NULL, ("tcp_usr_disconnect: inp == NULL"));
549             INP_WLOCK(inp);
550             if (inp->inp_flags & (INP_TIMEWAIT | INP_DROPPED)) {
551                     error = ECONNRESET;
552                     panic("ECONNRESET");
553                     /* log(LOG_INFO, "ECONNRESET 3: file %s; line %d\n", __FILE__, __LINE__); */
554                     goto out;
555             }
556             tp = intotcpcb(inp);
(kgdb) p/x inp->inp_flags
$1 = 0x4800000

#define INP_DROPPED             0x04000000 /* protocol drop flag */

(kgdb) fr 14
#14 0xc0914144 in soclose (so=0xc715c670) at /usr/src/sys/kern/uipc_socket.c:658
658                             error = sodisconnect(so);
(kgdb) list
653
654             CURVNET_SET(so->so_vnet);
655             funsetown(&so->so_sigio);
656             if (so->so_state & SS_ISCONNECTED) {
657                     if ((so->so_state & SS_ISDISCONNECTING) == 0) {
658                             error = sodisconnect(so);
659                             if (error) {
660                                     if (error == ENOTCONN)
661                                             error = 0;
662                                     goto drop;
(kgdb) p/x so->so_state  
$2 = 0x2000

#define SS_ISDISCONNECTED       0x2000  /* socket disconnected from peer */

(the code differs a bit here from original 8-STABLE version as it includes
patch from kern/144061 to deal with ENOTCONN errors but this does not
influence).

Actually, I think that the issue here is similar to kern/144061. In the code
above so_state is checked for SS_ISCONNECTED and SS_ISDISCONNECTING without
locking and then sodisconnect() is called. It looks like the following may
happen:

1) after shutdown() our output is closed;

2) then we call close(), soclose() checks that we are still in SS_ISCONNECTED
and calls sodisconnect();

3) at this time FIN arrives from the other end, which has called close() too,
and the kernel disconnects the socket (INP_DROPPED is set);

4) sodisconnect()/tcp_usr_disconnect() checks for INP_DROPPED and returns
ECONNRESET.

I am attaching the patch, which may not be a solution but rather for
illustration to described above. Running the test with this patch I am
observing the following messages in error logs

May 27 23:55:41 zhuzha kernel: ECONNRESET: so->state: 0x2000; file /usr/src/sys/kern/uipc_socket.c; line 664

and test does not fail.

-- 
Mikolaj Golub
Comment 3 Robert Watson freebsd_committer freebsd_triage 2010-05-27 23:21:06 UTC
On 27 May 2010, at 22:25, Mikolaj Golub wrote:

> We observed the same issue on our FreeBSD6 and 7 servers. I tried to =
reproduce
> the problem writing a simple test case but failed -- I didn't come to =
the idea
> of shutdown()/close() sequence (as Anton did). Although looking now at =
the
> code we had the issue with I see that shutdown()/close() sequence was =
used
> there too.

Hi Mikolaj--

Indeed, this looks very like the ENOTCONN issue in kern/144061 -- =
another indication that a bit more refinement is required in the =
synchronization for our socket-layer state machine. Unfortunately, not =
an issue we'll likely address in a general sense until 9.0. However, a =
similar workaround to kenr/144061 does sound like the solution -- I'll =
get this new regression test in the tree, as well as the workaround, =
tomorrow, with the hopes of making 8.1.

Thanks all!

Robert

>=20
> It looks like SO_LINGER is not important to reproduce ECONNRESET.
> shutdown()/close() on one end and close() on the other is enough. =
Also,
> slowdown of one the processes (done by Anton using select()) is not =
important
> too. Taking this into consideration I have wrote a simplified version =
of a test
> to reproduce the bug (may be it worth of including to =
tools/regression/sockets?).
>=20
> I can easily reproduce the error with this test on FreeBSD7.1 and
> 8-STABLE. Adding some prints to the kernel code I localized the place =
where
> the error appears and added panic() to get a backtrace.
>=20
> So, the backtrace:
>=20
> (kgdb) bt
> #0  doadump () at pcpu.h:246
> #1  0xc04ec829 in db_fncall (dummy1=3D-1064461270, dummy2=3D0, =
dummy3=3D-1, dummy4=3D0xe85e58b0 "=C4X^=E8")
>    at /usr/src/sys/ddb/db_command.c:548
> #2  0xc04ecc5f in db_command (last_cmdp=3D0xc0e0af9c, cmd_table=3D0x0, =
dopager=3D0)
>    at /usr/src/sys/ddb/db_command.c:445
> #3  0xc04ecd14 in db_command_script (command=3D0xc0e0bec4 "call =
doadump")
>    at /usr/src/sys/ddb/db_command.c:516
> #4  0xc04f0e50 in db_script_exec (scriptname=3D0xe85e59bc =
"kdb.enter.panic", warnifnotfound=3DVariable "warnifnotfound" is not =
available.
> )
>    at /usr/src/sys/ddb/db_script.c:302
> #5  0xc04f0f37 in db_script_kdbenter (eventname=3D0xc0cc78ea "panic")
>    at /usr/src/sys/ddb/db_script.c:324
> #6  0xc04eec18 in db_trap (type=3D3, code=3D0) at =
/usr/src/sys/ddb/db_main.c:228
> #7  0xc08d9aa6 in kdb_trap (type=3D3, code=3D0, tf=3D0xe85e5af8) at =
/usr/src/sys/kern/subr_kdb.c:535
> #8  0xc0befecb in trap (frame=3D0xe85e5af8) at =
/usr/src/sys/i386/i386/trap.c:690
> #9  0xc0bd15eb in calltrap () at =
/usr/src/sys/i386/i386/exception.s:165
> #10 0xc08d9c2a in kdb_enter (why=3D0xc0cc78ea "panic", msg=3D0xc0cc78ea =
"panic") at cpufunc.h:71
> #11 0xc08a95b6 in panic (fmt=3D0xc0ce6585 "ECONNRESET") at =
/usr/src/sys/kern/kern_shutdown.c:562
> #12 0xc0a3d805 in tcp_usr_disconnect (so=3D0xc715c670) at =
/usr/src/sys/netinet/tcp_usrreq.c:552
> #13 0xc09111bd in sodisconnect (so=3D0xc715c670) at =
/usr/src/sys/kern/uipc_socket.c:810
> #14 0xc0914144 in soclose (so=3D0xc715c670) at =
/usr/src/sys/kern/uipc_socket.c:658
> #15 0xc08f6459 in soo_close (fp=3D0xc743e230, td=3D0xc7023000)
>    at /usr/src/sys/kern/sys_socket.c:291
> #16 0xc086efc3 in _fdrop (fp=3D0xc743e230, td=3D0xc7023000) at =
file.h:293
> #17 0xc0870cf0 in closef (fp=3D0xc743e230, td=3D0xc7023000)
>    at /usr/src/sys/kern/kern_descrip.c:2117
> #18 0xc0871097 in kern_close (td=3D0xc7023000, fd=3D4) at =
/usr/src/sys/kern/kern_descrip.c:1162
> #19 0xc087123a in close (td=3D0xc7023000, uap=3D0xe85e5cf8)
>    at /usr/src/sys/kern/kern_descrip.c:1114
> #20 0xc0bef600 in syscall (frame=3D0xe85e5d38) at =
/usr/src/sys/i386/i386/trap.c:1111
> #21 0xc0bd1680 in Xint0x80_syscall () at =
/usr/src/sys/i386/i386/exception.s:261
> #22 0x00000033 in ?? ()
> Previous frame inner to this frame (corrupt stack?)
> (kgdb) fr 12
> #12 0xc0a3d805 in tcp_usr_disconnect (so=3D0xc715c670) at =
/usr/src/sys/netinet/tcp_usrreq.c:552
> 552                     panic("ECONNRESET");
> (kgdb) list
> 547             inp =3D sotoinpcb(so);
> 548             KASSERT(inp !=3D NULL, ("tcp_usr_disconnect: inp =3D=3D =
NULL"));
> 549             INP_WLOCK(inp);
> 550             if (inp->inp_flags & (INP_TIMEWAIT | INP_DROPPED)) {
> 551                     error =3D ECONNRESET;
> 552                     panic("ECONNRESET");
> 553                     /* log(LOG_INFO, "ECONNRESET 3: file %s; line =
%d\n", __FILE__, __LINE__); */
> 554                     goto out;
> 555             }
> 556             tp =3D intotcpcb(inp);
> (kgdb) p/x inp->inp_flags
> $1 =3D 0x4800000
>=20
> #define INP_DROPPED             0x04000000 /* protocol drop flag */
>=20
> (kgdb) fr 14
> #14 0xc0914144 in soclose (so=3D0xc715c670) at =
/usr/src/sys/kern/uipc_socket.c:658
> 658                             error =3D sodisconnect(so);
> (kgdb) list
> 653
> 654             CURVNET_SET(so->so_vnet);
> 655             funsetown(&so->so_sigio);
> 656             if (so->so_state & SS_ISCONNECTED) {
> 657                     if ((so->so_state & SS_ISDISCONNECTING) =3D=3D =
0) {
> 658                             error =3D sodisconnect(so);
> 659                             if (error) {
> 660                                     if (error =3D=3D ENOTCONN)
> 661                                             error =3D 0;
> 662                                     goto drop;
> (kgdb) p/x so->so_state =20
> $2 =3D 0x2000
>=20
> #define SS_ISDISCONNECTED       0x2000  /* socket disconnected from =
peer */
>=20
> (the code differs a bit here from original 8-STABLE version as it =
includes
> patch from kern/144061 to deal with ENOTCONN errors but this does not
> influence).
>=20
> Actually, I think that the issue here is similar to kern/144061. In =
the code
> above so_state is checked for SS_ISCONNECTED and SS_ISDISCONNECTING =
without
> locking and then sodisconnect() is called. It looks like the following =
may
> happen:
>=20
> 1) after shutdown() our output is closed;
>=20
> 2) then we call close(), soclose() checks that we are still in =
SS_ISCONNECTED
> and calls sodisconnect();
>=20
> 3) at this time FIN arrives from the other end, which has called =
close() too,
> and the kernel disconnects the socket (INP_DROPPED is set);
>=20
> 4) sodisconnect()/tcp_usr_disconnect() checks for INP_DROPPED and =
returns
> ECONNRESET.
>=20
> I am attaching the patch, which may not be a solution but rather for
> illustration to described above. Running the test with this patch I am
> observing the following messages in error logs
>=20
> May 27 23:55:41 zhuzha kernel: ECONNRESET: so->state: 0x2000; file =
/usr/src/sys/kern/uipc_socket.c; line 664
>=20
> and test does not fail.
>=20
> --=20
> Mikolaj Golub
>=20
> <tcp_close.c><uipc_socket.c.econnreset.patch>
Comment 4 Anton Lavrentiev 2010-05-28 05:07:16 UTC
Hi Mikolaj and Robert,

Thanks for following up on this issue!

> It looks like SO_LINGER is not important to reproduce ECONNRESET.

Indeed, it is not.

> I am attaching the patch, which may not be a solution

IMHO, it is not, unfortunately, a solution:  it seems to clear ECONNRESET
blindly and w/o distinguishing the situation when the remote end closes the
connection prematurely (i.e. before acknowledging all data written from the
local end) -- and that qualifies for the true "connection reset by peer"
from close()...

Anton Lavrentiev
Contractor NIH/NLM/NCBI
Comment 5 Mikolaj Golub 2010-05-28 10:26:33 UTC
On Fri, 28 May 2010 04:40:03 GMT Lavrentiev, Anton (NIH/NLM/NCBI) [C] wrote:

 LA>  IMHO, it is not, unfortunately, a solution:  it seems to clear ECONNRESET
 LA>  blindly and w/o distinguishing the situation when the remote end closes the
 LA>  connection prematurely (i.e. before acknowledging all data written from the
 LA>  local end) -- and that qualifies for the true "connection reset by peer"
 LA>  from close()...

I am not very familiar with the socket/tcp code but it looks for me that it
might not make any difference.

I can be wrong here but the situation you have described as true "connection
reset by peer" seems to have the following path in the code:

soclose() -> sodisconnect() -> tcp_usr_disconnect() -> tcp_disconnect()

But tcp_disconnect() does not return error, so we will not have ECONNRESET
error in any case.

May be you have a good test suite to reproduce this situation? :-) 

-- 
Mikolaj Golub
Comment 6 Mikolaj Golub 2010-05-30 09:05:45 UTC
 >  IMHO, it is not, unfortunately, a solution:  it seems to clear ECONNRESET
 >  blindly and w/o distinguishing the situation when the remote end closes the
 >  connection prematurely (i.e. before acknowledging all data written from the
 >  local end) -- and that qualifies for the true "connection reset by peer"
 >  from close()...

I did some experiments the results I would like to share here. The idea is
following: the client sends data in one write() more then a win, while the
server closes the connection without reading (sending RST on close). I also
played with LINGER option. I have managed to get ECONNRESET only on write(),
if the server sends RST before the client calls write(). In all other cases
write()/close() returned without error. See the attachment for details. 

So I think that with the workaround (ignore ECONNRESET returned by
sodisconnect() in soclose()) we would not make the situation worse (while it
fixed the issue with applications getting unexpectedly ECONNRESET after
shutdown()/close() sequence).

-- 
Mikolaj Golub
Comment 7 Eitan Adler freebsd_committer freebsd_triage 2017-12-31 07:59:10 UTC
For bugs matching the following criteria:

Status: In Progress Changed: (is less than) 2014-06-01

Reset to default assignee and clear in-progress tags.

Mail being skipped