1031 Commits

Author SHA1 Message Date
Willy Tarreau
52eed75ced MINOR: h2: match the H2 connection preface on init
The H2 preface is properly detected to switch to the settings state.
It's important to note that for now we don't send out settings frame
so the operation is not complete yet.
2017-10-31 18:16:18 +01:00
Willy Tarreau
081d472f79 MINOR: h2: add a function to send a GOAWAY error frame
For now it's only used to report immediate errors by announcing the
highest known stream-id on the mux's error path. The function may be
used both while processing a stream or directly in relation with the
connection. The wake() callback will automatically ask for send access
if an error is reported. The function should be usable for graceful
shutdowns as well by simply setting h2c->last_sid to the highest
acceptable stream-id (2^31-1) prior to calling the function.

A connection flag (H2_CF_GOAWAY_SENT) is set once the frame was
successfully sent. It will be usable to detect when it's safe to
close the connection.

Another flag (H2_CF_GOAWAY_FAILED) is set in case of unrecoverable
error while trying to send. It will also be used to know when it's safe
to close the connection.
2017-10-31 18:16:18 +01:00
Willy Tarreau
bc933930a7 MEDIUM: h2: start to implement the frames processing loop
The rcv_buf() callback now calls h2_process_demux() after an recv() call
leaving some data in the buffer, and the snd_buf() callback calls
h2_process_mux() to try to process pending data from streams.
2017-10-31 18:16:18 +01:00
Willy Tarreau
5160683fc7 MEDIUM: h2: wake the connection up for send on pending streams
If some streams were blocked on flow control and the connection's
window was recently opened, or if some streams are waiting while
no block flag remains, we immediately want to try to send again.
This can happen if a recv() for a stream wants to send after the
send() loop has already been processed.
2017-10-31 18:16:17 +01:00
Willy Tarreau
29a9824144 MEDIUM: h2: properly consider all conditions for end of connection
During h2_wake(), there are various situations that can lead to the
connection being closed :
  - low-level connection error
  - read0 received
  - fatal error (ERROR2)
  - failed to emit a GOAWAY
  - empty stream list with max_id >= last_sid

In such cases, all streams are notified and we have to wait for all
streams to leave while doing nothing, or if the last stream is gone,
we can simply terminate the connection.

It's important to do this test there again because an error might arise
while trying to send a pending GOAWAY after the last stream for example,
thus there's possibly no way to get notified of a closing stream.
2017-10-31 18:16:17 +01:00
Willy Tarreau
26bd761f01 MINOR: h2: also terminate the connection on shutr
It happens that an H2 mux is totally unusable once the client has shut,
so we must consider this situation equivalent to the connection error,
and let the possible streams drain their data if needed then stop.
2017-10-31 18:16:17 +01:00
Willy Tarreau
fbe3b4fcbe MEDIUM: h2: start to consider the H2_CF_{MUX,DEM}_* flags for polling
Now we start to set the flags to indicate that the response buffer is
being awaited or that it is full, it makes it possible to centralize a
little bit the polling management into the wake() callback.

In case of error, we wake all the streams up so that they are aware of
the nature of the event and are able to detach if needed.
2017-10-31 18:16:17 +01:00
Willy Tarreau
1b62c5caef MINOR: h2: update the {MUX,DEM}_{M,D}ALLOC flags on buffer availability
Flag H2_CF_DEM_DALLOC is set when the demux buffer fails to be allocated
in the recv() callback, and is cleared when it succeeds.

Both flags H2_CF_MUX_MALLOC and H2_CF_DEM_MROOM are cleared when the mux
buffer allocation succeeds.

In both cases it will be up to the callers to report allocation failures.
2017-10-31 18:16:17 +01:00
Willy Tarreau
3ccf4b2a20 MINOR: h2: add the function to create a new stream
This one will be used by the HEADERS frame handler and maybe later by
the PUSH frame handler. It creates a conn_stream in the mux's connection.

