mtrudel/bandit

Disabling HTTP/2 causes client errors

wojtekmach opened this issue · 7 comments

Hey, I'm not sure if it's a bug or I'm misunderstanding things. We're adding ALPN support to Finch and I wanted to test it against a web server without HTTP/2.

In the following snippet I'm using Mint with protocols: [:http1, :http2] against Bandit with http_2_options: [enabled: false]:

Mix.install([
  :mint,
  :bandit
])

defmodule Main do
  def main do
    unless File.exists?("selfsigned.pem") do
      File.write!("selfsigned.pem", """
      -----BEGIN CERTIFICATE-----
      MIICzjCCAbYCCQDM0i9xf9D8qTANBgkqhkiG9w0BAQsFADApMQswCQYDVQQGEwJJ
      VDELMAkGA1UECAwCUk0xDTALBgNVBAcMBFJvbWUwHhcNMTcxMjI4MTAzMTE1WhcN
      MTgxMjI4MTAzMTE1WjApMQswCQYDVQQGEwJJVDELMAkGA1UECAwCUk0xDTALBgNV
      BAcMBFJvbWUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6lBy3mpfB
      UrtclV/PFOM8OGiBNYVZmNJmZqCZCl2LQE5ekPrJ2Fh+zkcJcO19OxmseN+2F7VT
      zCYarty+h5ZXzNUmUcI2Ld60mfYwEfMjQRa4Tmp0K5PkphJ2gG9n9QOhFxky7KWz
      C84oe7Zm8iGni6wAQEEBOdo/qTCfGbPHzd39WUV+9Aft8HeDUcnpMhO6vXWDT3Yh
      658p04rXLzj8auyAZpfSq61x9ZS4WQYWB5vRLsJ4/V51RVGfA5nJYFKJ+cwZR4Hz
      bTRVc34rXaZS9ggIp8ktqL14NO6jfo9/dRng4RcTmkKMxU+0pWdTNZ7iPJX46/xM
      0XGxEd+7X4uHAgMBAAEwDQYJKoZIhvcNAQELBQADggEBABygqnZTQ4dsd5EFQKF7
      UT7ZfKi9a65e+iDGHikhUjHc+hSUMXFyP5RjpN6Z+4igi9LuWhJFZ0dqSZxDwCYG
      RdrMZSM/2yTBLKVdgcKXPuiV5eFPXHmYm39ru/WpNEqR/P28Q50xz/HJRoFhg3Qe
      AIlncG+v6AaUAKD8Qj6IZOLIVJuMaT8ONsDaa2LJiAz5uzKwgijEWiw7m83dvqGi
      FHkrj9/l5SQQVLGej/74Av+OFmMRI6nPc5lIu39atMRxsiPubrcQOVZmXZxRSEg8
      P7k3nBjtxCUhAnokjRqv/4rYfm8hvbqiRnL3rmtLlM1IF2L37nOqnfGo2NikjV+G
      1hU=
      -----END CERTIFICATE-----
      """)
    end

    unless File.exists?("selfsigned_key.pem") do
      File.write!("selfsigned_key.pem", """
      -----BEGIN PRIVATE KEY-----
      MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC6lBy3mpfBUrtc
      lV/PFOM8OGiBNYVZmNJmZqCZCl2LQE5ekPrJ2Fh+zkcJcO19OxmseN+2F7VTzCYa
      rty+h5ZXzNUmUcI2Ld60mfYwEfMjQRa4Tmp0K5PkphJ2gG9n9QOhFxky7KWzC84o
      e7Zm8iGni6wAQEEBOdo/qTCfGbPHzd39WUV+9Aft8HeDUcnpMhO6vXWDT3Yh658p
      04rXLzj8auyAZpfSq61x9ZS4WQYWB5vRLsJ4/V51RVGfA5nJYFKJ+cwZR4HzbTRV
      c34rXaZS9ggIp8ktqL14NO6jfo9/dRng4RcTmkKMxU+0pWdTNZ7iPJX46/xM0XGx
      Ed+7X4uHAgMBAAECggEAW+VNi6UJ778m5z/vU5iPH38NAe7xgiLCJouPuDEhx89h
      ijRQQZBcbgB9fonvfwnX6FoUnaRpvB9F+Uh9Ex7HDvGlXl1Qkczf7wYR+rUskwWh
      AiAlUJiSHEErwNAbjxFfuz0cPTfPmTNMVCYyvduudc5WZj0/hzIOa+KSPxqysMq+
      kVEuoKm4yzVpbsvfVjGFhYyetFfqbURZ1d0EONGNiYCtwVPTP+gvcjeMTdgGMno4
      cpPHFF5RYsfnG/koaARHbOBtFrnkERpmABfsSL6DzwWtQys/ghxwaL2ZhjhdgbXO
      9vjstnXso9mjCAxVZaATyCs6QKXCn//T9BLRkutz+QKBgQDhlwuz2Df7TZwsCOqM
      PoYn0GhsY/hHupifEiWvDDtkGCAPuiw48HtCe1aCXFCtbSN/6E3Wdm7ZnG2q7RMY
      R5jxEEhnrBGEN7JQhAZPdktndVbupOXMgnGdiz/7nwHPCCghr3X/mdPWY0isMqx/
      T7Bl1bv2C5ipOAqHUER7AKd6awKBgQDTus7n3mwYQusGBqhygmpqOr/5JmTjOcFb
      fGbyuOE9JntLSGR6mJhlfOYQiESvFhBZEOtw4eDh/n7r8LnV48x6D009466hBY3+
      Fs5jTq+Ah2a9gxCRhSfUdEXhrJct+YHEjI+BNvXlUs/2D/y3rvc/2f+qa6nzegTI
      1NcmtlEyVQKBgE2dPjV+KqSXqyerWacuy9Fe7s58BqwHEwOHptd3CegCNOW0VAqz
      EnVpIfZv9IH2jsQvFLi4vqK4IzMvpeYwm/o0c/TXSp+G2h7BjbpBJOhPgr1Qlo+q
      QZTGmBjmOCUW1VfhmmN6dVvJhPNZ6+dRb4tZ4fVhQADYeybbAvSe4QBJAoGAbn1V
      6/pOPnrtWr+ut9MG5VizRbmbfFhvZuaMcq24HMkwHiExDikDnjKHfKkf7p58+X2y
      372ANW8xnL6Ku+uckTXbASkHwE+9wZL1MS2muFPwcYUr6ESsfFoQ/aurWPqTlZYk
      bTHZMEr+61F8d/5+WHvSx4RXtA9A3+zyOel6heECgYEAxqgiod1asd9g6Ao0vQ5Q
      YIQihF9Qq15stxj5H0qnBskvDKwS+He8GKjMUJjJss0gD43KCYhRvE8bmMD+rgVJ
      oy8Hp5oGeMVUHMG7ly8vdAj01RWxU2oIL5wlk3hX+4Pb6XyAN+NuCTMCQ8XukfTf
      AyfpMLycjN7uWN86CdTv/YY=
      -----END PRIVATE KEY-----
      """)
    end

    defmodule MyPlug do
      def init(options) do
        options
      end

      def call(conn, _) do
        {adapter, _} = conn.adapter
        Plug.Conn.send_resp(conn, 200, inspect(adapter))
      end
    end

    {:ok, _} =
      Bandit.start_link(
        scheme: :https,
        port: 4000,
        plug: MyPlug,
        certfile: Path.expand("selfsigned.pem"),
        keyfile: Path.expand("selfsigned_key.pem"),
        http_2_options: [enabled: false]
      )

    {:ok, conn} =
      Mint.HTTP.connect(:https, "localhost", 4000,
        transport_opts: [
          verify: :verify_none
        ],
        mode: :passive,
        protocols: [:http1, :http2]
      )

    case Mint.HTTP.request(conn, "GET", "/", [], "") do
      {:ok, _conn, ref} ->
        IO.inspect(ref)

      {:error, _conn, exception} ->
        raise exception
    end
  end
end

Main.main()

It raises:

15:48:24.913 [info] Running Main.MyPlug with Bandit 1.1.0 at 0.0.0.0:4000 (https)
** (Mint.TransportError) socket closed
    req.exs:99: Main.main/0
    req.exs:104: (file)

Running curl against this server:

$ curl --insecure -v http://localhost:4000
*   Trying 127.0.0.1:4000...
* Connected to localhost (127.0.0.1) port 4000 (#0)
> GET / HTTP/1.1
> Host: localhost:4000
> User-Agent: curl/8.1.2
> Accept: */*
>
* Received HTTP/0.9 when not allowed
* Closing connection 0
curl: (1) Received HTTP/0.9 when not allowed

What names are you using within ALPN for the protocols? Bandit expects either h2 or http/1.1 per https://github.com/mtrudel/bandit/blob/main/lib/bandit/initial_handler.ex#L50-L51

The 'received HTTP/0.9` suggests to me that Bandit is erroring out and sending a fallback 400 response to the client. Do you have any logs on the server side? I'd expect Bandit to be noisy in any case.

About curl, sorry, user error. I was using http instead of https. For http:// bandit indeed printed:

16:09:44.954 [notice] TLS :server: In state :hello at tls_record.erl:561 generated SERVER ALERT: Fatal - Unexpected Message
 - {:unsupported_record_type, 71}

Still getting curl error for https:// though:

$ curl --insecure -v https://localhost:4000
*   Trying 127.0.0.1:4000...
* Connected to localhost (127.0.0.1) port 4000 (#0)
* ALPN: offers h2,http/1.1
* (304) (OUT), TLS handshake, Client hello (1):
* (304) (IN), TLS handshake, Server hello (2):
* (304) (IN), TLS handshake, Unknown (8):
* (304) (IN), TLS handshake, Certificate (11):
* (304) (IN), TLS handshake, CERT verify (15):
* (304) (IN), TLS handshake, Finished (20):
* (304) (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / AEAD-CHACHA20-POLY1305-SHA256
* ALPN: server accepted h2
* Server certificate:
*  subject: C=IT; ST=RM; L=Rome
*  start date: Dec 28 10:31:15 2017 GMT
*  expire date: Dec 28 10:31:15 2018 GMT
*  issuer: C=IT; ST=RM; L=Rome
*  SSL certificate verify result: self signed certificate (18), continuing anyway.
* using HTTP/2
* h2 [:method: GET]
* h2 [:scheme: https]
* h2 [:authority: localhost:4000]
* h2 [:path: /]
* h2 [user-agent: curl/8.1.2]
* h2 [accept: */*]
* Using Stream ID: 1 (easy handle 0x124813800)
> GET / HTTP/2
> Host: localhost:4000
> User-Agent: curl/8.1.2
> Accept: */*
>
* Closing connection 0
curl: (56) Failure when receiving data from the peer

regarding ALPN, Mint seems to be using correct values: https://github.com/elixir-mint/mint/blob/v1.5.1/lib/mint/negotiate.ex#L14.

Curious. Let me try and repro here.

I see it.

The initial handler is seeing an ALPN protocol of h2, but because we're not configured to accept HTTP/2 it's closing the connection here.

The actual problem is that we're still registering h2 support here, when we should be only registering the actually supported protocols.

I'll get a fix up for this right away

PR is up - testing locally shows that our ALPN advertisements are as expected

Published as 1.1.1