PcapFileEx.HTTP2 (pcap_file_ex v0.5.5)

View Source

HTTP/2 cleartext (h2c) stream reconstruction.

Parses HTTP/2 frames from TCP payloads and reconstructs complete request/response exchanges. Supports prior-knowledge h2c only (no HTTP/1.1 Upgrade flow).

Example

{:ok, complete, incomplete} = PcapFileEx.HTTP2.analyze("capture.pcap")

IO.puts("Complete: #{length(complete)}, Incomplete: #{length(incomplete)}")

Enum.each(complete, fn ex ->
  IO.puts("#{ex.request.method} #{ex.request.path} -> #{ex.response.status}")
end)

Limitations

  • Cleartext only: No TLS-encrypted HTTP/2 (h2)
  • Prior-knowledge h2c only: No HTTP/1.1 Upgrade flow support
  • No server push: PUSH_PROMISE frames are ignored
  • Analysis only: No playback server implementation

Mid-Connection Capture

When capturing starts after the HTTP/2 connection is established:

  • Client identification falls back to stream ID semantics
  • Some HPACK dynamic table entries may be missing (static table works)
  • SETTINGS frames are deferred until client is identified

Summary

Functions

Analyzes a PCAP file and returns HTTP/2 exchanges.

Analyzes directional TCP segments directly.

Returns the HTTP/2 connection preface string.

Check if binary data starts with HTTP/2 connection preface.

Functions

analyze(pcap_path, opts \\ [])

@spec analyze(
  Path.t(),
  keyword()
) ::
  {:ok, [PcapFileEx.HTTP2.Exchange.t()],
   [PcapFileEx.HTTP2.IncompleteExchange.t()]}
  | {:error, term()}

Analyzes a PCAP file and returns HTTP/2 exchanges.

Returns {:ok, complete, incomplete} where:

  • complete - List of fully completed request/response exchanges
  • incomplete - List of partial exchanges (RST, GOAWAY, truncated)

Options

  • :port - Filter to specific TCP port (default: nil, all ports)
  • :decode_content - When true (default), automatically decodes request/response bodies based on Content-Type header. Multipart bodies are recursively decoded, JSON is parsed, and text is validated as UTF-8. When false, bodies remain as raw binaries and decoded_body is nil.
  • :hosts_map - Map of IP address strings to hostname strings for endpoint resolution.
  • :decoders - List of custom decoder specs (see PcapFileEx.Flows.Decoder)
  • :keep_binary - When true, preserve original binary in multipart parts' body_binary field when custom decoders are invoked (default: false)

Example

{:ok, complete, incomplete} = PcapFileEx.HTTP2.analyze("capture.pcap")

Enum.each(complete, fn ex ->
  IO.puts("#{ex.request.method} #{ex.request.path} -> #{ex.response.status}")
end)

Enum.each(incomplete, fn ex ->
  IO.puts("Incomplete: #{PcapFileEx.HTTP2.IncompleteExchange.to_string(ex)}")
end)

# With hosts mapping
hosts = %{"192.168.1.1" => "client", "10.0.0.1" => "server"}
{:ok, complete, _incomplete} = PcapFileEx.HTTP2.analyze("capture.pcap", hosts_map: hosts)
%{client: client, server: server} = hd(complete)
IO.puts("Request from #{client} to #{server}")

analyze_segments(segments, opts \\ [])

Analyzes directional TCP segments directly.

Use this when you already have TCP-reassembled segments with direction information, skipping the PCAP parsing step.

Options

  • :decode_content - When true (default), automatically decodes request/response bodies based on Content-Type header. When false, bodies remain as raw binaries.
  • :hosts_map - Map of IP address strings to hostname strings for endpoint resolution.

Example

segments = [
  %{flow_key: {client, server}, direction: :a_to_b, data: preface_bytes, timestamp: ts1},
  %{flow_key: {client, server}, direction: :a_to_b, data: settings_frame, timestamp: ts2},
  ...
]

{:ok, complete, incomplete} = PcapFileEx.HTTP2.analyze_segments(segments)

# With hosts mapping
hosts = %{"192.168.1.1" => "client"}
{:ok, complete, _incomplete} = PcapFileEx.HTTP2.analyze_segments(segments, hosts_map: hosts)

connection_preface()

@spec connection_preface() :: binary()

Returns the HTTP/2 connection preface string.

http2?(arg1)

@spec http2?(binary()) :: boolean()

Check if binary data starts with HTTP/2 connection preface.

The connection preface is "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n" (24 bytes).