Skip to content

Commit 7df9a3a

Browse files
committed
[#2955] Fix MQTT5 Connect Reason Codes
MQTT5 defines new reason codes to be included in CONNACK packets when connection establishment fails. The abstract adapter base class has been changed accordingly. Also added integration tests based on HiveMQ client for testing connection establishment.
1 parent 7dfcc6a commit 7df9a3a

File tree

8 files changed

+1263
-35
lines changed

8 files changed

+1263
-35
lines changed

adapters/mqtt-base/src/main/java/org/eclipse/hono/adapter/mqtt/AbstractVertxBasedMqttProtocolAdapter.java

+51-16
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*******************************************************************************
2-
* Copyright (c) 2016, 2023 Contributors to the Eclipse Foundation
2+
* Copyright (c) 2023 Contributors to the Eclipse Foundation
33
*
44
* See the NOTICE file(s) distributed with this work for additional
55
* information regarding copyright ownership.
@@ -41,6 +41,9 @@
4141
import org.eclipse.hono.adapter.AbstractProtocolAdapterBase;
4242
import org.eclipse.hono.adapter.AdapterConnectionsExceededException;
4343
import org.eclipse.hono.adapter.AuthorizationException;
44+
import org.eclipse.hono.adapter.ConnectionDurationExceededException;
45+
import org.eclipse.hono.adapter.DataVolumeExceededException;
46+
import org.eclipse.hono.adapter.TenantConnectionsExceededException;
4447
import org.eclipse.hono.adapter.auth.device.AuthHandler;
4548
import org.eclipse.hono.adapter.auth.device.ChainAuthHandler;
4649
import org.eclipse.hono.adapter.auth.device.CredentialsApiAuthProvider;
@@ -90,6 +93,7 @@
9093
import io.netty.handler.codec.mqtt.MqttConnectReturnCode;
9194
import io.netty.handler.codec.mqtt.MqttProperties;
9295
import io.netty.handler.codec.mqtt.MqttQoS;
96+
import io.netty.handler.codec.mqtt.MqttVersion;
9397
import io.opentracing.Span;
9498
import io.opentracing.SpanContext;
9599
import io.opentracing.log.Fields;
@@ -518,7 +522,8 @@ final void handleEndpointConnection(final MqttEndpoint endpoint) {
518522
log.debug("rejecting connection request from client [clientId: {}], cause:",
519523
endpoint.clientIdentifier(), t);
520524

521-
final MqttConnectReturnCode code = getConnectReturnCode(t);
525+
final boolean isPreMqtt5 = ((int) MqttVersion.MQTT_5.protocolLevel()) > endpoint.protocolVersion();
526+
final var code = isPreMqtt5 ? getMqtt3ConnackReturnCode(t) : getMqtt5ConnackReasonCode(t);
522527
rejectConnectionRequest(endpoint, code, span);
523528
TracingHelper.logError(span, t);
524529
}
@@ -1106,18 +1111,18 @@ final MqttDeviceEndpoint createMqttDeviceEndpoint(
11061111
return mqttDeviceEndpoint;
11071112
}
11081113

1109-
private static MqttConnectReturnCode getConnectReturnCode(final Throwable e) {
1110-
1111-
if (e instanceof MqttConnectionException) {
1112-
return ((MqttConnectionException) e).code();
1113-
} else if (e instanceof AuthorizationException) {
1114-
if (e instanceof AdapterConnectionsExceededException) {
1115-
return MqttConnectReturnCode.CONNECTION_REFUSED_SERVER_UNAVAILABLE;
1116-
} else {
1117-
return MqttConnectReturnCode.CONNECTION_REFUSED_NOT_AUTHORIZED;
1118-
}
1119-
} else if (e instanceof ServiceInvocationException) {
1120-
switch (((ServiceInvocationException) e).getErrorCode()) {
1114+
private static MqttConnectReturnCode getMqtt3ConnackReturnCode(final Throwable e) {
1115+
if (e instanceof MqttConnectionException connectionException) {
1116+
return connectionException.code();
1117+
}
1118+
if (e instanceof AdapterConnectionsExceededException) {
1119+
return MqttConnectReturnCode.CONNECTION_REFUSED_SERVER_UNAVAILABLE;
1120+
}
1121+
if (e instanceof AuthorizationException) {
1122+
return MqttConnectReturnCode.CONNECTION_REFUSED_NOT_AUTHORIZED;
1123+
}
1124+
if (e instanceof ServiceInvocationException exception) {
1125+
switch (exception.getErrorCode()) {
11211126
case HttpURLConnection.HTTP_UNAUTHORIZED:
11221127
case HttpURLConnection.HTTP_NOT_FOUND:
11231128
return MqttConnectReturnCode.CONNECTION_REFUSED_BAD_USER_NAME_OR_PASSWORD;
@@ -1126,9 +1131,39 @@ private static MqttConnectReturnCode getConnectReturnCode(final Throwable e) {
11261131
default:
11271132
return MqttConnectReturnCode.CONNECTION_REFUSED_NOT_AUTHORIZED;
11281133
}
1129-
} else {
1130-
return MqttConnectReturnCode.CONNECTION_REFUSED_NOT_AUTHORIZED;
11311134
}
1135+
1136+
return MqttConnectReturnCode.CONNECTION_REFUSED_NOT_AUTHORIZED;
1137+
}
1138+
1139+
private static MqttConnectReturnCode getMqtt5ConnackReasonCode(final Throwable e) {
1140+
1141+
if (e instanceof MqttConnectionException connectionException) {
1142+
return connectionException.code();
1143+
}
1144+
if (e instanceof AdapterConnectionsExceededException) {
1145+
return MqttConnectReturnCode.CONNECTION_REFUSED_USE_ANOTHER_SERVER;
1146+
}
1147+
if (e instanceof TenantConnectionsExceededException
1148+
|| e instanceof DataVolumeExceededException
1149+
|| e instanceof ConnectionDurationExceededException) {
1150+
return MqttConnectReturnCode.CONNECTION_REFUSED_QUOTA_EXCEEDED;
1151+
}
1152+
if (e instanceof AuthorizationException) {
1153+
return MqttConnectReturnCode.CONNECTION_REFUSED_NOT_AUTHORIZED_5;
1154+
}
1155+
if (e instanceof ServiceInvocationException exception) {
1156+
switch (exception.getErrorCode()) {
1157+
case HttpURLConnection.HTTP_UNAUTHORIZED:
1158+
case HttpURLConnection.HTTP_NOT_FOUND:
1159+
return MqttConnectReturnCode.CONNECTION_REFUSED_BAD_USERNAME_OR_PASSWORD;
1160+
case HttpURLConnection.HTTP_UNAVAILABLE:
1161+
return MqttConnectReturnCode.CONNECTION_REFUSED_SERVER_UNAVAILABLE_5;
1162+
default:
1163+
return MqttConnectReturnCode.CONNECTION_REFUSED_NOT_AUTHORIZED_5;
1164+
}
1165+
}
1166+
return MqttConnectReturnCode.CONNECTION_REFUSED_UNSPECIFIED_ERROR;
11321167
}
11331168

11341169
/**

bom/pom.xml

+6
Original file line numberDiff line numberDiff line change
@@ -595,6 +595,12 @@ quarkus.vertx.max-event-loop-execute-time=${max.event-loop.execute-time:20000}
595595
</dependency>
596596

597597
<!-- Testing -->
598+
<dependency>
599+
<groupId>com.hivemq</groupId>
600+
<artifactId>hivemq-mqtt-client</artifactId>
601+
<version>1.3.3</version>
602+
<scope>test</scope>
603+
</dependency>
598604
<dependency>
599605
<groupId>org.eclipse.hono</groupId>
600606
<artifactId>core-test-utils</artifactId>

site/documentation/content/user-guide/mqtt-adapter.md

+29-15
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ consumers and for receiving commands from applications and sending back response
99

1010
The MQTT adapter is **not** a general purpose MQTT broker. In particular the adapter
1111

12-
* supports MQTT 3.1.1 only.
12+
* supports clients connecting using MQTT 3.1.1 or 5.0 only.
1313
* does not maintain session state for clients and thus always sets the *session present* flag in its CONNACK packet
1414
to `0`, regardless of the value of the *clean session* flag provided in a client's CONNECT packet.
1515
* ignores any *Will* included in a client's CONNECT packet.
@@ -23,7 +23,7 @@ The MQTT adapter is **not** a general purpose MQTT broker. In particular the ada
2323
## Authentication
2424

2525
The MQTT adapter by default requires clients (devices or gateway components) to authenticate during connection
26-
establishment. The adapter supports both the authentication based on the username/password provided in an MQTT CONNECT
26+
establishment. The adapter supports both authentication based on the username/password provided in an MQTT CONNECT
2727
packet as well as client certificate based authentication as part of a TLS handshake for that purpose.
2828

2929
The adapter tries to authenticate the device using these mechanisms in the following order
@@ -46,7 +46,8 @@ in order to support this mechanism.
4646

4747
The MQTT adapter supports authenticating clients based on credentials provided during MQTT connection establishment.
4848
This means that clients need to provide a *user* and a *password* field in their MQTT CONNECT packet as defined in
49-
[MQTT Version 3.1.1, Section 3.1](http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718028)
49+
[MQTT Version 3.1.1, Section 3.1.3](https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718031)
50+
and [MQTT Version 5.0, Section 3.1.3](https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901058)
5051
when connecting to the MQTT adapter. The username provided in the *user* field must match the pattern
5152
*auth-id@tenant*, e.g. `sensor1@DEFAULT_TENANT`.
5253

@@ -68,8 +69,9 @@ concepts.
6869
The MQTT adapter supports authenticating clients based on a signed
6970
[JSON Web Token](https://www.rfc-editor.org/rfc/rfc7519) (JWT) provided during MQTT connection establishment. This requires
7071
a client to provide a *client identifier*, a *user* and a *password* field in its MQTT CONNECT packet as defined in
71-
[MQTT Version 3.1.1, Section 3.1](http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718028)
72-
when connecting to the MQTT adapter. The JWT must be sent in the password field. The content of the *user* field is
72+
[MQTT Version 3.1.1, Section 3.1.3](https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718031)
73+
and [MQTT Version 5.0, Section 3.1.3](https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901058)
74+
when connecting to the MQTT adapter. The JWT must be sent in the *password* field. The content of the *user* field is
7375
ignored. The information about the tenant and the authentication identifier can be presented to the protocol adapter in
7476
one of two ways:
7577

@@ -107,26 +109,38 @@ a client tries to connect and/or send a message to the adapter.
107109

108110
### Connection Limits
109111

110-
The adapter rejects a client’s connection attempt with return code
112+
The adapter rejects a client’s attempt to connect using
111113

112-
* `0x03` (*Connection Refused: server unavailable*), if the maximum number of connections per protocol adapter instance
113-
is reached
114-
* `0x05` (*Connection Refused: not authorized*), if the maximum number of simultaneously connected devices for the
115-
tenant is reached.
114+
* MQTT 3.1.1 with return code `0x03` (*Server Unavailable*),
115+
* MQTT 5.0 with reason code `0x9C` (*Use Another Server*),
116+
117+
if the maximum number of connections per protocol adapter instance is reached.
118+
119+
The adapter rejects a client’s attempt to connect using
120+
121+
* MQTT 3.1.1 with return code `0x05` (*Not Authorized*),
122+
* MQTT 5.0 with reason code `0x97` (*Quota Exceeded*),
123+
124+
if the maximum number of simultaneously connected devices for the tenant is reached.
116125

117126
### Connection Duration Limits
118127

119-
The adapter rejects a client’s connection attempt with return code `0x05` (*Connection Refused: not authorized*), if the
120-
[connection duration limit]({{< relref "/concepts/resource-limits#connection-duration-limit" >}}) that has been
121-
configured for the client’s tenant is exceeded.
128+
The adapter rejects a client’s attempt to connect using
129+
130+
* MQTT 3.1.1 with return code `0x05` (*Not Authorized*)
131+
* MQTT 5.0 with reason code `0x97` (*Quota Exceeded*)
132+
133+
if the [connection duration limit]({{< relref "/concepts/resource-limits#connection-duration-limit" >}})
134+
that has been configured for the client’s tenant is exceeded.
122135

123136
### Message Limits
124137

125138
The adapter
126139

127-
* rejects a client's connection attempt with return code `0x05` (*Connection Refused: not authorized*),
140+
* rejects a client's attempt to connect using MQTT 3.1.1 with return code `0x05` (*Not Authorized*),
141+
* rejects a client's attempt to connect using MQTT 5.0 with reason code `0x97` (*Quota Exceeded*),
128142
* discards any MQTT PUBLISH packet containing telemetry data or an event that is sent by a client and
129-
* rejects any AMQP 1.0 message containing a command sent by a north bound application
143+
* rejects any command messages sent by a north bound application
130144

131145
if the [message limit]({{< relref "/concepts/resource-limits.md" >}}) that has been configured for the device’s tenant
132146
is exceeded.

tests/pom.xml

+5
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,11 @@
319319
<artifactId>vertx-mqtt</artifactId>
320320
<scope>test</scope>
321321
</dependency>
322+
<dependency>
323+
<groupId>com.hivemq</groupId>
324+
<artifactId>hivemq-mqtt-client</artifactId>
325+
<scope>test</scope>
326+
</dependency>
322327
<dependency>
323328
<groupId>org.eclipse.californium</groupId>
324329
<artifactId>californium-core</artifactId>

tests/src/test/java/org/eclipse/hono/tests/mqtt/MqttConnectionIT.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@
6767
import io.vertx.mqtt.MqttConnectionException;
6868

6969
/**
70-
* Integration tests for checking connection to the MQTT adapter.
70+
* Integration tests for checking MQTT 3.1.1 based connection to the MQTT adapter.
7171
*
7272
*/
7373
@ExtendWith(VertxExtension.class)

tests/src/test/java/org/eclipse/hono/tests/mqtt/MqttTestBase.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*******************************************************************************
2-
* Copyright (c) 2016, 2023 Contributors to the Eclipse Foundation
2+
* Copyright (c) 2023 Contributors to the Eclipse Foundation
33
*
44
* See the NOTICE file(s) distributed with this work for additional
55
* information regarding copyright ownership.
@@ -36,7 +36,7 @@
3636
import io.vertx.mqtt.messages.MqttConnAckMessage;
3737

3838
/**
39-
* Base class for MQTT adapter integration tests.
39+
* Base class for MQTT adapter integration tests using MQTT 3.1.1.
4040
*
4141
*/
4242
public abstract class MqttTestBase {
@@ -70,7 +70,7 @@ public abstract class MqttTestBase {
7070
protected Context context;
7171

7272
/**
73-
* Creates default AMQP client options.
73+
* Creates default MQTT client options.
7474
*/
7575
@BeforeAll
7676
public static void init() {

0 commit comments

Comments
 (0)