From: Bogdan Cristea <cristeab@gmail.com>
To: Celelibi <celelibi@gmail.com>, linux-c-programming@vger.kernel.org
Subject: Re: close socket and TCP RST
Date: Tue, 10 Apr 2012 23:01:32 +0200 [thread overview]
Message-ID: <1441444.tphEHApd4k@desktop> (raw)
In-Reply-To: <CAJR2zJ9EQ5D-0U_6Yrzmv5nJkv7BBhiOuGLXcgF=c-K7qxsO5Q@mail.gmail.com>
On Tuesday 10 April 2012 01:46:45 you wrote:
> Hello,
>
> I'm not sure about the mailing list I should ask this. I would try
> linux-net if it was still alive.
>
> I spent a few days searching about a weird bug I had.
> It is actually similar to this one:
> http://marc.info/?l=linux-net&m=127651583824851&w=2 .
>
> Summary for lazy people ^^: calling close(2) on a socket with a
> non-empty receive kernel-buffer cause the connection to be ReSeT and
> the send buffer discarded and not sent.
>
>
> I have a server program (code included below) that wait for a client.
> When a client connect, the server:
> - sent "Greet\n"
> - read one char
> - send "Hello "
> - send "World\n"
> - send "Quit\n"
> - close the socket
>
> And it happens that sometimes (quite often actually) that the client
> does not recieve anything after "Hello " and the connection is just
> closed.
> Actually, after I dumped the TCP trafic with wireshark, I saw that
> "World\n" and "Quit\n" were NOT sent, despite the send(2) succeded.
> The server just send a packet with the RST flag to interrupt the
> connection.
> Even more strange: This only occurs when the client send more data to
> the server than what was expected.
> i.e.: the server read one byte, the client send one, the last messages
> arrives just fine. The client send two bytes, the last messages never
> arrives!
>
> From what I intuited and understood from the Linux kernel code:
> http://lxr.linux.no/linux+v3.3.1/net/ipv4/tcp.c#L1893
> When the sever call close(2) on the socket file descriptor, the
> connection is "reset" if the receive buffer was not empty. And in that
> case, the output buffer is never sent to the client (whenever
> SO_LINGER is set or not).
>
>
> The workaround I found is to call shutdown(2) before calling close.
> When the outgoing direction of the socket is shutdown the buffer is
> flushed and sent, and it initiate a gentle connection ending. (While
> the close still send a RST because of the non-empty input buffer.)
>
> Therefore I have two questions:
> 1) Is this a standard behavior? Doesn't the RFC state that every
> pending data is sent when the connection is closed?
> 2) Shouldn't that behavior be documented somewhere? I didn't found any
> information about that anywhere. I looked at the man close(2),
> shutdown(2), socket(7), tcp(7).
>
> From this I deduce that shutdown must be called everytime we want to
> close a socket. But this is not taught anywhere. :p
>
>
>
> Here is the code of the server for those who want to try it. And since
> it seems time related I also provide a client that exhibit the bug (at
> least on my machine).
>
> #include <stdio.h>
> #include <stdlib.h>
> #include <unistd.h>
> #include <string.h>
> #include <sys/types.h>
> #include <sys/socket.h>
> #include <netdb.h>
>
>
>
> #define STRING_GREET "Greet\n"
> #define STRING_HELLO "Hello "
> #define STRING_WORLD "World"
> #define STRING_QUIT "Quit\n"
>
>
>
>
> int create_socket(void) {
> struct addrinfo hints, *res, *rp;
> int sockfd = -1;
> int err;
>
>
> memset(&hints, 0, sizeof(hints));
> hints.ai_family = AF_UNSPEC;
> hints.ai_socktype = SOCK_STREAM;
> hints.ai_protocol = 0;
> hints.ai_flags = AI_V4MAPPED | AI_PASSIVE;
>
> err = getaddrinfo("127.0.0.1", "1337", &hints, &res);
> if (err) {
> fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(err));
> exit(EXIT_FAILURE);
> }
>
> for (rp = res; rp; rp = rp->ai_next) {
> int optval;
>
> sockfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
> if (sockfd == -1) {
> perror("socket");
> continue;
> }
>
> optval = 1;
> err = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &optval,
> sizeof(optval)); if (err == -1) {
> perror("setsockopt");
> close(sockfd);
> sockfd = -1;
> continue;
> }
>
> err = bind(sockfd, rp->ai_addr, rp->ai_addrlen);
> if (err == -1) {
> perror("bind");
> close(sockfd);
> sockfd = -1;
> continue;
> }
>
> break;
> }
>
> freeaddrinfo(res);
>
> if (sockfd == -1) {
> fprintf(stderr, "can't bind at all\n");
> exit(EXIT_FAILURE);
> }
>
> err = listen(sockfd, 5);
> if (err == -1) {
> perror("listen");
> close(sockfd);
> exit(EXIT_FAILURE);
> }
>
> return sockfd;
> }
>
>
>
> void handle_client(int sockfd, int s) {
> char c;
> int err;
>
> err = send(s, STRING_GREET, strlen(STRING_GREET), MSG_NOSIGNAL);
> if (err == -1) {
> perror("send");
> close(s);
> close(sockfd);
> exit(EXIT_FAILURE);
> }
> fprintf(stderr, "send(STRING_GREET) = %d\n", err);
>
> err = recv(s, &c, sizeof(c), 0);
> if (err == -1) {
> perror("recv");
> close(s);
> close(sockfd);
> exit(EXIT_FAILURE);
> }
>
> err = send(s, STRING_HELLO, strlen(STRING_HELLO), MSG_NOSIGNAL);
> if (err == -1) {
> perror("send");
> close(s);
> close(sockfd);
> exit(EXIT_FAILURE);
> }
> fprintf(stderr, "send(STRING_HELLO) = %d\n", err);
>
> err = send(s, STRING_WORLD, strlen(STRING_WORLD), MSG_NOSIGNAL);
> if (err == -1) {
> perror("send");
> close(s);
> close(sockfd);
> exit(EXIT_FAILURE);
> }
> fprintf(stderr, "send(STRING_WORLD) = %d\n", err);
>
> err = send(s, "\n", 1, 0);
> if (err == -1) {
> perror("send");
> close(s);
> close(sockfd);
> exit(EXIT_FAILURE);
> }
> fprintf(stderr, "send(\\n) = %d\n", err);
>
> err = send(s, STRING_QUIT, strlen(STRING_QUIT), MSG_NOSIGNAL);
> if (err == -1) {
> perror("send");
> close(s);
> close(sockfd);
> exit(EXIT_FAILURE);
> }
> fprintf(stderr, "send(STRING_QUIT) = %d\n", err);
>
> /*err = shutdown(s, SHUT_RDWR);
> if (err == -1) {
> perror("shutdown");
> close(sockfd);
> exit(EXIT_FAILURE);
> }*/
>
> err = close(s);
> if (err == -1) {
> perror("close");
> close(sockfd);
> exit(EXIT_FAILURE);
> }
> }
>
>
>
> int main(void) {
> struct linger lin;
> int sockfd;
> int err;
>
> sockfd = create_socket();
>
> lin.l_onoff = 1;
> lin.l_linger = 1;
> err = setsockopt(sockfd, SOL_SOCKET, SO_LINGER, &lin, sizeof(lin));
> if (err == -1) {
> perror("setsockopt(SO_LINGER)");
> close(sockfd);
> exit(EXIT_FAILURE);
> }
>
>
> while (1) {
> int s;
>
> s = accept(sockfd, NULL, NULL);
> if (s == -1) {
> perror("accept");
> close(sockfd);
> exit(EXIT_FAILURE);
> }
>
> handle_client(sockfd, s);
> }
>
> err = close(sockfd);
> if (err == -1) {
> perror("close");
> exit(EXIT_FAILURE);
> }
>
> return EXIT_SUCCESS;
> }
>
>
>
>
>
>
>
>
> The client:
>
> #include <stdio.h>
> #include <stdlib.h>
> #include <unistd.h>
> #include <string.h>
> #include <sys/types.h>
> #include <sys/socket.h>
> #include <netdb.h>
>
>
> #define MIN(a, b) (((a) < (b)) ? (a) : (b))
>
>
> size_t readline(int fd, unsigned char *out, size_t out_size) {
> static unsigned char buff[8 * 1024];
> static size_t data_buff = 0;
> size_t retval = 0;
> ssize_t nr;
>
> while (1) {
> unsigned char *nl;
>
>
> /* Is there already a \n in the buffer? */
> nl = memchr(buff, '\n', data_buff);
> if (nl) {
> size_t nc = MIN(out_size, nl - buff + 1UL);
> memcpy(out, buff, nc);
> retval += nc;
> memmove(buff, buff + nc, data_buff - nc);
> data_buff -= nc;
> break;
> } else {
> /* No \n found */
> if (data_buff >= out_size) {
> /* No space left in the out buffer */
> memcpy(out, buff, out_size);
> retval += out_size;
> memmove(buff, buff + out_size, data_buff - out_size);
> data_buff -= out_size;
> break;
> } else {
> /* No \n and some space left in the out buffer.
> * copy _ALL_ the buffer! */
> memcpy(out, buff, data_buff);
> retval += data_buff;
> out += data_buff;
> out_size -= data_buff;
> data_buff = 0;
> }
> }
>
>
> nr = recv(fd, buff, sizeof(buff), 0);
>
> /* No matter the errors, we have data! */
> if (nr == -1 && retval != 0)
> break;
>
> if (nr == -1) {
> perror("recv");
> close(fd);
> exit(EXIT_FAILURE);
> }
>
> if (nr == 0)
> break;
>
> data_buff = nr;
> }
>
> return retval;
> }
>
>
>
> int main(void) {
> struct addrinfo hints, *res, *rp;
> int sockfd = -1;
> int err;
> unsigned char buff[8 * 1024];
> size_t line_size = 0;
>
>
> memset(&hints, 0, sizeof(hints));
> hints.ai_family = AF_INET;
> hints.ai_socktype = SOCK_STREAM;
> hints.ai_protocol = 0;
> hints.ai_flags = AI_V4MAPPED;
>
> err = getaddrinfo("127.0.0.1", "1337", &hints, &res);
> if (err) {
> fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(err));
> exit(EXIT_FAILURE);
> }
>
> for (rp = res; rp; rp = rp->ai_next) {
> sockfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
> if (sockfd == -1) {
> perror("socket");
> continue;
> }
>
> err = connect(sockfd, rp->ai_addr, rp->ai_addrlen);
> if (err == -1) {
> perror("connect");
> close(sockfd);
> sockfd = -1;
> continue;
> }
>
> break;
> }
>
> freeaddrinfo(res);
>
> if (sockfd == -1) {
> fprintf(stderr, "can't connect at all\n");
> exit(EXIT_FAILURE);
> }
>
> line_size = readline(sockfd, buff, sizeof(buff));
> write(STDOUT_FILENO, buff, line_size);
>
> err = send(sockfd, "XX", 2, 0);
> if (err == -1) {
> perror("send");
> close(sockfd);
> exit(EXIT_FAILURE);
> }
>
> line_size = readline(sockfd, buff, sizeof(buff));
> write(STDOUT_FILENO, buff, line_size);
>
> line_size = readline(sockfd, buff, sizeof(buff));
> write(STDOUT_FILENO, buff, line_size);
>
> err = close(sockfd);
> if (err == -1) {
> perror("close");
> exit(EXIT_FAILURE);
> }
>
> return EXIT_SUCCESS;
> }
>
>
> Thanks for reading that whole long message.
>
> Celelibi
> --
> To unsubscribe from this list: send the line "unsubscribe
> linux-c-programming" in the body of a message to majordomo@vger.kernel.org
> More majordomo info at http://vger.kernel.org/majordomo-info.html
Hi
It seems indeed a little strange that with SO_LINGER option you cannot send
everything. Anyway, at the first look it seems that you have several
programming errors:
- socket ops for the server should be set before starting to listen on the
socket
-in the main function you never exit from the while loop, so why bother to
close the socket after this loop ?
Have a closer look at your code, there might be a bug in your program.
regards
--
Bogdan
next prev parent reply other threads:[~2012-04-10 21:01 UTC|newest]
Thread overview: 13+ messages / expand[flat|nested] mbox.gz Atom feed top
2012-04-09 23:46 close socket and TCP RST Celelibi
2012-04-10 21:01 ` Bogdan Cristea [this message]
2012-04-11 0:38 ` Celelibi
2012-04-11 13:33 ` Glynn Clements
2012-04-11 18:42 ` Bogdan Cristea
2012-04-11 23:31 ` Glynn Clements
2012-04-13 1:54 ` Celelibi
2012-04-13 1:37 ` Celelibi
2012-04-13 7:32 ` Glynn Clements
2012-04-14 15:37 ` Celelibi
2012-04-14 16:13 ` Glynn Clements
2012-04-15 0:41 ` Celelibi
2012-04-16 20:43 ` Glynn Clements
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=1441444.tphEHApd4k@desktop \
--to=cristeab@gmail.com \
--cc=celelibi@gmail.com \
--cc=linux-c-programming@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).