mirror of
http://git.haproxy.org/git/haproxy.git/
synced 2024-12-15 07:54:33 +00:00
c14b7d94a0
These ones were design notes and ideas collected during the 1.5 development phase lying on my development machine. There might still be some value in keeping them for future reference since they mention certain corner cases.
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 konw :
|
|
- 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 (instanciates 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
|
|
|