Max keep alive not respected?

Hello all,

I have a question related to the max-keep-alive we have set that is not respected.

We have the setting as this:

<!-- Disallow keep-alive of more than 60s because load balancer drops connections after 90s (should be 1.5x keep-alive) -->
        <keep-alive>
            <max-keep-alive>60</max-keep-alive>
            <allow-unlimited>false</allow-unlimited>
        </keep-alive>

However, when I run this test:

@Test
void tooLongKeepAliveWillNotWork() {
	String clientId = randomAlphabetic(10);

	when(authenticationProxyService.isAuthenticated(eq(clientId), eq("test-password")))
		.thenAnswer(i -> AuthenticatedResponse.authenticated("test"));

	{
		Mqtt5BlockingClient client = Mqtt5Client.builder()
			.identifier(clientId)
			.serverPort(hivemqProperties.getPortNumber())
			.simpleAuth(new MqttSimpleAuth(MqttUtf8StringImpl.of("test"),
				ByteBuffer.wrap("test-password".getBytes(StandardCharsets.UTF_8))))
			.buildBlocking();

		Mqtt5ConnAck send = client.connectWith().keepAlive(5000).send();
		OptionalInt serverKeepAlive = send.getServerKeepAlive();
		assertThat(serverKeepAlive.orElseThrow(), is(equalTo(60)));

		int keepAlive = client.getConfig().getConnectionConfig().orElseThrow().getKeepAlive();
		assertThat(keepAlive, is(equalTo(60))); // works
	}
	{
		Mqtt3BlockingClient client = Mqtt3Client.builder()
			.identifier(clientId)
			.serverPort(hivemqProperties.getPortNumber())
			.simpleAuth(Mqtt3SimpleAuth.builder()
				.username(MqttUtf8StringImpl.of("test"))
				.password(ByteBuffer.wrap("test-password".getBytes(StandardCharsets.UTF_8)))
				.build())
			.buildBlocking();

		Mqtt3ConnAck send = client.connectWith().keepAlive(5000).send();

		Mqtt3ConnAckReturnCode returnCode = send.getReturnCode();
		assertThat(returnCode, is(not(equalTo(Mqtt3ConnAckReturnCode.SUCCESS))));

		int keepAlive = client.getConfig().getConnectionConfig().orElseThrow().getKeepAlive();
		assertThat(keepAlive, is(equalTo(60))); // also doesn't pass
	}
}

It does not pass the bottom 2 assertions. This seems very weird to me as the setting clearly says:

The max-keep-alive value refers to the maximum value of the keepAlive field in the CONNECT packet of the client that will be accepted by the broker. If a client sends a CONNECT with a keepAlive value that exceeds this value, the broker will not accept the connection.

Am I missing something or is this a bug? When using Mqtt V5, it does seem to work, but in the ConnAck tells you to what the server keep-alive is (which also doesn’t match the text close to the config). Is this just inherent to Mqtt V3 that you cannot have this? And how could I simulate such a thing? Do a check in the authenticator and don’t authenticate it if its V3 and the keep-alive is too long?

I also did some tests regarding the maximum clientId size, and that one seems to work.

Can somebody shed some light on this?

Many thanks,

Bram

FYI: I’m using hiveMq 2023.9 Community Edition

EDIT: If I look at this code (in the ConnectHandler):

if (ProtocolVersion.MQTTv5.equals(msg.getProtocolVersion()) &&
                ((msg.getKeepAlive() == 0 && !allowZeroKeepAlive) || (msg.getKeepAlive() > serverKeepAliveMaximum))) {
            if (log.isTraceEnabled()) {
                log.trace("Client {} used keepAlive {} which is invalid, using server maximum of {}",
                        msg.getClientIdentifier(),
                        msg.getKeepAlive(),
                        serverKeepAliveMaximum);
            }
            keepAlive = serverKeepAliveMaximum;
        } else {
            keepAlive = msg.getKeepAlive();
        }

It looks like this is done on only Mqtt V5, is this an oversight? Or just a consequence of Mqtt 3, I’m not super familiar with the spec and implementation details.

Dear @basimons,

I hope this message finds you well. Thank you for reaching out to us.

I wanted to provide some additional information regarding your inquiry. The Maximum Keep Alive feature is specific to MQTT 5. It’s important to note that MQTT 3 clients are unable to interpret the max-keep-alive value sent by the broker in the CONNACK packet. Consequently, these clients will retain their original keep-alive value.

In your case, the broker will accept the keepAlive value provided by the MQTT 3 client in their CONNECT packet.

For further details, please refer to our documentation: HiveMQ Configuration :: HiveMQ Documentation.

If you have any more questions or if there’s anything else I can assist you with, please feel free to let me know. We appreciate your understanding and value your engagement with our platform.

Best regards,
Dasha from HivemQ Team

Hmm interesting. Thanks for your reply.

Just to check, there is no reason to not do a validation on this in the authentication right? Some (V3) clients might be confused, but the thing is that our load balancer does not allow connections on which nothing is sent for 90s, which would be even more confusing for the clients to be disconnected?

Also I think the explanation next to the configuration is confusing/incorrect.

The max-keep-alive value refers to the maximum value of the keepAlive field in the CONNECT packet of the client that will be accepted by the broker. If a client sends a CONNECT with a keepAlive value that exceeds this value, the broker will not accept the connection.

As it does accept the connection, and only forces the client to a lower one on Mqtt V5.

Thanks again for your help

Hi @basimons,

Thank you for providing feedback on our documentation; we truly appreciate your insights. Rest assured, we will share your comments with our dedicated documentation team to enhance and improve our materials.

I would like to seek further clarification regarding the authentication query you raised. Could you please elaborate on how authentication is connected to the keep-alive mechanism? Specifically, are you interested in overriding the keep-alive duration from MQTT 3 CONNECT packets and enforcing it to a fixed value of 60 seconds?

Your additional details will help us better understand your concerns and provide you with a more accurate and tailored response.

Best regards,
Dasha from HiveMQ Team

What I meant to say was that I have an SimpleAuthenticator implementation (in an extension). Where you could do something like this:

 @Override
    public void onConnect(@NotNull SimpleAuthInput simpleAuthInput, @NotNull SimpleAuthOutput simpleAuthOutput) {
        ConnectPacket connectPacket = simpleAuthInput.getConnectPacket();

        if (connectPacket.getMqttVersion() != MqttVersion.V_5 && (connectPacket.getKeepAlive() == 0 || connectPacket.getKeepAlive() > 60)) {
            simpleAuthOutput.failAuthentication(ConnackReasonCode.IMPLEMENTATION_SPECIFIC_ERROR);
        }
}

This would basically prevent any clients from (successfully) connecting to our broker with the settings that we don’t allow right? Would something like this be advisable, or are there different/better ways to do it? I know you have a connect interceptor, would that be a better place for this?

Thanks for thinking along :slight_smile:

Bram

Hi @basimons

Would you mind providing the link for the quote? Your assistance is greatly appreciated.

Thank you in advance,
Dasha from HiveMQ Team

Of course, you can find it here: MQTT Specific Configuration · hivemq/hivemq-community-edition Wiki · GitHub

Thank you @basimons for bringing this to our attention. We truly appreciate your diligence in reporting the issue with the hivemq-ce documentation. Our documentation team is now aware and will promptly make the necessary updates. Your commitment to improving our resources is invaluable.

1 Like