eclipse / paho.mqtt-sn.embedded-c

Paho C MQTT-SN gateway and libraries for embedded systems. Paho is an Eclipse IoT project.
https://eclipse.org/paho
Other
314 stars 179 forks source link

MQTT-Client can't move from one wireless node to another #188

Closed martinkirsche closed 4 years ago

martinkirsche commented 4 years ago

My forwarder uses the client MAC address as the Wireless Node Id for the encapsulated MQTT-SN messages. It can happen that the MAC address changes (random MAC or different hardware). However the MQTT-SN ClientId won't change.

It looks like the gateway won't notice such a change and will keep using the old Wireless Node Id even if the MQTT-SN client already reconnected using its new Wireless Node Id.

I can't find anything in the MQTT-SN Protocol Specification that forces the ClientId and Wireless Node Id to be the same or only change together.

The following script will trigger the issue:

import socket
import time

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind(("127.0.0.1", 22002))

sock.sendto(bytearray([0x04, 0xFE, 0x00, 0x01, 0x16, 0x04, 0x04, 0x01, 0x00, 0x3c, 0x47]), ("127.0.0.1", 10000))
print(sock.recvfrom(6))
sock.sendto(bytearray([0x04, 0xFE, 0x00, 0x01, 0x02, 0x16]), ("127.0.0.1", 10000))
print(sock.recvfrom(5))

# Reconnecting using a different Wireless Node Id. But the gateway will send the answer to the old Wireless Node Id.
sock.sendto(bytearray([0x04, 0xFE, 0x00, 0x02, 0x16, 0x04, 0x04, 0x01, 0x00, 0x3c, 0x47]), ("127.0.0.1", 10000))
print(sock.recvfrom(6))

# The next message will trigger the error.
#Error: MQTTSNGWClientRecvTask  Client(127.0.0.1:22002) is not connecting. message has been discarded.
sock.sendto(bytearray([0x04, 0xFE, 0x00, 0x02, 0x02, 0x16]), ("127.0.0.1", 10000))

# This message was just added to check if the old Wireless Node Id will still work.
sock.sendto(bytearray([0x04, 0xFE, 0x00, 0x01, 0x02, 0x16]), ("127.0.0.1", 10000))
print(sock.recvfrom(5))

And the MQTTSNGateway's log output:

20200319 224901.620   CONNECT           ===>  mqttsn-gateway_QoS-1                10 20 00 04 4D 51 54 54 04 02 03 84 00 14 6D 71 74 74 73 6E 2D 67 61 74 65 77 61 79 5F 51 6F 53 2D 31
20200319 224901.621   CONNACK           <===  mqttsn-gateway_QoS-1                20 02 00 00
20200319 224901.621   CONNACK           --->  mqttsn-gateway_QoS-1                03 05 00
20200319 224902.086   ENCAPSULATED      <---  Forwarder01                         04 FE 00 01 16 04 04 01 00 3C 47

20200319 224902.086   CONNECT           <---  G                                   16 04 04 01 00 3C 47
20200319 224902.086   CONNECT           ===>  G                                   10 0D 00 04 4D 51 54 54 04 02 00 3C 00 01 47
20200319 224902.121   CONNACK           <===  G                                   20 02 00 00
20200319 224902.121   CONNACK           --->  G                                   03 05 00
20200319 224902.121   ENCAPSULATED      --->  Forwarder01                         FE 00 01 03 05 00
20200319 224902.122   ENCAPSULATED      <---  Forwarder01                         04 FE 00 01 02 16

20200319 224902.122   PINGREQ           <---  G                                   02 16
20200319 224902.122   PINGREQ           ===>  G                                   C0 00
20200319 224902.122   PINGRESP          <===  G                                   D0 00
20200319 224902.122   PINGRESP          --->  G                                   02 17
20200319 224902.122   ENCAPSULATED      --->  Forwarder01                         FE 00 01 02 17
20200319 224902.122   ENCAPSULATED      <---  Forwarder01                         04 FE 00 02 16 04 04 01 00 3C 47

20200319 224902.122   CONNECT           <---  G                                   16 04 04 01 00 3C 47
20200319 224902.122   CONNECT           ===>  G                                   10 0D 00 04 4D 51 54 54 04 02 00 3C 00 01 47
20200319 224902.123   CONNACK           <===  G                                   20 02 00 00
20200319 224902.123   CONNACK           --->  G                                   03 05 00
20200319 224902.123   ENCAPSULATED      --->  Forwarder01                         FE 00 01 03 05 00
20200319 224902.123   ENCAPSULATED      <---  Forwarder01                         04 FE 00 02 02 16

20200319 224902.123   PINGREQ           <---  Unknown Client !                    02 16
Error: MQTTSNGWClientRecvTask  Client(127.0.0.1:22002) is not connecting. message has been discarded.
20200319 224902.123   ENCAPSULATED      <---  Forwarder01                         04 FE 00 01 02 16

20200319 224902.123   PINGREQ           <---  G                                   02 16
20200319 224902.123   PINGREQ           ===>  G                                   C0 00
20200319 224902.123   PINGRESP          <===  G                                   D0 00
20200319 224902.123   PINGRESP          --->  G                                   02 17
20200319 224902.123   ENCAPSULATED      --->  Forwarder01                         FE 00 01 02 17
ty4tw commented 4 years ago

