Skip to content

Expose the remote TCP port of requests and websocket connections - #1245

Merged
gittiver merged 1 commit into
CrowCpp:masterfrom
L-Acoustics:feature/expose-remote-port
Sep 16, 2026
Merged

gittiver merged 1 commit into
CrowCpp:masterfrom
L-Acoustics:feature/expose-remote-port

Conversation

@christophe-calmejane

Copy link
Copy Markdown
Contributor

Motivation

Crow tells a handler which address a request came from (request::remote_ip_address,
websocket::connection::get_remote_ip()), but never which port. For anything else than
logging, the address alone is not an identity: on a local connection every client is
127.0.0.1, and the source port is the only thing that tells them apart.

The port is right there in the adaptor's endpoint, but nothing exposes it, and
Connection::adaptor_ is private, so an application cannot reach it either. Today the
only workarounds are patching Crow or keeping a private fork.

What this adds

API Where
uint16_t SocketAdaptor::remote_port() const include/crow/socket_adaptors.h
uint16_t UnixSocketAdaptor::remote_port() const include/crow/socket_adaptors.h
uint16_t SSLAdaptor::remote_port() const include/crow/socket_adaptors.h
uint16_t request::remote_port include/crow/http_request.h, filled in http_connection.h next to remote_ip_address
uint16_t websocket::connection::get_remote_port() include/crow/websocket.h

It mirrors exactly what already exists for the address (address() /
remote_ip_address / get_remote_ip()), so there is nothing new to learn.

CROW_ROUTE(app, "/whoami")([](const crow::request& req) {
    return req.remote_ip_address + ":" + std::to_string(req.remote_port);
});

CROW_WEBSOCKET_ROUTE(app, "/ws")
  .onopen([](crow::websocket::connection& conn) {
      CROW_LOG_INFO << "new connection from " << conn.get_remote_ip() << ':' << conn.get_remote_port();
  });

Behaviour

  • The port is read through the error_code overload of remote_endpoint(), so the new
    accessors cannot throw: a socket that is already closed by the time a handler asks
    reports 0 instead of raising. (address() uses the throwing overload; this PR does
    not change that, but the same treatment could be applied in a follow-up.)
  • A Unix domain socket has no port, so UnixSocketAdaptor::remote_port() returns 0 and
    requests served over local_socket_path() report 0. This matches address(), which
    returns an empty string there.
  • remote_port is set per request, at the same place as remote_ip_address, so it stays
    correct across keep-alive requests.
  • No allocation, no syscall beyond the getpeername() already done for the address, and
    nothing changes when the accessors are not used.

Compatibility

get_remote_port() is added as a pure virtual to the abstract websocket::connection
interface, for consistency with get_remote_ip() and get_subprotocol(). The only
implementation in the tree is websocket::Connection, but code that derives from
websocket::connection (a mock in a test suite, typically) would have to implement the
new method. Happy to give it a default { return 0; } implementation instead if you
prefer to keep that interface strictly additive.

The new request member is default-initialised to 0 and appended after
remote_ip_address; aggregate initialisation of request is not possible anyway (it has
user-provided constructors), and the existing constructor is unchanged.

Tests

The three adaptors are covered by checking, in each case, that the port the server
reports is the local port of the client socket (an ephemeral port, so the assertion is
meaningful rather than a constant comparison):

  • tests/unittest.cpp, TEST_CASE("remote_port"): HTTP over SocketAdaptor. Uses
    port(0) and app.port(), so it does not add a hard-coded port to the suite.
  • tests/unit_tests/test_websocket.cpp, TEST_CASE("websocket_remote_port"): the
    websocket connection reports the client's port in onopen.
  • tests/ssl/ssltest.cpp: two checks added to the existing SSL test, covering
    SSLAdaptor without a new target or a second certificate.
  • tests/unittest.cpp, TEST_CASE("unix_socket"): one check that the request reports
    0 over a Unix domain socket.

HttpClient in tests/unittest.cpp gained a local_port() accessor for this.

Full suite on macOS (AppleClang, standalone Asio, CROW_ENABLE_SSL=ON): 131 test cases,
1000 assertions, all passing; ssltest passing.

Docs

docs/guides/websockets.md gains a short "Remote endpoint" section documenting
get_remote_ip() / get_remote_port() and the 0 cases, tagged master like the other
unreleased features.

Add `remote_port()` to the socket adaptors, `request::remote_port` and
`websocket::connection::get_remote_port()`, alongside the existing
`address()` / `remote_ip_address` / `get_remote_ip()`.
@gittiver
gittiver merged commit da3f8e2 into CrowCpp:master Sep 16, 2026
20 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants