Skip to content

Conversation

@pniedzielski
Copy link
Collaborator

@pniedzielski pniedzielski commented Nov 24, 2025

The BlazingMQ project provides three SDKs for writing applications that send and receive messages from a BlazingMQ cluster:

  • the C++03 libbmq library, housed in this repository;
  • the Java com.bloomberg.bmq package; and
  • the Python blazingmq package.

Of these, libbmq and com.bloomberg.bmq are separate, independent implementations of the BlazingMQ protocol, and blazingmq is a Cython wrapper around libbmq. Future SDKs, produced either by the BlazingMQ project or by the community, may take either road.

It is useful telemetry to know which SDK language and version a client is connecting to a broker with, and the ClientIdentity generated type does partially provide this information, in the form of an sdkLanguage enumeration and an sdkVersion integer. These two fields are useful for programmatic checks on protocol compatibility, but for telemetry, we have found them to be insufficient.

First, any SDK that wraps libbmq, like the Python blazingmq package, will advertise itself as an E_CPP client. libbmq provides no way for a client to override the SDK language and version it negotiates with, so blazingmq has no way to advertise itself as anything different. While it is useful to know what version of the underlying libbmq a Python client is connecting with, this leaves it difficult for us to distinguish between clients that are connecting directly using libbmq, or Python clients that are connecting using blazingmq. This problem will be true for any future SDK that wraps libbmq.

Second, any new SDK that does not wrap libbmq will either need to choose from one of the existing sdkLanguages (or use E_UNKNOWN), or request a protocol change from BlazingMQ to add their language version. The same problem as above happens with the first option, while the second option is simply unscalable.

This pull request introduces a new optional field called userAgent to the ClientIdentity type that is sent during session negotiation. At the protocol level, this field is a free-form string, with which client libraries can identify themselves, and the broker does nothing more than log it along with the session negotiation message. This allows clients that do not wrap libbmq to identify themselves to the broker.

Importantly, this field MUST NOT be used for anything more than telemetry, because clients do not need to specify it, and malicious client libraries can easily spoof their userAgent to be the same as other client libraries.

The important changes in this PR:

  1. Add a user-readable version string to *scm_version components, which gives us a nicer way of adding version information to the user agent.
  2. Protocol change in bmqp to add optional user agent field.
  3. Add a new session option, userAgentPrefix, which is prefixed to the user agent libbmq generates. This lets wrapping libraries add their own version information, while preserving the version of libbmq they're wrapping.
  4. Teach libbmq, bmqbrkr, and bmqtool about this field, so they can identify themselves.

Please see commit messages for additional details.

@pniedzielski pniedzielski force-pushed the feat/useragent branch 7 times, most recently from 98b91bf to bac1ce7 Compare November 24, 2025 23:18
@pniedzielski
Copy link
Collaborator Author

pniedzielski commented Nov 24, 2025

Example log messages from bmqtool with this patch:

