Skip to content

jsonrpc: simple_http sets Host header to resolved IP instead of URL hostname #577

@lorenzolfm

Description

@lorenzolfm

Description

The simple_http transport in the jsonrpc crate constructs the HTTP Host header from the resolved socket address (<IP>:<port>) instead of the URL's authority component. This violates RFC 9112 §3.2:

A client MUST send a Host header field [...] in all HTTP/1.1 request messages. If the target URI includes an authority component, then a client MUST send a field value for Host that is identical to that authority component, excluding any userinfo subcomponent and its "@" delimiter [...].

Impact: any consumer of Client::simple_http cannot reach a server that sits behind a host-routed reverse proxy. The proxy receives Host: <ip>:<port>, which doesn't match any vhost rule, and serves its default backend (typically a 404).

Reproduce

# Terminal 1: tiny listener that prints what it receives
python3 -c '
import socket
s = socket.socket(socket.AF_INET6); s.bind(("::", 8080)); s.listen()
c, _ = s.accept()
print(c.recv(4096).decode(errors="replace"))
c.send(b"HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n")
'
# Terminal 2: minimal client
cargo new --quiet /tmp/host-bug && cd /tmp/host-bug
echo 'jsonrpc = "0.19"' >> Cargo.toml
echo 'fn main() {
    let c = jsonrpc::Client::simple_http("http://localhost:8080/", None, None).unwrap();
    let _ = c.send_request(c.build_request("ping", None));
}' > src/main.rs
cargo run --quiet

Terminal 1 prints the actual outgoing request:

POST / HTTP/1.1
host: [::1]:8080                          <-- bug: resolved IP, not "localhost:8080"
Content-Type: application/json
Content-Length: 54

{"method":"ping","params":null,"id":1,"jsonrpc":"2.0"}

The host: value is [::1]:8080 (the address localhost resolved to), but RFC 9110 §7.2 requires it to be localhost:8080 (the host from the target URI). The bug only manifests when the URL hostname differs from its DNS resolution, exactly the case for any deployment behind a host-routed reverse proxy. (Switch to http://127.0.0.1:8080/ and the symptom hides because the URL hostname is the resolved IP.)

Expected behavior

The on-the-wire Host header should be the URL's authority component verbatim (with userinfo user:pass@ stripped, IPv6 brackets preserved per RFC 7230 §2.7.1, and explicit/omitted ports following the URL):

POST / HTTP/1.1
host: localhost:8080
...

Root cause

jsonrpc/src/http/simple_http.rs, three connected lines:

  1. Line 282check_url() calls to_socket_addrs() on the URL's authority and returns only (SocketAddr, path). The hostname string exists transiently and is dropped after resolution.
  2. Lines 40-51SimpleHttpTransport has no field for the URL hostname; only addr: SocketAddr and path: String.
  3. Lines 152-153 — the request serializer writes self.addr.to_string() (resolved <IP>:<port>) to the host: header:
    request_bytes.write_all(b"host: ")?;
    request_bytes.write_all(self.addr.to_string().as_bytes())?;

The struct can't carry the hostname, so the parser has nowhere to put it, and the serializer has nothing but the IP to use.

Environment

  • jsonrpc 0.19.0 from crates.io (current code on master is identical at the affected lines).
  • Reproduces on Linux (also on macOS — same simple_http.rs codepath).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions