mirror of
http://git.haproxy.org/git/haproxy.git/
synced 2024-12-12 22:44:32 +00:00
4658c4dfe5
This contains some thoughts about how to improve connection management for 1.5 in order to support SSL.
194 lines
6.3 KiB
Plaintext
194 lines
6.3 KiB
Plaintext
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).
|
|
|