> start
24NOV2025_23:16:09.438 (8708547072) INFO m_bmqtool_interactive.cpp:176 --> Starting session: [ async = false ]
24NOV2025_23:16:09.452 (8708547072) INFO bmqp_messageguidgenerator.cpp:156 GUID generator initialized [IPAddress: 127.0.0.1, hostname: 'L3C070VGTW', used IPAddress true, nanoSecondsFromEpoch: 1764026169438642000, pid: 45897, sessionId: 1, clientID: 3238AB026780]
24NOV2025_23:16:09.453 (8708547072) INFO bmqimp_eventqueue.cpp:314 [#1] Creating event queue with [lowWatermark: 1,000, highWatermark: 10,000, initialCapacity: 10,000, using 1 threads]
24NOV2025_23:16:09.454 (8708547072) INFO bmqimp_eventqueue.cpp:443 [#1] Starting EventQueue ThreadPool [numThreads: 1]
24NOV2025_23:16:09.454 (6095958016) INFO bmqimp_brokersession.cpp:3073 [#1] FSM thread started [id: 6095958016]
24NOV2025_23:16:09.454 (6096531456) INFO bmqimp_eventqueue.cpp:252 [#1] EventHandler thread started [id: 6096531456]
24NOV2025_23:16:09.454 (8708547072) INFO bmqimp_application.cpp:645 [#1] Creating Application [bmq: BLP_LIB_BMQ_99.99.99], options:
    [
        brokerUri = "tcp://localhost:30114"
        processNameOverride = ""
        numProcessingThreads = 1
        blobBufferSize = 4096
        channelHighWatermark = 134217728
        statsDumpInterval = 300
        connectTimeout = 300
        disconnectTimeout = 300
        openQueueTimeout = 300
        configureQueueTimeout = 300
        closeQueueTimeout = 300
        eventQueueLowWatermark = 1000
        eventQueueHighWatermark = 10000
        hasHostHealthMonitor = false
        hasDistributedTracing = false
        userAgentPrefix = "bmqtool"
    ]

24NOV2025_23:16:09.454 (8708547072) INFO bmqimp_application.cpp:726 [#1] ::: START (SYNC) << [state: STOPPED] :::
24NOV2025_23:16:09.454 (6095958016) INFO bmqimp_brokersession.cpp:371 [#1] ::: STATE TRANSITION: STOPPED -> [START] -> STARTING :::
24NOV2025_23:16:09.454 (6095958016) INFO bmqimp_application.cpp:522 [#1] State transitioning from STOPPED to STARTING on START
24NOV2025_23:16:09.457 (6095958016) INFO bmqio_reconnectingchannelfactory.cpp:264 Resolved endpoint 'localhost:30114' to: [localhost:30114, localhost:30114]
24NOV2025_23:16:09.460 (6101348352) INFO bmqimp_negotiatedchannelfactory.cpp:149 Sending negotiation message to '127.0.0.1~localhost:30114': [ clientIdentity = [ protocolVersion = 1 sdkVersion = 999999 clientType = E_TCPCLIENT processName = "blazingmq/build/src/applications/bmqtool/bmqtool.tsk" pid = 45897 sessionId = 1 hostName = "" features = "PROTOCOL_ENCODING:BER,JSON;MPS:MESSAGE_PROPERTIES_EX" clusterName = "" clusterNodeId = -1 sdkLanguage = E_CPP guidInfo = [ clientId = "3238AB026780" nanoSecondsFromEpoch = 1764026169438642000 ] userAgent = "bmqtool libbmq:99.99.99" ] ]
24NOV2025_23:16:09.461 (6101348352) INFO bmqimp_negotiatedchannelfactory.cpp:328 Negotiation with broker was successful: [ brokerResponse = [ result = [ category = E_SUCCESS code = 0 message = "" ] protocolVersion = 1 brokerVersion = 999999 isDeprecatedSdk = false brokerIdentity = [ protocolVersion = 1 sdkVersion = 999999 clientType = E_TCPBROKER processName = "blazingmq/build/src/applications/bmqbrkr/bmqbrkr.tsk" pid = 45852 sessionId = 1 hostName = "localhost" features = "PROTOCOL_ENCODING:BER,JSON;HA:GRACEFUL_SHUTDOWN,GRACEFUL_SHUTDOWN_V2,BROADCAST_TO_PROXIES;MPS:MESSAGE_PROPERTIES_EX;SUBSCRIPTIONS:CONFIGURE_STREAM" clusterName = "" clusterNodeId = -1 sdkLanguage = E_CPP guidInfo = [ clientId = "" nanoSecondsFromEpoch = 0 ] userAgent = "bmqbrkr:99.99.99" ] heartbeatIntervalMs = 3000 maxMissedHeartbeats = 10 ] ]
24NOV2025_23:16:09.461 (6101348352) INFO bmqimp_application.cpp:240 [#1] Session with '127.0.0.1~localhost:30114' is now UP
24NOV2025_23:16:09.461 (6101348352) INFO bmqimp_brokersession.cpp:5765 [#1] Channel is CREATED [host: 127.0.0.1~localhost:30114]
24NOV2025_23:16:09.461 (6095958016) INFO bmqimp_brokersession.cpp:5056 [#1] Setting channel [host: 127.0.0.1~localhost:30114]
24NOV2025_23:16:09.461 (6095958016) INFO bmqimp_brokersession.cpp:371 [#1] ::: STATE TRANSITION: STARTING -> [CHANNEL_UP] -> STARTED :::
24NOV2025_23:16:09.461 (6095958016) INFO bmqimp_application.cpp:522 [#1] State transitioning from STARTING to STARTED on CHANNEL_UP
24NOV2025_23:16:09.461 (8708547072) INFO m_bmqtool_interactive.cpp:187 <-- session.start(5.0) => SUCCESS (0)
> 24NOV2025_23:16:09.461 (6095958016) INFO bmqimp_brokersession.cpp:941 [#1] Start took: 6.71 ms (6706708 nanoseconds)
24NOV2025_23:16:09.461 (6096531456) INFO m_bmqtool_application.cpp:657 ==> EVENT received: [ type = "SESSION" sessionEventType = CONNECTED statusCode = 0 correlationId = [ "* unset *" ] ]

Note userAgent = "bmqtool libbmq:99.99.99" in the request and userAgent = "bmqbrkr:99.99.99" in the response.

@pniedzielski
Copy link
Collaborator Author

And on the broker side:

24NOV2025_23:16:09.460 (6115651584) INFO mqba_sessionnegotiator.cpp:364 Handle negotiation message received from '127.0.0.1~localhost:63164': [ protocolVersion = 1 sdkVersion = 999999 clientType = E_TCPCLIENT processName = "blazingmq/build/src/applications/bmqtool/bmqtool.tsk" pid = 45897 sessionId = 1 hostName = "" features = "PROTOCOL_ENCODING:BER,JSON;MPS:MESSAGE_PROPERTIES_EX" clusterName = "" clusterNodeId = -1 sdkLanguage = E_CPP guidInfo = [ clientId = "3238AB026780" nanoSecondsFromEpoch = 1764026169438642000 ] userAgent = "bmqtool libbmq:99.99.99" ]
24NOV2025_23:16:09.460 (6115651584) INFO mqba_clientsession.cpp:2458 bmqtool.tsk:45897: created [dispatcherProcessor: 0, identity: [ protocolVersion = 1 sdkVersion = 999999 clientType = E_TCPCLIENT processName = "blazingmq/build/src/applications/bmqtool/bmqtool.tsk" pid = 45897 sessionId = 1 hostName = "127.0.0.1~localhost:63164" features = "PROTOCOL_ENCODING:BER,JSON;MPS:MESSAGE_PROPERTIES_EX" clusterName = "" clusterNodeId = -1 sdkLanguage = E_CPP guidInfo = [ clientId = "3238AB026780" nanoSecondsFromEpoch = 1764026169438642000 ] userAgent = "bmqtool libbmq:99.99.99" ], ptr: 0x14d025e10, queueHandleRequesterId: 1].
24NOV2025_23:16:09.461 (6115651584) INFO mqbnet_tcpsessionfactory.cpp:534 TCPSessionFactory 'TCPInterface' successfully negotiated a session [session: 'bmqtool.tsk:45897', channel: '127.0.0.1~localhost:63164#0x6000003e8728', maxMissedHeartbeat: 10]
24NOV2025_23:16:09.461 (6115651584) INFO mqbnet_transportmanager.cpp:197 Client session is up [channel: '127.0.0.1~localhost:63164#0x6000003e8728']
24NOV2025_23:16:09.461 (6115651584) INFO mqbnet_tcpsessionfactory.cpp:921 Open session 'bmqtool.tsk:45897' took: 1.05 ms (1050333 nanoseconds)

@pniedzielski pniedzielski force-pushed the feat/useragent branch 2 times, most recently from 54f017c to f65c095 Compare November 25, 2025 17:29
@pniedzielski pniedzielski marked this pull request as ready for review November 25, 2025 17:32
@pniedzielski pniedzielski requested a review from a team as a code owner November 25, 2025 17:32
pniedzielski and others added 4 commits December 29, 2025 12:29
The BlazingMQ project provides three SDKs for writing applications
that send and receive messages from a BlazingMQ cluster:

  - the C++03 `libbmq` library, housed in this repository;
  - the Java `com.bloomberg.bmq` package; and
  - the Python `blazingmq` package.

Of these, `libbmq` and `com.bloomberg.bmq` are separate, independent
implementations of the BlazingMQ protocol, and `blazingmq` is a Cython
wrapper around `libbmq`.  Future SDKs, produced either by the
BlazingMQ project or by the community, may take either road.

It is useful telemetry to know which SDK language and version a client
is connecting to a broker with, and the `ClientIdentity` generated
type does partially provide this information, in the form of an
`sdkLanguage` enumeration and an `sdkVersion` integer.  These two
fields are useful for programmatic checks on protocol compatibility,
but for telemetry, we have found them to be insufficient.

First, any SDK that wraps `libbmq`, like the Python `blazingmq`
package, will advertise itself as an `E_CPP` client.  `libbmq`
provides no way for a client to override the SDK language and version
it negotiates with, so `blazingmq` has no way to advertise itself as
anything different.  While it is useful to know what version of the
underlying `libbmq` a Python client is connecting with, this leaves it
difficult for us to distinguish between clients that are connecting
directly using `libbmq`, or Python clients that are connecting using
`blazingmq`.  This problem will be true for any future SDK that wraps
`libbmq`.

Second, any new SDK that does not wrap `libbmq` will either need to
choose from one of the existing `sdkLanguages` (or use `E_UNKNOWN`),
or request a protocol change from BlazingMQ to add their language
version.  The same problem as above happens with the first option,
while the second option is simply unscalable.

This patch introduces a new optional field called `userAgent` to the
`ClientIdentity` type that is sent during session negotiation.  At the
protocol level, this field is a free-form string, with which client
libraries can identify themselves, and the broker does nothing more
than log it along with the session negotiation message.  This allows
clients that do not wrap `libbmq` to identify themselves to the
broker.

This patch also teaches `libbmq` and `bmqbrkr` about this field, so
they can send identifying version strings as their `userAgent`.  At
the moment, `libbmq` does provide any way to modify this field, so
libraries that wrap `libbmq`, like the Python `blazingmq`, still
suffer from the first problem noted above.  A future patch will expose
a way to modify the user agent (likely in restricted ways).

Importantly, this field MUST NOT be used for anything more than
telemetry, because clients do not need to specify it, and malicious
client libraries can easily spoof their `userAgent` to be the same as
other client libraries.

Signed-off-by: Patrick M. Niedzielski <[email protected]>
This patch adds a new field `s_versionDotString` to the `Version`
structs in `*scm_version` components, which stores a human-readable
version string.  This differs from the `version()` function, which has
information identifying the package group or application prefixed to
it.  In principle, `s_versionDotString` could contain other
information, like prerelease numbers if we adopt those, without
breaking an compatibility for existing callers of `version()`.

Signed-off-by: Patrick M. Niedzielski <[email protected]>
This patch changes user agents constructed by `libbmq` and the broker
from forms like `libbmq:BLP_LIB_BMQ_99.99.99` to `libbmq:99.99.99‘.

Signed-off-by: Patrick M. Niedzielski <[email protected]>
The BlazingMQ protocol now contains support for a user agent field
sent during session negotiation.  This allows client libraries to
identify themselves for the purposes of broker telementry.  However,
client libraries that wrap `libbmq`, like the open source `blazingmq`
Python package, are still forced to identify themselves as `libbmq`.

This patch exposes a `userAgentPrefix` option in
`bmqt::SessionOptions`, which is prefixed to the user agent that
`libbmq` constructs.  This allows client libraries that are built with
`libbmq` to add their own version information that will be passed
along during session negotiation.  Although we encourage users not to
set this, by making this option a prefix, we prevent users from
erasing `libbmq`’s version string, limiting the damage they can
accidentally do to telemetry.  It is expected that libraries wrapping
`libbmq` can do something similar, in case further libraries wrap
them.

The setting exposed in this patch has further restrictions that are
not present in the protocol, to make sure that clients built on top of
`libbmq` do not accidentally write large/unprintable strings to the
broker log:

  1. The user agent prefix must be less than 128 characters long.
     This can be increased safely in the future if necessary, but it
     is generous as is.

  2. The user agent prefix must contain only printable ASCII
     characters.

Example usage:

```
bmqt::SessionOptions options;
// ...
options.setUserAgentPrefix("myWrapperLib:1.2.3~unstable");
bmqa::Session session(options);
session.start();
    // userAgent sent during session negotiation:
    //     myWrapperLib:1.2.3~unstable libbmq:99.99.99
```

Signed-off-by: Patrick M. Niedzielski <[email protected]>
@pniedzielski pniedzielski added enhancement New feature or request A-Broker Area: C++ Broker A-Client Area: C++ SDK labels Dec 29, 2025
bmqtool is not a wrapper library, this is a cheap way to test that the
user agent string sent to the broker contains both the user-specified
`userAgentPrefix` string (which in this case will be `bmqtool`) and
the `libbmq`-provided user agent string.

Signed-off-by: Patrick M. Niedzielski <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-Broker Area: C++ Broker A-Client Area: C++ SDK enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant