diff --git a/include/common/compat.h b/include/common/compat.h index 0085a3aa1..a0764b103 100644 --- a/include/common/compat.h +++ b/include/common/compat.h @@ -64,6 +64,13 @@ #define MSG_MORE 0 #endif +/* On Linux 2.4 and above, MSG_TRUNC can be used on TCP sockets to drop any + * pending data. Let's rely on NETFILTER to detect if this is supported. + */ +#ifdef NETFILTER +#define MSG_TRUNC_CLEARS_INPUT +#endif + /* Maximum path length, OS-dependant */ #ifndef MAXPATHLEN #define MAXPATHLEN 128 diff --git a/include/proto/proto_tcp.h b/include/proto/proto_tcp.h index 20ae2df71..07127648f 100644 --- a/include/proto/proto_tcp.h +++ b/include/proto/proto_tcp.h @@ -34,6 +34,7 @@ int tcp_connect_server(struct connection *conn, int data, int delack); int tcp_connect_probe(struct connection *conn); int tcp_get_src(int fd, struct sockaddr *sa, socklen_t salen, int dir); int tcp_get_dst(int fd, struct sockaddr *sa, socklen_t salen, int dir); +int tcp_drain(int fd); int tcp_inspect_request(struct session *s, struct channel *req, int an_bit); int tcp_inspect_response(struct session *s, struct channel *rep, int an_bit); int tcp_exec_req_rules(struct session *s); diff --git a/include/types/protocol.h b/include/types/protocol.h index 0af2ed8a3..e03692a35 100644 --- a/include/types/protocol.h +++ b/include/types/protocol.h @@ -59,6 +59,7 @@ struct protocol { int (*connect)(struct connection *, int data, int delack); /* connect function if any */ int (*get_src)(int fd, struct sockaddr *, socklen_t, int dir); /* syscall used to retrieve src addr */ int (*get_dst)(int fd, struct sockaddr *, socklen_t, int dir); /* syscall used to retrieve dst addr */ + int (*drain)(int fd); /* indicates whether we can safely close the fd */ struct list listeners; /* list of listeners using this protocol */ int nb_listeners; /* number of listeners */ diff --git a/src/checks.c b/src/checks.c index d1b7a367d..315ef7a83 100644 --- a/src/checks.c +++ b/src/checks.c @@ -1210,11 +1210,11 @@ static void event_srv_chk_r(struct connection *conn) */ if (conn->xprt && conn->xprt->shutw) conn->xprt->shutw(conn, 0); - if (conn->ctrl) { - if (!(conn->flags & CO_FL_WAIT_RD)) - recv(conn->t.sock.fd, trash.str, trash.size, MSG_NOSIGNAL|MSG_DONTWAIT); - setsockopt(conn->t.sock.fd, SOL_SOCKET, SO_LINGER, - (struct linger *) &nolinger, sizeof(struct linger)); + + if (conn->ctrl && !(conn->flags & CO_FL_SOCK_RD_SH)) { + if (conn->flags & CO_FL_WAIT_RD || !conn->ctrl->drain || !conn->ctrl->drain(conn->t.sock.fd)) + setsockopt(conn->t.sock.fd, SOL_SOCKET, SO_LINGER, + (struct linger *) &nolinger, sizeof(struct linger)); } __conn_data_stop_both(conn); task_wakeup(t, TASK_WOKEN_IO); diff --git a/src/proto_tcp.c b/src/proto_tcp.c index e1b5d8b9e..049988c4e 100644 --- a/src/proto_tcp.c +++ b/src/proto_tcp.c @@ -77,6 +77,7 @@ static struct protocol proto_tcpv4 = { .enable_all = enable_all_listeners, .get_src = tcp_get_src, .get_dst = tcp_get_dst, + .drain = tcp_drain, .listeners = LIST_HEAD_INIT(proto_tcpv4.listeners), .nb_listeners = 0, }; @@ -98,6 +99,7 @@ static struct protocol proto_tcpv6 = { .enable_all = enable_all_listeners, .get_src = tcp_get_src, .get_dst = tcp_get_dst, + .drain = tcp_drain, .listeners = LIST_HEAD_INIT(proto_tcpv6.listeners), .nb_listeners = 0, }; @@ -513,6 +515,41 @@ int tcp_get_dst(int fd, struct sockaddr *sa, socklen_t salen, int dir) return getsockname(fd, sa, &salen); } +/* Tries to drain any pending incoming data from the socket to reach the + * receive shutdown. Returns non-zero if the shutdown was found, otherwise + * zero. This is useful to decide whether we can close a connection cleanly + * are we must kill it hard. + */ +int tcp_drain(int fd) +{ + int turns = 2; + int len; + + while (turns) { +#ifdef MSG_TRUNC_CLEARS_INPUT + len = recv(fd, NULL, INT_MAX, MSG_DONTWAIT | MSG_NOSIGNAL | MSG_TRUNC); + if (len == -1 && errno == EFAULT) +#endif + len = recv(fd, trash.str, trash.size, MSG_DONTWAIT | MSG_NOSIGNAL); + + if (len == 0) /* cool, shutdown received */ + return 1; + + if (len < 0) { + if (errno == EAGAIN) /* connection not closed yet */ + return 0; + if (errno == EINTR) /* oops, try again */ + continue; + /* other errors indicate a dead connection, fine. */ + return 1; + } + /* OK we read some data, let's try again once */ + turns--; + } + /* some data are still present, give up */ + return 0; +} + /* This is the callback which is set when a connection establishment is pending * and we have nothing to send, or if we have an init function we want to call * once the connection is established. It updates the FD polling status. It diff --git a/src/session.c b/src/session.c index adcfd4e92..7309af00b 100644 --- a/src/session.c +++ b/src/session.c @@ -157,7 +157,8 @@ int session_accept(struct listener *l, int cfd, struct sockaddr_storage *addr) * - HEALTH mode without HTTP check => just send "OK" * - TCP mode from monitoring address => just close */ - recv(cfd, trash.str, trash.size, MSG_DONTWAIT); + if (l->proto->drain) + l->proto->drain(cfd); if (p->mode == PR_MODE_HTTP || (p->mode == PR_MODE_HEALTH && (p->options2 & PR_O2_CHK_ANY) == PR_O2_HTTP_CHK)) send(cfd, "HTTP/1.0 200 OK\r\n\r\n", 19, MSG_DONTWAIT|MSG_NOSIGNAL|MSG_MORE); diff --git a/src/ssl_sock.c b/src/ssl_sock.c index 8a39dade3..7523246e0 100644 --- a/src/ssl_sock.c +++ b/src/ssl_sock.c @@ -1096,7 +1096,8 @@ int ssl_sock_handshake(struct connection *conn, unsigned int flag) * TCP sockets. We first try to drain possibly pending * data to avoid this as much as possible. */ - ret = recv(conn->t.sock.fd, trash.str, trash.size, MSG_NOSIGNAL|MSG_DONTWAIT); + if (conn->ctrl && conn->ctrl->drain) + conn->ctrl->drain(conn->t.sock.fd); if (!conn->err_code) conn->err_code = CO_ER_SSL_HANDSHAKE; goto out_error; @@ -1146,7 +1147,8 @@ int ssl_sock_handshake(struct connection *conn, unsigned int flag) * TCP sockets. We first try to drain possibly pending * data to avoid this as much as possible. */ - ret = recv(conn->t.sock.fd, trash.str, trash.size, MSG_NOSIGNAL|MSG_DONTWAIT); + if (conn->ctrl && conn->ctrl->drain) + conn->ctrl->drain(conn->t.sock.fd); if (!conn->err_code) conn->err_code = CO_ER_SSL_HANDSHAKE; goto out_error;