This is for penetration testing purposes, not for any kind of prod setup.
I’d like to set up HAProxy to receive HTTP/2 traffic (h2c, HTTP/2 without TLS) coming from a native application. HAProxy should then convert this traffic to HTTP/1.1 and send it to a desired endpoint.
More particularly, the client that’ll be connecting to HTTP/2 HAProxy non-TLS listener sends the PRI request first, and as such, I’m assuming the HTTP/2 connection mode the client is using is “Starting HTTP/2 with Prior Knowledge” RFC
What settings could I use to have HAProxy listen HTTP/2 without TLS?
I’ve checked the “bind” command in the configuration file language and apart from ALPN (which I’m assuming is an incorrect option here as it implies TLS).
I’m guessing the minimalistic configuration file would be something like (listen for HTTP/2 on port 8009 and pass on to port 8000).
Thank you for the quick response, clears out I should try in another direction such as nginx. If anyone knows another tool that’d do this, please post it here.
Using curl to send http requests with HTTP/1.1 and HTTP/2 (i.e. h2c) shows that while the HTTP/1.1 requests works, the HTTP/2 requests fail. I made HTTP/2 request directly to the backend to verify that h2c works on the backend.
# HTTP/2 directly to backend works
$ curl -v --http2-prior-knowledge --negotiate -u: -X GET "http://localhost:9090/status"
...
* Using HTTP2, server supports multi-use
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* Using Stream ID: 1 (easy handle 0x5613...)
> GET /status HTTP/1.1
> Host: localhost:9090
...
< HTTP/2 200
{"backend-protocol":"HTTP/2.0"}
# HTTP/2 through HAProxy does not work!
$ curl -v --http2-prior-knowledge --negotiate -u: -X GET "http://localhost:8080/status"
...
* Using HTTP2, server supports multi-use
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* Using Stream ID: 1 (easy handle 0x5613...)
> GET /status HTTP/1.1
> Host: localhost:8080
...
* http2 error: Remote peer returned unexpected data while we expected SETTINGS frame. Perhaps, peer does not support HTTP/2 properly.
* Curl_http_done: called premature == 1
* Closing connection 0
curl: (16) Error in the HTTP2 framing layer
# HTTP/1.1 directly to backend reports HTTP/1.1 protocol
$ curl -v --http1.1 --negotiate -u: -X GET "http://localhost:9090/status"
> GET /status HTTP/1.1
> Host: localhost:9090
...
< HTTP/1.1 200
{"backend-protocol":"HTTP/1.1"}
# HTTP/1.1 through HAProxy works
$ curl -v --http1.1 --negotiate -u: -X GET "http://localhost:8080/status"
> GET /status HTTP/1.1
> Host: localhost:9090
...
< HTTP/1.1 200
{"backend-protocol":"HTTP/2.0"}
The problem with putting it on the bind line ( bind *:8080 proto h2) is that now the client can no longer make http/1.1 requests:
$ curl --http2-prior-knowledge http://localhost:8080/status
Hello World
$ curl --http1.1 http://localhost:8080/status
curl: (52) Empty reply from server
This is in contrast to how the SSL support works. When http/1.1 and http/2 (h2) is enabled via alpn, HAProxy can handle both http/1.1 and http/2 requests.
It is possible in Jetty (version 9.4.14.v20181114) to configure a server to use both http/1.1 and h2c listening on the same port. Afaik, it doesn’t need ALPN support to handle both http/1.1 and http/2 requests coming in to the same port.
Couldn’t this be achieved with the following (sub-optimal) setup:
an upstream frontend listening on a single port in TCP mode;
two upstream backends, one for HTTP1 and another for HTTP2 without TLS; (these downstream backends have servers pointing to the downstream frontend respective port;)
the upstream frontend uses TCP data inspection to decide if the request is HTTP2 and if so dispatch it to the proper backend; else use the HTTP1 backend;
a downstream frontend with two bind statements, one for HTTP1 and one for HTTP2 without TLS;