Expose the remote TCP port of requests and websocket connections - #1245
Merged
Merged
Conversation
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
approved these changes
Sep 16, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 thanlogging, 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 theonly workarounds are patching Crow or keeping a private fork.
What this adds
uint16_t SocketAdaptor::remote_port() constinclude/crow/socket_adaptors.huint16_t UnixSocketAdaptor::remote_port() constinclude/crow/socket_adaptors.huint16_t SSLAdaptor::remote_port() constinclude/crow/socket_adaptors.huint16_t request::remote_portinclude/crow/http_request.h, filled inhttp_connection.hnext toremote_ip_addressuint16_t websocket::connection::get_remote_port()include/crow/websocket.hIt mirrors exactly what already exists for the address (
address()/remote_ip_address/get_remote_ip()), so there is nothing new to learn.Behaviour
error_codeoverload ofremote_endpoint(), so the newaccessors cannot throw: a socket that is already closed by the time a handler asks
reports
0instead of raising. (address()uses the throwing overload; this PR doesnot change that, but the same treatment could be applied in a follow-up.)
UnixSocketAdaptor::remote_port()returns0andrequests served over
local_socket_path()report0. This matchesaddress(), whichreturns an empty string there.
remote_portis set per request, at the same place asremote_ip_address, so it stayscorrect across keep-alive requests.
getpeername()already done for the address, andnothing changes when the accessors are not used.
Compatibility
get_remote_port()is added as a pure virtual to the abstractwebsocket::connectioninterface, for consistency with
get_remote_ip()andget_subprotocol(). The onlyimplementation in the tree is
websocket::Connection, but code that derives fromwebsocket::connection(a mock in a test suite, typically) would have to implement thenew method. Happy to give it a default
{ return 0; }implementation instead if youprefer to keep that interface strictly additive.
The new
requestmember is default-initialised to0and appended afterremote_ip_address; aggregate initialisation ofrequestis not possible anyway (it hasuser-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 overSocketAdaptor. Usesport(0)andapp.port(), so it does not add a hard-coded port to the suite.tests/unit_tests/test_websocket.cpp,TEST_CASE("websocket_remote_port"): thewebsocket connection reports the client's port in
onopen.tests/ssl/ssltest.cpp: two checks added to the existing SSL test, coveringSSLAdaptorwithout a new target or a second certificate.tests/unittest.cpp,TEST_CASE("unix_socket"): one check that the request reports0over a Unix domain socket.HttpClientintests/unittest.cppgained alocal_port()accessor for this.Full suite on macOS (AppleClang, standalone Asio,
CROW_ENABLE_SSL=ON): 131 test cases,1000 assertions, all passing;
ssltestpassing.Docs
docs/guides/websockets.mdgains a short "Remote endpoint" section documentingget_remote_ip()/get_remote_port()and the0cases, taggedmasterlike the otherunreleased features.