Help with Gun http/websocket connection - TLS Client: wait_cert_cr at ssl_handshake

I’m trying to use Gun to communicate with the Discord API for educational purposes (I chose Gun for being a lower-level tool to understand some complexities), and I’m having issues during the handshake with the Discord WebSocket gateway. I’m receiving the following error:

(MatchError) no match of right hand side value: {:error, {:down, {:shutdown, {:tls_alert, {:internal_error, ~c"TLS client: In state wait_cert_cr at ssl_handshake .erl:364 generated CLIENT ALERT: Fatal - Internal Error\n {unexpected_error,undef}"}}}}}

Please note that my :gun.ws_upgrade is failing during the handshake when transitioning from HTTP to WebSocket. I have no idea what might be wrong to reach this point with this error. Can you shed some light on what could be the issue and how to fix it?

Here is my code

  def test do
    opts2 = %{
        connect_timeout: 60000,
        retry: 10,
        retry_timeout: 300,
        transport: :tls,
        tls_opts: [
          verify: :verify_none,
          cacerts: :certifi.cacerts(),
          depth: 99,
          reuse_sessions: false
        ],
        http_opts: %{version: :"HTTP/1.1"},
        protocols: [:http],
    }
    opts = %{
        connect_timeout: 60000,
        retry: 10,
        retry_timeout: 300,
        transport: :tls,
        tls_opts: [
          verify: :verify_peer,
          cacertfile: CAStore.file_path(),
          depth: 99,
          reuse_sessions: false,
          verify_fun: {&:ssl_verify_hostname.verify_fun/3, [check_hostname: 'gateway.discord.gg']}
        ],
        http_opts: %{version: :"HTTP/1.1"},
        protocols: [:http],
    }
    {:ok, conn} = :gun.open('discord.com', 443, opts2)
    {:ok, _} = :gun.await_up(conn)
    stream = :gun.get(conn, "/api/v10/gateway")
    {response, :nofin, 200, headers} = :gun.await(conn, stream)
    {:ok, body} = :gun.await_body(conn, stream)
    {:ok, map} = Jason.decode(body)
    IO.inspect(map["url"])

    {:ok, conn} = :gun.open('gateway.discord.gg', 443, opts)
    {:ok, _} = :gun.await_up(conn)
    :timer.sleep(1000)
    stream = :gun.ws_upgrade(conn, '/?v=10&encoding=json', headers, opts)
    receive do
      {:gun_upgrade, _, _} ->
        IO.puts("WebSocket Upgrade Successful")
        connected(conn)
      {:gun_error, _reason} ->
        IO.puts("WebSocket Upgrade Error")
    after
      5000 ->
        IO.puts("WebSocket Upgrade Timeout")
    end
  end

  defp connected(conn) do
    receive do
      something ->
        IO.puts("Received WebSocket Text Frame")
        connected(conn)
      {:gun_close, _status, _reason} ->
        IO.puts("WebSocket Connection Closed")
    after
      5000 ->
        IO.puts("WebSocket Communication Timeout")
    end
  end
1 Like

I got a gun example with a websocket upgrade to work a long time ago. Maybe it will help.