The create streams are inserted in the h2c's tree sorted by IDs. The
caller is expected to have verified that the stream doesn't exist yet.
2017-10-31 18:16:17 +01:00
Willy Tarreau
2a8561895d MINOR: h2: create dummy idle and closed streams
It will be more convenient to always manipulate existing streams than
null pointers. Here we create one idle stream and one closed stream.
The idea is that we can easily point any stream to one of these states
in order to merge maintenance operations.
2017-10-31 18:15:51 +01:00
Willy Tarreau
2373acc384 MINOR: h2: add stream lookup function based on the stream ID
The function performs a simple lookup in the tree and returns
either the matching h2s or NULL if not found.
2017-10-31 18:12:14 +01:00
Willy Tarreau
54c150653d MINOR: h2: add a few functions to retrieve contents from a wrapping buffer
Functions h2_get_buf_n{16,32,64}() and h2_get_buf_bytes() respectively
extract a network-ordered 16/32/64 bit value from a possibly wrapping
buffer, or any arbitrary size. They're convenient to retrieve a PING
payload or to parse SETTINGS frames. Since they copy one byte at a time,
they will be less efficient than a memcpy-based implementation on large
blocks.
2017-10-31 18:12:14 +01:00
Willy Tarreau
715d5316e5 MINOR: h2: new function h2_peek_frame_hdr() to retrieve a new frame header
This function extracts the next frame header but doesn't consume it.
This will allow to detect a stream-id change and to perform a yielding
window update without losing information. The result is stored into a
temporary frame descriptor. We could also store the next frame header
into the connection but parsing the header again is much cheaper than
wasting bytes in the connection for a rare use case.

A function (h2_skip_frame_hdr()) is also provided to skip the parsed
header (always 9 bytes) and another one (h2_get_frame_hdr()) to do both
at once.
2017-10-31 18:12:14 +01:00
Willy Tarreau
e482074c96 MINOR: h2: add h2_set_frame_size() to update the size in a binary frame
This function is called after preparing a frame, in order to update the
frame's size in the frame header. It takes the frame payload length in
argument.

It simply writes a 24-bit frame size into a buffer, making use of the
net_helper functions which try to optimize per platform (this is a
frequently used operation).
2017-10-31 18:12:14 +01:00
Willy Tarreau
2e43f08c60 MINOR: h2: new function h2s_error() to mark an error on a stream
This one will store the error into the stream's errcode if it's neither
idle nor closed (since these ones are read-only) and switch its state to
H2_SS_ERROR. If a conn_stream is attached, it will be flagged with
CS_FL_ERROR.
2017-10-31 18:12:14 +01:00
Willy Tarreau
741d6df870 MINOR: h2: new function h2c_error to mark an error on the connection
This one sets the error code in h2c->errcode and changes the connection's
stat to H2_CS_ERROR.
2017-10-31 18:12:14 +01:00
Willy Tarreau
5b5e68741a MINOR: h2: small function to know when the mux is busy
A mux is busy when any stream id >= 0 is currently being handled
and the current stream's id doesn't match. When no stream is
involved (ie: demuxer), stream 0 is considered. This will be
necessary to know when it's possible to send frames.
2017-10-31 18:12:14 +01:00
Willy Tarreau
71681174f3 MINOR: h2: add function h2s_id() to report a stream's ID
This one supports being called with NULL and returns 0 in this case,
making it easier to check for stream IDs in various send functions.
2017-10-31 18:12:14 +01:00
Willy Tarreau
2e5b60ee18 MINOR: h2: add the connection and stream flags listing the causes for blocking
A demux may be prevented from receiving for the following reasons :
  - no receive buffer could be allocated
  - the receive buffer is full
  - a response is needed and the mux is currently being used by a stream
  - a response is needed and some room could not be found in the mux
    buffer (either full or waiting for allocation)
  - the stream buffer is waiting for allocation
  - the stream buffer is full

A mux may stop accepting data for the following reasons :
  - the buffer could not be allocated
  - the buffer is full

A stream may stop sending data to a mux for the following reaons :
  - the mux is busy processing another stream
  - the mux buffer lacks room (full or not allocated)
  - the mux's flow control prevents from sending
  - the stream's flow control prevents from sending

