Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

net: http_server: capturing request headers does not correctly account for concurrent HTTP2 streams #82273

Open
mrodgers-witekio opened this issue Nov 28, 2024 · 3 comments · May be fixed by #82444
Assignees
Labels
area: Networking bug The issue is a bug, or the PR is fixing a bug priority: low Low impact/importance bug

Comments

@mrodgers-witekio
Copy link
Collaborator

mrodgers-witekio commented Nov 28, 2024

Describe the bug
The header capture feature of the HTTP server (CONFIG_HTTP_SERVER_CAPTURE_HEADERS) does not correctly handle HTTP2 concurrent streams. In the case of a dynamic POST request, headers associated with one request could sometimes be accessed by an application callback associated with a different request.

To Reproduce
I think it would actually be quite difficult to create this situation in "real" usage of the HTTP server, but the situation would be:

  • Send two concurrent POST requests to the HTTP server, ideally to two separate paths (I can't think of a logical use-case for concurrent requests from the same client to the same path).
  • The requests should be interleaved: HEADERS1, HEADERS2, DATA1, DATA2
  • The dynamic resource callbacks for each request might be able to access headers associated with the other request. For example the callback sending DATA1 to the application happens after HEADERS2 has been parsed and stored in the client context.

Perhaps the easiest way to demonstrate this is the failing test case I've created in this branch: https://github.com/mrodgers-witekio/zephyr/tree/http2_request_headers_concurrent_streams

Expected behavior
Each request should call the dynamic response callback with the correct headers associated with that request.

Impact
Minimal, since concurrent HTTP2 POST requests with the header capture feature enabled and different headers in each request is unlikely to happen often. The issue shouldn't happen with concurrent GET requests, since the header frames of two different streams cannot be interleaved, only header frames with data frames.

Additional context
I didn't consider this situation when implementing the header capture feature, and only just noticed the issue now. I have a few ideas on how to fix, but it would be good to get some thoughts on the right way to go before I submit a PR.

Based on RFC9113 section 4.3 I think that headers from different streams cannot not be interleaved (ie. HEADERS_START_1, HEADERS_START2, CONTINUATION_1, ...). If my understanding is correct, it makes fixing the issue significantly simpler since we do not need to simultaneously store headers for multiple different streams.

So for HTTP/2 we could call the application callback as soon as the headers are parsed to pass the headers to the application, even if no data is sent yet. Then the headers can be cleared ready for a request on a different stream. Any subsequent callbacks providing data to the application would not contain the header data which was passed in the first callback.

Since the application callback currently just accesses the headers from the client_ctx struct this cannot currently work, so a different way of passing the headers to the application would be needed. Maybe an additional parameter to the callback for request headers, or combine the request headers and data into a struct http_request_ctx similar to the struct htp_response_ctx used to pass response data back in the other direction?

Both of these would require a change to the dynamic resource callback signature which would break existing applications. If we want to avoid this, then maybe adding a pointer to the headers in the client_ctx struct, which points to the existing header_capture_ctx if the headers are valid for a given callback, or is set to NULL if not.

@mrodgers-witekio mrodgers-witekio added the bug The issue is a bug, or the PR is fixing a bug label Nov 28, 2024
@jukkar
Copy link
Member

jukkar commented Nov 28, 2024

Both of these would require a change to the dynamic resource callback signature which would break existing applications.

I think we can still break the API if needed, it is not considered stable API yet. We need to document these API changes for example in migration guide.

@mrodgers-witekio
Copy link
Collaborator Author

mrodgers-witekio commented Nov 28, 2024

I think we can still break the API if needed, it is not considered stable API yet. We need to document these API changes for example in migration guide.

In that case I think something like:

typedef int (*http_resource_dynamic_cb_t)(struct http_client_ctx *client,
					  enum http_data_status status,
					  struct http_request_ctx *request_ctx,
					  struct http_response_ctx *response_ctx,
					  void *user_data);

struct http_request_ctx {
        uint8_t *data;
        size_t data_len;
        struct http_header *headers;
        enum http_header_status header_status;
        size_t header_count;
};

would be a good option, with the struct http_request_ctx being quite similar to the struct http_response_ctx.

@rlubos
Copy link
Contributor

rlubos commented Nov 28, 2024

Based on RFC9113 section 4.3 I think that headers from different streams cannot not be interleaved (ie. HEADERS_START_1, HEADERS_START2, CONTINUATION_1, ...). If my understanding is correct, it makes fixing the issue significantly simpler since we do not need to simultaneously store headers for multiple different streams.

So for HTTP/2 we could call the application callback as soon as the headers are parsed to pass the headers to the application, even if no data is sent yet. Then the headers can be cleared ready for a request on a different stream. Any subsequent callbacks providing data to the application would not contain the header data which was passed in the first callback.

That sounds reasonable, you're right that the client should not mixup header frames from different streams - if one header frame does not carry a full set of headers, it has to be followed with continuation on the same stream.

with the struct http_request_ctx being quite similar to the struct http_response_ctx.

I like the idea, seems pretty consistent.

mrodgers-witekio added a commit to mrodgers-witekio/zephyr that referenced this issue Dec 2, 2024
Concurrent HTTP POST requests on different HTTP2 concurrent streams
require that the client's header_capture_context is re-used to capture
headers on a second stream before all of the body data has been received
(and sent to the application) on the first stream.

As a result, any captured headers must be sent to the application
callback before any headers can be received on a different stream. In
practice this means that for HTTP2 the application callback is called
for the first time on receiving a headers frame, before any data frames
are received. All subsequent application callbacks will not include the
request header data.

While this mechanism is not necessary for HTTP1, it is also updated to
only send headers in the first application callback for consistency.

Fixes zephyrproject-rtos#82273

Signed-off-by: Matt Rodgers <[email protected]>
mrodgers-witekio added a commit to mrodgers-witekio/zephyr that referenced this issue Dec 3, 2024
Concurrent HTTP POST requests on different HTTP2 concurrent streams
require that the client's header_capture_context is re-used to capture
headers on a second stream before all of the body data has been received
(and sent to the application) on the first stream.

As a result, any captured headers must be sent to the application
callback before any headers can be received on a different stream. In
practice this means that for HTTP2 the application callback is called
for the first time on receiving a headers frame, before any data frames
are received. All subsequent application callbacks will not include the
request header data.

While this mechanism is not necessary for HTTP1, it is also updated to
only send headers in the first application callback for consistency.

Fixes zephyrproject-rtos#82273

Signed-off-by: Matt Rodgers <[email protected]>
mrodgers-witekio added a commit to mrodgers-witekio/zephyr that referenced this issue Dec 3, 2024
Concurrent HTTP POST requests on different HTTP2 concurrent streams
require that the client's header_capture_context is re-used to capture
headers on a second stream before all of the body data has been received
(and sent to the application) on the first stream.

As a result, any captured headers must be sent to the application
callback before any headers can be received on a different stream. In
practice this means that for HTTP2 the application callback is called
for the first time on receiving a headers frame, before any data frames
are received. All subsequent application callbacks will not include the
request header data.

While this mechanism is not necessary for HTTP1, it is also updated to
only send headers in the first application callback for consistency.

Fixes zephyrproject-rtos#82273

Signed-off-by: Matt Rodgers <[email protected]>
mrodgers-witekio added a commit to mrodgers-witekio/zephyr that referenced this issue Dec 3, 2024
Concurrent HTTP POST requests on different HTTP2 concurrent streams
require that the client's header_capture_context is re-used to capture
headers on a second stream before all of the body data has been received
(and sent to the application) on the first stream.

As a result, any captured headers must be sent to the application
callback before any headers can be received on a different stream. In
practice this means that for HTTP2 the application callback is called
for the first time on receiving a headers frame, before any data frames
are received. All subsequent application callbacks will not include the
request header data.

While this mechanism is not necessary for HTTP1, it is also updated to
only send headers in the first application callback for consistency.

Fixes zephyrproject-rtos#82273

Signed-off-by: Matt Rodgers <[email protected]>
@kartben kartben added the priority: low Low impact/importance bug label Dec 3, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area: Networking bug The issue is a bug, or the PR is fixing a bug priority: low Low impact/importance bug
Projects
None yet
4 participants