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.
This commit is contained in:
Willy Tarreau 2012-07-06 17:12:34 +02:00 committed by Willy Tarreau
parent 61ace1b2ca
commit 2c6be84b3a
5 changed files with 121 additions and 66 deletions

View File

@ -25,6 +25,7 @@
#include <stdlib.h>
#include <common/config.h>
#include <types/session.h>
#include <types/stream_interface.h>
@ -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;

View File

@ -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.

View File

@ -14,7 +14,8 @@
#include <common/config.h>
#include <types/connection.h>
#include <types/stream_interface.h>
#include <proto/stream_interface.h>
/* 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;

View File

@ -41,7 +41,7 @@
#include <proto/arg.h>
#include <proto/buffers.h>
#include <proto/connection.h>
#include <proto/frontend.h>
//#include <proto/frontend.h>
#include <proto/log.h>
#include <proto/port_range.h>
#include <proto/protocols.h>
@ -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:

View File

@ -28,6 +28,7 @@
#include <proto/buffers.h>
#include <proto/fd.h>
#include <proto/frontend.h>
#include <proto/sock_raw.h>
#include <proto/stream_interface.h>
#include <proto/task.h>
@ -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 <flag> 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