All these conditions were turned into flags for use by the respective
places.
2017-10-31 18:12:14 +01:00
Willy Tarreau
1439812da8 MEDIUM: h2: implement the mux buffer allocator
The idea is that we may need a mux buffer for anything, ranging from
receiving to sending traffic. For now it's unclear where exactly the
calls will be placed so let's block both send and recv when a buffer
is missing, and re-enable both of them at the end. This will have to
be changed later.
2017-10-31 18:12:14 +01:00
Willy Tarreau
35dbd5d719 MEDIUM: h2: dynamically allocate the demux buffer on Rx
This patch implements a very basic Rx buffer management. The mux needs
an rx buffer to decode the connection's stream. If this buffer it
available upon Rx events, we fill it with whatever input data are
available. Otherwise we try to allocate it and subscribe to the buffer
wait queue in case of failure. In such a situation, a function
"h2_dbuf_available()" will be called once a buffer may be allocated.
The buffer is released if it's still empty after recv().
2017-10-31 18:12:14 +01:00
Willy Tarreau
a2af51291f MEDIUM: h2: implement basic recv/send/wake functions
For now they don't do much since the buffers are not yet allocated, but
the squeletton is here.
2017-10-31 18:12:14 +01:00
Willy Tarreau
32218eb344 MEDIUM: h2: allocate and release the h2c context on connection init/end
The connection's h2c context is now allocated and initialized on mux
initialization, and released on mux destruction. Note that for now the
release() code is never called.
2017-10-31 18:12:14 +01:00
Willy Tarreau
c64051404d MINOR: h2: add a frame header descriptor for incoming frames
This descriptor will be used by the frame parser, it's designed to ease
manipulation of frame length, type, flags and sid.
2017-10-31 18:03:24 +01:00
Willy Tarreau
96060bad26 MINOR: h2: handle two extra stream states for errors
We need to deal with stream error notifications (RST_STREAM) as well as
internal reporting. The problem is that we don't know in which order
this will be done so we can't unilaterally decide to deallocate the
stream. In order to help, we add two extra stream states, H2_SS_ERROR
and H2_SS_RESET. The former mentions that the stream has an error pending
and the latter indicates that the error was already sent and that the
stream is now closed. It's equivalent to H2_SS_CLOSED except that in this
state we'll avoid sending new RST_STREAM as per RFC7540#5.4.2.

With this it will be possible to only detach or deallocate the h2s once
the stream is closed.
2017-10-31 18:03:24 +01:00
Willy Tarreau
183126488b MINOR: h2: create the h2s struct and the associated pool
This describes an HTTP/2 stream with its relation to the connection
and to the conn_stream on the other side.

For now we also allocate request and response state for HTTP/1 because
the internal HTTP representation is HTTP/1 at the moment. Later this
should evolve towards a version-agnostic representation and this H1
message state will disappear.

It's important to consider that the streams are necessarily polarized
depending on h2c : if the connection is incoming, streams initiated by
the connection receive requests and send responses. Otherwise it's the
other way around. Such information is known during the connection
instanciation by h2c_frt_init() and will normally be reflected in the
stream ID (odd=demux from client, even=demux from server). The initial
H2_CS_PREFACE state will also depend on the direction. The current h2c
state machine doesn't allow for outgoing connections as it uses a single
state for both (rx state only). It should be the demux state only.
2017-10-31 18:03:24 +01:00
Willy Tarreau
5ab6b57c6f MINOR: h2: create the h2c struct and allocate its pool
The h2c struct describes an H2 connection context and is assigned as the
mux's context. It has its own pool, allocated at boot time and released
after deinit().
2017-10-31 18:03:24 +01:00
Willy Tarreau
5242ef8095 MINOR: h2: expose tune.h2.max-concurrent-streams to limit the number of streams
This will be advertised in the settings frame.
2017-10-31 18:03:24 +01:00
Willy Tarreau
e6baec0e23 MINOR: h2: expose tune.h2.initial-window-size to configure the window size
This will be advertised in the settings frame.
2017-10-31 18:03:24 +01:00
Willy Tarreau
fe20e5b8c7 MINOR: h2: expose tune.h2.header-table-size to configure the table size
It's the HPACK header table size which is to be advertised in the settings
frames. It defaults to 4096.
2017-10-31 18:03:24 +01:00
Willy Tarreau
62f5269d05 MINOR: h2: create a very minimalistic h2 mux
This one currently does nothing and rejects every connection. It
registers ALPN token "h2".
2017-10-31 18:03:24 +01:00