mirror of
http://git.haproxy.org/git/haproxy.git/
synced 2024-12-22 04:10:48 +00:00
2075ca8a93
This is the third round of cleanups in various docs
277 lines
9.3 KiB
Plaintext
277 lines
9.3 KiB
Plaintext
2012/07/05 - Connection layering and sequencing
|
|
|
|
|
|
An FD has a state :
|
|
- CLOSED
|
|
- READY
|
|
- ERROR (?)
|
|
- LISTEN (?)
|
|
|
|
A connection has a state :
|
|
- CLOSED
|
|
- ACCEPTED
|
|
- CONNECTING
|
|
- ESTABLISHED
|
|
- ERROR
|
|
|
|
A stream interface has a state :
|
|
- INI, REQ, QUE, TAR, ASS, CON, CER, EST, DIS, CLO
|
|
|
|
Note that CON and CER might be replaced by EST if the connection state is used
|
|
instead. CON might even be more suited than EST to indicate that a connection
|
|
is known.
|
|
|
|
|
|
si_shutw() must do :
|
|
|
|
data_shutw()
|
|
if (shutr) {
|
|
data_close()
|
|
ctrl_shutw()
|
|
ctrl_close()
|
|
}
|
|
|
|
si_shutr() must do :
|
|
data_shutr()
|
|
if (shutw) {
|
|
data_close()
|
|
ctrl_shutr()
|
|
ctrl_close()
|
|
}
|
|
|
|
Each of these steps may fail, in which case the step must be retained and the
|
|
operations postponed in an asynchronous task.
|
|
|
|
The first asynchronous data_shut() might already fail so it is mandatory to
|
|
save the other side's status with the connection in order to let the async task
|
|
know whether the 3 next steps must be performed.
|
|
|
|
The connection (or perhaps the FD) needs to know :
|
|
- the desired close operations : DSHR, DSHW, CSHR, CSHW
|
|
- the completed close operations : DSHR, DSHW, CSHR, CSHW
|
|
|
|
|
|
On the accept() side, we probably need to know :
|
|
- if a header is expected (eg: accept-proxy)
|
|
- if this header is still being waited for
|
|
=> maybe both info might be combined into one bit
|
|
|
|
- if a data-layer accept() is expected
|
|
- if a data-layer accept() has been started
|
|
- if a data-layer accept() has been performed
|
|
=> possibly 2 bits, to indicate the need to free()
|
|
|
|
On the connect() side, we need to know :
|
|
- the desire to send a header (eg: send-proxy)
|
|
- if this header has been sent
|
|
=> maybe both info might be combined
|
|
|
|
- if a data-layer connect() is expected
|
|
- if a data-layer connect() has been started
|
|
- if a data-layer connect() has been completed
|
|
=> possibly 2 bits, to indicate the need to free()
|
|
|
|
On the response side, we also need to know :
|
|
- the desire to send a header (eg: health check response for monitor-net)
|
|
- if this header was sent
|
|
=> might be the same as sending a header over a new connection
|
|
|
|
Note: monitor-net has precedence over proxy proto and data layers. Same for
|
|
health mode.
|
|
|
|
For multi-step operations, use 2 bits :
|
|
00 = operation not desired, not performed
|
|
10 = operation desired, not started
|
|
11 = operation desired, started but not completed
|
|
01 = operation desired, started and completed
|
|
|
|
=> X != 00 ==> operation desired
|
|
X & 01 ==> operation at least started
|
|
X & 10 ==> operation not completed
|
|
|
|
Note: no way to store status information for error reporting.
|
|
|
|
Note2: it would be nice if "tcp-request connection" rules could work at the
|
|
connection level, just after headers ! This means support for tracking stick
|
|
tables, possibly not too much complicated.
|
|
|
|
|
|
Proposal for incoming connection sequence :
|
|
|
|
- accept()
|
|
- if monitor-net matches or if mode health => try to send response
|
|
- if accept-proxy, wait for proxy request
|
|
- if tcp-request connection, process tcp rules and possibly keep the
|
|
pointer to stick-table
|
|
- if SSL is enabled, switch to SSL handshake
|
|
- then switch to DATA state and instantiate a session
|
|
|
|
We just need a map of handshake handlers on the connection. They all manage the
|
|
FD status themselves and set the callbacks themselves. If their work succeeds,
|
|
they remove themselves from the list. If it fails, they remain subscribed and
|
|
enable the required polling until they are woken up again or the timeout strikes.
|
|
|
|
Identified handshake handlers for incoming connections :
|
|
- HH_HEALTH (tries to send OK and dies)
|
|
- HH_MONITOR_IN (matches src IP and adds/removes HH_SEND_OK/HH_SEND_HTTP_OK)
|
|
- HH_SEND_OK (tries to send "OK" and dies)
|
|
- HH_SEND_HTTP_OK (tries to send "HTTP/1.0 200 OK" and dies)
|
|
- HH_ACCEPT_PROXY (waits for PROXY line and parses it)
|
|
- HH_TCP_RULES (processes TCP rules)
|
|
- HH_SSL_HS (starts SSL handshake)
|
|
- HH_ACCEPT_SESSION (instantiates a session)
|
|
|
|
Identified handshake handlers for outgoing connections :
|
|
- HH_SEND_PROXY (tries to build and send the PROXY line)
|
|
- HH_SSL_HS (starts SSL handshake)
|
|
|
|
For the pollers, we could check that handshake handlers are not 0 and decide to
|
|
call a generic connection handshake handler instead of usual callbacks. Problem
|
|
is that pollers don't know connections, they know fds. So entities which manage
|
|
handlers should update change the FD callbacks accordingly.
|
|
|
|
With a bit of care, we could have :
|
|
- HH_SEND_LAST_CHUNK (sends the chunk pointed to by a pointer and dies)
|
|
=> merges HEALTH, SEND_OK and SEND_HTTP_OK
|
|
|
|
It sounds like the ctrl vs data state for the connection are per-direction
|
|
(eg: support an async ctrl shutw while still reading data).
|
|
|
|
Also support shutr/shutw status at L4/L7.
|
|
|
|
In practice, what we really need is :
|
|
|
|
shutdown(conn) =
|
|
conn.data.shut()
|
|
conn.ctrl.shut()
|
|
conn.fd.shut()
|
|
|
|
close(conn) =
|
|
conn.data.close()
|
|
conn.ctrl.close()
|
|
conn.fd.close()
|
|
|
|
With SSL over Remote TCP (RTCP + RSSL) to reach the server, we would have :
|
|
|
|
HTTP -> RTCP+RSSL connection <-> RTCP+RRAW connection -> TCP+SSL connection
|
|
|
|
The connection has to be closed at 3 places after a successful response :
|
|
- DATA (RSSL over RTCP)
|
|
- CTRL (RTCP to close connection to server)
|
|
- SOCK (FD to close connection to second process)
|
|
|
|
Externally, the connection is seen with very few flags :
|
|
- SHR
|
|
- SHW
|
|
- ERR
|
|
|
|
We don't need a CLOSED flag as a connection must always be detached when it's closed.
|
|
|
|
The internal status doesn't need to be exposed :
|
|
- FD allocated (Y/N)
|
|
- CTRL initialized (Y/N)
|
|
- CTRL connected (Y/N)
|
|
- CTRL handlers done (Y/N)
|
|
- CTRL failed (Y/N)
|
|
- CTRL shutr (Y/N)
|
|
- CTRL shutw (Y/N)
|
|
- DATA initialized (Y/N)
|
|
- DATA connected (Y/N)
|
|
- DATA handlers done (Y/N)
|
|
- DATA failed (Y/N)
|
|
- DATA shutr (Y/N)
|
|
- DATA shutw (Y/N)
|
|
|
|
(note that having flags for operations needing to be completed might be easier)
|
|
--------------
|
|
|
|
Maybe we need to be able to call conn->fdset() and conn->fdclr() but it sounds
|
|
very unlikely since the only functions manipulating this are in the code of
|
|
the data/ctrl handlers.
|
|
|
|
FDSET/FDCLR cannot be directly controlled by the stream interface since it also
|
|
depends on the DATA layer (WANT_READ/WANT_WRITE).
|
|
|
|
But FDSET/FDCLR is probably controlled by who owns the connection (eg: DATA).
|
|
|
|
Example: an SSL conn relies on an FD. The buffer is full, and wants the conn to
|
|
stop reading. It must not stop the FD itself. It is the read function which
|
|
should notice that it has nothing to do with a read wake-up, which needs to
|
|
disable reading.
|
|
|
|
Conversely, when calling conn->chk_rcv(), the reader might get a WANT_READ or
|
|
even WANT_WRITE and adjust the FDs accordingly.
|
|
|
|
------------------------
|
|
|
|
OK, the problem is simple : we don't manipulate the FD at the right level.
|
|
We should have :
|
|
->connect(), ->chk_snd(), ->chk_rcv(), ->shutw(), ->shutr() which are
|
|
called from the upper layer (buffer)
|
|
->recv(), ->send(), called from the lower layer
|
|
|
|
Note that the SHR is *reported* by lower layer but can be forced by upper
|
|
layer. In this case it's like a delayed abort. The difficulty consists in
|
|
knowing the output data were correctly read. Probably we'd need to drain
|
|
incoming data past the active shutr().
|
|
|
|
The only four purposes of the top-down shutr() call are :
|
|
- acknowledge a shut read report : could probably be done better
|
|
- read timeout => disable reading : it's a delayed abort. We want to
|
|
report that the buffer is SHR, maybe even the connection, but the
|
|
FD clearly isn't.
|
|
- read abort due to error on the other side or desire to close (eg:
|
|
http-server-close) : delayed abort
|
|
- complete abort
|
|
|
|
The active shutr() is problematic as we can't disable reading if we expect some
|
|
exchanges for data acknowledgement. We probably need to drain data only until
|
|
the shutw() has been performed and ACKed.
|
|
|
|
A connection shut down for read would behave like this :
|
|
|
|
1) bidir exchanges
|
|
|
|
2) shutr() => read_abort_pending=1
|
|
|
|
3) drain input, still send output
|
|
|
|
4) shutw()
|
|
|
|
5) drain input, wait for read0 or ack(shutw)
|
|
|
|
6) close()
|
|
|
|
--------------------- 2012/07/05 -------------------
|
|
|
|
Communications must be performed this way :
|
|
|
|
connection <-> channel <-> connection
|
|
|
|
A channel is composed of flags and stats, and may store data in either a buffer
|
|
or a pipe. We need low-layer operations between sockets and buffers or pipes.
|
|
Right now we only support sockets, but later we might support remote sockets
|
|
and maybe pipes or shared memory segments.
|
|
|
|
So we need :
|
|
|
|
- raw_sock_to_buf() => receive raw data from socket into buffer
|
|
- raw_sock_to_pipe => receive raw data from socket into pipe (splice in)
|
|
- raw_sock_from_buf() => send raw data from buffer to socket
|
|
- raw_sock_from_pipe => send raw data from pipe to socket (splice out)
|
|
|
|
- ssl_sock_to_buf() => receive ssl data from socket into buffer
|
|
- ssl_sock_to_pipe => receive ssl data from socket into a pipe (NULL)
|
|
- ssl_sock_from_buf() => send ssl data from buffer to socket
|
|
- ssl_sock_from_pipe => send ssl data from pipe to socket (NULL)
|
|
|
|
These functions should set such status flags :
|
|
|
|
#define ERR_IN 0x01
|
|
#define ERR_OUT 0x02
|
|
#define SHUT_IN 0x04
|
|
#define SHUT_OUT 0x08
|
|
#define EMPTY_IN 0x10
|
|
#define FULL_OUT 0x20
|
|
|