diff --git a/include/proto/connection.h b/include/proto/connection.h index a7647bd8f..001276de4 100644 --- a/include/proto/connection.h +++ b/include/proto/connection.h @@ -43,6 +43,7 @@ extern struct mux_proto_list mux_proto_list; * provided by the connection's sock_ops. */ void conn_fd_handler(int fd); +int conn_fd_check(struct connection *conn); /* receive a PROXY protocol header over a connection */ int conn_recv_proxy(struct connection *conn, int flag); diff --git a/include/proto/proto_tcp.h b/include/proto/proto_tcp.h index 76f89b1f3..daac1d836 100644 --- a/include/proto/proto_tcp.h +++ b/include/proto/proto_tcp.h @@ -30,7 +30,6 @@ int tcp_bind_socket(int fd, int flags, struct sockaddr_storage *local, struct sockaddr_storage *remote); int tcp_pause_listener(struct listener *l); int tcp_connect_server(struct connection *conn, int flags); -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); diff --git a/src/connection.c b/src/connection.c index 6c8855fd5..3f3e99d12 100644 --- a/src/connection.c +++ b/src/connection.c @@ -91,7 +91,7 @@ void conn_fd_handler(int fd) * attempted yet to probe the connection. Then let's retry the * connect(). */ - if (!tcp_connect_probe(conn)) + if (!conn_fd_check(conn)) goto leave; } @@ -191,6 +191,97 @@ void conn_update_xprt_polling(struct connection *c) c->flags = f; } +/* This is the callback which is set when a connection establishment is pending + * and we have nothing to send. It may update the FD polling status to indicate + * !READY. It returns 0 if it fails in a fatal way or needs to poll to go + * further, otherwise it returns non-zero and removes the CO_FL_WAIT_L4_CONN + * flag from the connection's flags. In case of error, it sets CO_FL_ERROR and + * leaves the error code in errno. + */ +int conn_fd_check(struct connection *conn) +{ + struct sockaddr_storage *addr; + int fd = conn->handle.fd; + + if (conn->flags & CO_FL_ERROR) + return 0; + + if (!conn_ctrl_ready(conn)) + return 0; + + if (!(conn->flags & CO_FL_WAIT_L4_CONN)) + return 1; /* strange we were called while ready */ + + if (!fd_send_ready(fd)) + return 0; + + /* Here we have 2 cases : + * - modern pollers, able to report ERR/HUP. If these ones return any + * of these flags then it's likely a failure, otherwise it possibly + * is a success (i.e. there may have been data received just before + * the error was reported). + * - select, which doesn't report these and with which it's always + * necessary either to try connect() again or to check for SO_ERROR. + * In order to simplify everything, we double-check using connect() as + * soon as we meet either of these delicate situations. Note that + * SO_ERROR would clear the error after reporting it! + */ + if (cur_poller.flags & HAP_POLL_F_ERRHUP) { + /* modern poller, able to report ERR/HUP */ + if ((fdtab[fd].ev & (FD_POLL_IN|FD_POLL_ERR|FD_POLL_HUP)) == FD_POLL_IN) + goto done; + if ((fdtab[fd].ev & (FD_POLL_OUT|FD_POLL_ERR|FD_POLL_HUP)) == FD_POLL_OUT) + goto done; + if (!(fdtab[fd].ev & (FD_POLL_ERR|FD_POLL_HUP))) + goto wait; + /* error present, fall through common error check path */ + } + + /* Use connect() to check the state of the socket. This has the double + * advantage of *not* clearing the error (so that health checks can + * still use getsockopt(SO_ERROR)) and giving us the following info : + * - error + * - connecting (EALREADY, EINPROGRESS) + * - connected (EISCONN, 0) + */ + addr = conn->dst; + if ((conn->flags & CO_FL_SOCKS4) && obj_type(conn->target) == OBJ_TYPE_SERVER) + addr = &objt_server(conn->target)->socks4_addr; + + if (connect(fd, (const struct sockaddr *)addr, get_addr_len(addr)) == -1) { + if (errno == EALREADY || errno == EINPROGRESS) + goto wait; + + if (errno && errno != EISCONN) + goto out_error; + } + + done: + /* The FD is ready now, we'll mark the connection as complete and + * forward the event to the transport layer which will notify the + * data layer. + */ + conn->flags &= ~CO_FL_WAIT_L4_CONN; + fd_may_send(fd); + fd_cond_recv(fd); + errno = 0; // make health checks happy + return 1; + + out_error: + /* Write error on the file descriptor. Report it to the connection + * and disable polling on this FD. + */ + fdtab[fd].linger_risk = 0; + conn->flags |= CO_FL_ERROR | CO_FL_SOCK_RD_SH | CO_FL_SOCK_WR_SH; + __conn_xprt_stop_both(conn); + return 0; + + wait: + __conn_xprt_want_send(conn); + fd_cant_send(fd); + return 0; +} + /* Send a message over an established connection. It makes use of send() and * returns the same return code and errno. If the socket layer is not ready yet * then -1 is returned and ENOTSOCK is set into errno. If the fd is not marked diff --git a/src/proto_tcp.c b/src/proto_tcp.c index 5ed15a985..df4e5a4d2 100644 --- a/src/proto_tcp.c +++ b/src/proto_tcp.c @@ -636,97 +636,6 @@ int tcp_get_dst(int fd, struct sockaddr *sa, socklen_t salen, int dir) } } -/* This is the callback which is set when a connection establishment is pending - * and we have nothing to send. It updates the FD polling status. It returns 0 - * if it fails in a fatal way or needs to poll to go further, otherwise it - * returns non-zero and removes the CO_FL_WAIT_L4_CONN flag from the connection's - * flags. In case of error, it sets CO_FL_ERROR and leaves the error code in - * errno. - */ -int tcp_connect_probe(struct connection *conn) -{ - struct sockaddr_storage *addr; - int fd = conn->handle.fd; - - if (conn->flags & CO_FL_ERROR) - return 0; - - if (!conn_ctrl_ready(conn)) - return 0; - - if (!(conn->flags & CO_FL_WAIT_L4_CONN)) - return 1; /* strange we were called while ready */ - - if (!fd_send_ready(fd)) - return 0; - - /* Here we have 2 cases : - * - modern pollers, able to report ERR/HUP. If these ones return any - * of these flags then it's likely a failure, otherwise it possibly - * is a success (i.e. there may have been data received just before - * the error was reported). - * - select, which doesn't report these and with which it's always - * necessary either to try connect() again or to check for SO_ERROR. - * In order to simplify everything, we double-check using connect() as - * soon as we meet either of these delicate situations. Note that - * SO_ERROR would clear the error after reporting it! - */ - if (cur_poller.flags & HAP_POLL_F_ERRHUP) { - /* modern poller, able to report ERR/HUP */ - if ((fdtab[fd].ev & (FD_POLL_IN|FD_POLL_ERR|FD_POLL_HUP)) == FD_POLL_IN) - goto done; - if ((fdtab[fd].ev & (FD_POLL_OUT|FD_POLL_ERR|FD_POLL_HUP)) == FD_POLL_OUT) - goto done; - if (!(fdtab[fd].ev & (FD_POLL_ERR|FD_POLL_HUP))) - goto wait; - /* error present, fall through common error check path */ - } - - /* Use connect() to check the state of the socket. This has the double - * advantage of *not* clearing the error (so that health checks can - * still use getsockopt(SO_ERROR)) and giving us the following info : - * - error - * - connecting (EALREADY, EINPROGRESS) - * - connected (EISCONN, 0) - */ - addr = conn->dst; - if ((conn->flags & CO_FL_SOCKS4) && obj_type(conn->target) == OBJ_TYPE_SERVER) - addr = &objt_server(conn->target)->socks4_addr; - - if (connect(fd, (const struct sockaddr *)addr, get_addr_len(addr)) == -1) { - if (errno == EALREADY || errno == EINPROGRESS) - goto wait; - - if (errno && errno != EISCONN) - goto out_error; - } - - done: - /* The FD is ready now, we'll mark the connection as complete and - * forward the event to the transport layer which will notify the - * data layer. - */ - conn->flags &= ~CO_FL_WAIT_L4_CONN; - fd_may_send(fd); - fd_cond_recv(fd); - errno = 0; // make health checks happy - return 1; - - out_error: - /* Write error on the file descriptor. Report it to the connection - * and disable polling on this FD. - */ - fdtab[fd].linger_risk = 0; - conn->flags |= CO_FL_ERROR | CO_FL_SOCK_RD_SH | CO_FL_SOCK_WR_SH; - __conn_xprt_stop_both(conn); - return 0; - - wait: - __conn_xprt_want_send(conn); - fd_cant_send(fd); - return 0; -} - /* XXX: Should probably be elsewhere */ static int compare_sockaddr(struct sockaddr_storage *a, struct sockaddr_storage *b) {