From 2c6be84b3a94150e4f74f256ba7ef12be7a42f36 Mon Sep 17 00:00:00 2001 From: Willy Tarreau Date: Fri, 6 Jul 2012 17:12:34 +0200 Subject: [PATCH] MEDIUM: connection: extract the send_proxy callback from proto_tcp This handshake handler must be independant, so move it away from proto_tcp. It has a dedicated connection flag. It is tested before I/O handlers and automatically removes the CO_FL_WAIT_L4_CONN flag upon success. It also sets the BF_WRITE_NULL flag on the stream interface and stops the SI timeout. However it does not perform the task_wakeup(), and relies on the data handler to do so for now. The SI wakeup will have to be moved elsewhere anyway. --- include/proto/stream_interface.h | 2 + include/types/connection.h | 2 + src/connection.c | 15 ++++-- src/proto_tcp.c | 82 ++++++++---------------------- src/stream_interface.c | 86 ++++++++++++++++++++++++++++++++ 5 files changed, 121 insertions(+), 66 deletions(-) diff --git a/include/proto/stream_interface.h b/include/proto/stream_interface.h index 7f1cb9e97d..f36af89662 100644 --- a/include/proto/stream_interface.h +++ b/include/proto/stream_interface.h @@ -25,6 +25,7 @@ #include #include +#include #include @@ -32,6 +33,7 @@ int stream_int_check_timeouts(struct stream_interface *si); void stream_int_report_error(struct stream_interface *si); void stream_int_retnclose(struct stream_interface *si, const struct chunk *msg); +int conn_si_send_proxy(struct connection *conn, unsigned int flag); extern struct sock_ops stream_int_embedded; extern struct sock_ops stream_int_task; diff --git a/include/types/connection.h b/include/types/connection.h index 94c8410c3c..6e0b545101 100644 --- a/include/types/connection.h +++ b/include/types/connection.h @@ -36,6 +36,8 @@ enum { CO_FL_NONE = 0x00000000, CO_FL_ERROR = 0x00000001, /* a fatal error was reported */ CO_FL_WAIT_L4_CONN = 0x00000002, /* waiting for L4 to be connected */ + /* flags below are used for connection handshakes */ + CO_FL_SI_SEND_PROXY = 0x00000004, /* send a valid PROXY protocol header */ }; /* This structure describes a connection with its methods and data. diff --git a/src/connection.c b/src/connection.c index 089ad89255..c30e7395b6 100644 --- a/src/connection.c +++ b/src/connection.c @@ -14,7 +14,8 @@ #include #include -#include + +#include /* I/O callback for fd-based connections. It calls the read/write handlers * provided by the connection's sock_ops, which must be valid. It returns @@ -26,22 +27,26 @@ int conn_fd_handler(int fd) int ret = 0; if (!conn) - return ret; + goto leave; if (conn->flags & CO_FL_ERROR) - return ret; + goto leave; + + if (conn->flags & CO_FL_SI_SEND_PROXY) + if ((ret = conn_si_send_proxy(conn, CO_FL_SI_SEND_PROXY))) + goto leave; if (fdtab[fd].ev & (FD_POLL_IN | FD_POLL_HUP | FD_POLL_ERR)) if (!conn->data->read(fd)) ret |= FD_WAIT_READ; if (conn->flags & CO_FL_ERROR) - return ret; + goto leave; if (fdtab[fd].ev & (FD_POLL_OUT | FD_POLL_ERR)) if (!conn->data->write(fd)) ret |= FD_WAIT_WRITE; - + leave: /* remove the events before leaving */ fdtab[fd].ev &= ~(FD_POLL_IN | FD_POLL_OUT | FD_POLL_HUP | FD_POLL_ERR); return ret; diff --git a/src/proto_tcp.c b/src/proto_tcp.c index 87ac849ce6..86ef47b7cf 100644 --- a/src/proto_tcp.c +++ b/src/proto_tcp.c @@ -41,7 +41,7 @@ #include #include #include -#include +//#include #include #include #include @@ -470,18 +470,20 @@ int tcp_connect_server(struct stream_interface *si) fdtab[fd].flags = FD_FL_TCP | FD_FL_TCP_NODELAY; si->conn.flags = CO_FL_WAIT_L4_CONN; /* connection in progress */ - /* If we have nothing to send, we want to confirm that the TCP + /* Prepare to send a few handshakes related to the on-wire protocol. + * If we have nothing to send, we want to confirm that the TCP * connection is established before doing so, so we use our own write * callback then switch to the sock layer. */ - if ((si->ob->flags & BF_OUT_EMPTY) || si->send_proxy_ofs) { + fdtab[fd].cb[DIR_RD].f = NULL; + fdtab[fd].cb[DIR_WR].f = NULL; + + if (si->send_proxy_ofs) + si->conn.flags |= CO_FL_SI_SEND_PROXY; + else if (si->ob->flags & BF_OUT_EMPTY) { fdtab[fd].cb[DIR_RD].f = tcp_connect_read; fdtab[fd].cb[DIR_WR].f = tcp_connect_write; } - else { - fdtab[fd].cb[DIR_RD].f = NULL; - fdtab[fd].cb[DIR_WR].f = NULL; - } fdtab[fd].iocb = conn_fd_handler; fd_insert(fd); @@ -551,64 +553,23 @@ static int tcp_connect_write(int fd) if (b->flags & BF_SHUTW) goto out_wakeup; - /* If we have a PROXY line to send, we'll use this to validate the - * connection, in which case the connection is validated only once - * we've sent the whole proxy line. Otherwise we use connect(). + /* We have no data to send to check the connection, and + * getsockopt() will not inform us whether the connection + * is still pending. So we'll reuse connect() to check the + * state of the socket. This has the advantage of giving us + * the following info : + * - error + * - connecting (EALREADY, EINPROGRESS) + * - connected (EISCONN, 0) */ - if (si->send_proxy_ofs) { - int ret; - - /* The target server expects a PROXY line to be sent first. - * If the send_proxy_ofs is negative, it corresponds to the - * offset to start sending from then end of the proxy string - * (which is recomputed every time since it's constant). If - * it is positive, it means we have to send from the start. - */ - ret = make_proxy_line(trash, trashlen, &b->prod->addr.from, &b->prod->addr.to); - if (!ret) - goto out_error; - - if (si->send_proxy_ofs > 0) - si->send_proxy_ofs = -ret; /* first call */ - - /* we have to send trash from (ret+sp for -sp bytes) */ - ret = send(fd, trash + ret + si->send_proxy_ofs, -si->send_proxy_ofs, - (b->flags & BF_OUT_EMPTY) ? 0 : MSG_MORE); - - if (ret == 0) + if ((connect(fd, conn->peeraddr, conn->peerlen) < 0)) { + if (errno == EALREADY || errno == EINPROGRESS) goto out_ignore; - if (ret < 0) { - if (errno == EAGAIN) - goto out_ignore; + if (errno && errno != EISCONN) goto out_error; - } - si->send_proxy_ofs += ret; /* becomes zero once complete */ - if (si->send_proxy_ofs != 0) - goto out_ignore; - - /* OK we've sent the whole line, we're connected */ - } - else { - /* We have no data to send to check the connection, and - * getsockopt() will not inform us whether the connection - * is still pending. So we'll reuse connect() to check the - * state of the socket. This has the advantage of giving us - * the following info : - * - error - * - connecting (EALREADY, EINPROGRESS) - * - connected (EISCONN, 0) - */ - if ((connect(fd, conn->peeraddr, conn->peerlen) < 0)) { - if (errno == EALREADY || errno == EINPROGRESS) - goto out_ignore; - - if (errno && errno != EISCONN) - goto out_error; - - /* otherwise we're connected */ - } + /* otherwise we're connected */ } /* OK we just need to indicate that we got a connection @@ -629,7 +590,6 @@ static int tcp_connect_write(int fd) task_wakeup(si->owner, TASK_WOKEN_IO); out_ignore: - fdtab[fd].ev &= ~FD_POLL_OUT; return retval; out_error: diff --git a/src/stream_interface.c b/src/stream_interface.c index 5f7ac3dc6e..59eca1bd3d 100644 --- a/src/stream_interface.c +++ b/src/stream_interface.c @@ -28,6 +28,7 @@ #include #include +#include #include #include #include @@ -401,6 +402,91 @@ void stream_int_unregister_handler(struct stream_interface *si) clear_target(&si->target); } +/* This callback is used to send a valid PROXY protocol line to a socket being + * established. It returns a combination of FD_WAIT_* if it wants some polling + * before being called again, otherwise it returns zero and removes itself from + * the connection's flags (the bit is provided in by the caller). + */ +int conn_si_send_proxy(struct connection *conn, unsigned int flag) +{ + int fd = conn->t.sock.fd; + struct stream_interface *si = container_of(conn, struct stream_interface, conn); + struct buffer *b = si->ob; + + /* we might have been called just after an asynchronous shutw */ + if (b->flags & BF_SHUTW) + goto out_error; + + /* If we have a PROXY line to send, we'll use this to validate the + * connection, in which case the connection is validated only once + * we've sent the whole proxy line. Otherwise we use connect(). + */ + if (si->send_proxy_ofs) { + int ret; + + /* The target server expects a PROXY line to be sent first. + * If the send_proxy_ofs is negative, it corresponds to the + * offset to start sending from then end of the proxy string + * (which is recomputed every time since it's constant). If + * it is positive, it means we have to send from the start. + */ + ret = make_proxy_line(trash, trashlen, &b->prod->addr.from, &b->prod->addr.to); + if (!ret) + goto out_error; + + if (si->send_proxy_ofs > 0) + si->send_proxy_ofs = -ret; /* first call */ + + /* we have to send trash from (ret+sp for -sp bytes) */ + ret = send(fd, trash + ret + si->send_proxy_ofs, -si->send_proxy_ofs, + (b->flags & BF_OUT_EMPTY) ? 0 : MSG_MORE); + + if (ret == 0) + goto out_wait; + + if (ret < 0) { + if (errno == EAGAIN) + goto out_wait; + goto out_error; + } + + si->send_proxy_ofs += ret; /* becomes zero once complete */ + if (si->send_proxy_ofs != 0) + goto out_wait; + + /* OK we've sent the whole line, we're connected */ + } + + /* The FD is ready now, simply return and let the connection handler + * notify upper layers if needed. + */ + if (conn->flags & CO_FL_WAIT_L4_CONN) + conn->flags &= ~CO_FL_WAIT_L4_CONN; + b->flags |= BF_WRITE_NULL; + si->exp = TICK_ETERNITY; + + out_leave: + conn->flags &= ~flag; + return 0; + + out_error: + /* Write error on the file descriptor. We mark the FD as STERROR so + * that we don't use it anymore. The error is reported to the stream + * interface which will take proper action. We must not perturbate the + * buffer because the stream interface wants to ensure transparent + * connection retries. + */ + + conn->flags |= CO_FL_ERROR; + fdtab[fd].ev &= ~FD_POLL_STICKY; + EV_FD_REM(fd); + goto out_leave; + + out_wait: + return FD_WAIT_WRITE; +} + + /* * Local variables: * c-indent-level: 8