Hi,

Forwarder Addresses and ID are defined in clients.conf as Clients. It's not a specification. it's my own restriction for security. (Forwarders should be defined by clients.conf.)

your Encapsulation format is not correct.

[0x04, 0xFE, 0x00, 0x01, 0x16, 0x04, 0x04, 0x01, 0x00, 0x3c, 0x47]

Client ID in the CONNECT is missing and Packet lengthes are not correct.

[0x05, 0xFE, 0x00, 0x01, 0x16, 0x08, 0x04, 0x01, 0x00, 0x3c, 0x47,     0x43, 0x44]
                                                                  clientID  "CD"
sock.sendto(bytearray([0x05, 0xFE, 0x00, 0x01, 0x16, 0x08, 0x04, 0x01, 0x01, 0x3c, 0x47, 0x43, 0x44]), ("127.0.0.1", 10000))

sock.sendto(bytearray([0x05, 0xFE, 0x00, 0x02, 0x16, 0x08, 0x04, 0x01, 0x01, 0x3c, 0x47, 0x43, 0x45]), ("127.0.0.1", 10000))

Result of the correct message is

20200320 153357.170   ENCAPSULATED     <---  Forwarder01            05 FE 00 01 16 08 04 01 01 3C 47 43 44

20200320 153357.170   CONNECT          <---  CD                     08 04 01 01 3C 47 43 44
20200320 153357.171   CONNECT           ===> CD                     10 0E 00 04 4D 51 54 54 04 00 3C 47 00 02 43 44
20200320 153357.441   CONNACK           <=== CD                     20 02 01 00
20200320 153357.441   CONNACK           ---> CD                     03 05 00
20200320 153357.441   ENCAPSULATED      ---> Forwarder01            FE 00 01 16 03 05 00
20200320 153409.798   ENCAPSULATED     <---  Forwarder01            05 FE 00 02 16 08 04 01 01 3C 47 43 45

20200320 153409.798   CONNECT          <---  CE                     08 04 01 01 3C 47 43 45
20200320 153409.798   CONNECT           ===> CE                     10 0E 00 04 4D 51 54 54 04 00 3C 47 00 02 43 45
20200320 153409.955   CONNACK           <=== CE                     20 02 00 00
20200320 153409.955   CONNACK           ---> CE                     03 05 00
20200320 153409.955   ENCAPSULATED      ---> Forwarder01            FE 00 02 16 03 05 00
martinkirsche commented 4 years ago

Hi, you are right the length field of the message was wrong. But the length field of the forwarding is fine.

However, in your modified example you also changed the client ID at the second connect call. In that case the bug is not triggered. So I restructured my example to get a better overview of what is actually happening.

import socket

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind(("127.0.0.1", 22002))

def connect(wireless_node_id):
    return bytearray([
        # Forwarder Encapsulation
        0x04, # Length(0)
        0xFE, # MsgType(1)
        0x00, # Ctrl(2)
        wireless_node_id, # Wireless Node Id (3)

        # Message
        0x07, # Length(0)
        0x04, # MsgType(1)
        0x04, # Flags(2)
        0x01, # ProtocolId(3)
        0x00, # Duration(4)
        0x3c, # Duration(5)
        0x47, # ClientId(6)
    ])

def ping(wireless_node_id):
    return bytearray([
        # Forwarder Encapsulation
        0x04, # Length(0)
        0xFE, # MsgType(1)
        0x00, # Ctrl(2)
        wireless_node_id, # Wireless Node Id (3)

        # Message
        0x02, # Length(0)
        0x16, # MsgType(1)
    ])

sock.sendto(connect(1), ("127.0.0.1", 10000))
print(sock.recvfrom(6))

sock.sendto(ping(1), ("127.0.0.1", 10000))
print(sock.recvfrom(5))

# Reconnecting using a different Wireless Node Id. But the gateway will send the answer to the old Wireless Node Id.
sock.sendto(connect(2), ("127.0.0.1", 10000))
print(sock.recvfrom(6))

# The next message will trigger the error.
#Error: MQTTSNGWClientRecvTask  Client(127.0.0.1:22002) is not connecting. message has been discarded.
sock.sendto(ping(2), ("127.0.0.1", 10000))

# This message was just added to check if the old Wireless Node Id will still work.
sock.sendto(ping(1), ("127.0.0.1", 10000))
print(sock.recvfrom(5))

The bug will still get triggered, even if the length field has the right value.

ty4tw commented 4 years ago

Wireless Id and Client Id pare is a key to identify the client to ensure security. So, the Gateway can not change the wireless Id of a forwarded client. According to the spec, Wireless Node Id: identifies the wireless node which has sent or should receive the encapsulated MQTT-SN message. The mapping between this Id and the address of the wireless node is implemented by the for- warder, if needed.

martinkirsche commented 4 years ago

I think the identity of a wireless node is most likely bound to some kind of hardware device. But that is not necessarily true for the the client, which can run on any device. This is probably why they use two different IDs, one for the wireless node and one for the client. Handling them as a pair to identify the client is not mentioned within the MQTT-SN spec.

It should be possible to let the client use a different wireless node to communicate as long as the client does a reconnect.