Open lcoudeville opened 5 years ago
Diving in the code learned me that PING messages are signal messages and not response/request messages. However I did not reveal a legal way to send them except hacking the tcp connection itself from the TCPClient._pool
. I didn't find a proper way even in the tests to send them. I guess a built-in manner to maintain a TCP connection might be interesting for others as well.
This code did the job for me:
ping = Message(code=Code.PING)
for tcp_conn in self.coap_client_tokenmanager.token_interface._pool.values():
tcp_conn._send_message(ping)
If somebody can give some insight where this could should be I can contribute this to the community.
I see a few options:
Ping messages are internal to the chosen protocol (CoAP-over-TCP), and so far not exposed in aiocoap. The request method is, as you discovered, there for using the request/response layer of CoAP (provided eg. by the token manager), where there exist neither pings nor empty messages.
Where it'd make sense to add support for sending pings depends on what they are used for -- I haven't found a case for it myself yet.
What do you use signalling pings for? Do you use/need empty messages (RFC8232 Section 3.4) or TCP keepalives as well? Do you use the custody option? Based on that, we can try to figure out where to best put them.
I really like the fact that those messages are handled internally - this makes aiocoap compatible with libraries that have this functionality - so well done. But there is a problem when using aiocoap to send such messages.
Sending empty messages is an alternative but does't telly anything if the other end is alive and kicking. For example if the socket on the other end might be alive but the other end will never be able to reply (I agree this is an issue on the other end but it would be nice to verify that the other side is ok).
I made a small change to codes.py
which makes it possible to send ping requests. Consider my output of git diff
below:
diff --git a/aiocoap/numbers/codes.py b/aiocoap/numbers/codes.py
index 4981839..ee6d87f 100644
--- a/aiocoap/numbers/codes.py
+++ b/aiocoap/numbers/codes.py
@@ -69,15 +69,15 @@ class Code(ExtensibleIntEnum):
def is_request(self):
"""True if the code is in the request code range"""
- return True if (self >= 1 and self < 32) else False
+ return True if (self >= 1 and self < 32) or self == Code.PING else False
def is_response(self):
"""True if the code is in the response code range"""
- return True if (self >= 64 and self < 192) else False
+ return True if (self >= 64 and self < 192) or self == Code.PONG else False
def is_signalling(self):
- return True if self >= 224 else False
+ return self in (Code.CSM, Code.PING, Code.RELEASE, Code.ABORT)
This allows to initiate a PING
request and await for the reply (see code below, it's a beauty). However, it doesn't feels right that PING
is still in the is_signalling
list because . On the other hand, PING and PONG are signalling message and belong both to the 'is_signalling' messages. Ping is still in the "is_signalling" list because ping messages, which expect pong replies, would be propagated else.
This is how I can send messages to certain connections:
for tcp_conn in self.coap_client_tokenmanager.token_interface._pool.values():
ping = Message(code=Code.PING)
ping.remote = tcp_conn
logging.debug("Send ping")
await self.coap_context.request(ping).response
logging.debug("Pong received")
I really like this because I can use asyncio.wait_for(timeout) now if I don't receive a reply within the expected interval.
On Mon, Jan 07, 2019 at 04:43:59PM +0000, lcoudeville wrote:
But there is a problem when using aiocoap to send such messages.
I think that this is the core misunderstanding here -- "messages" are something that happens in the serialization of CoAP. On the "upper" side of the protocol (which is what aiocoap primarily exposes), there are no messages, only requests and responses, and the .request() / .response mechanism will not cater for anything else, because it needs to be agnostic of the underlying transports.
Ping/Pong happens to have request/response semantics too, but with different characteristics (eg. a request is sent to an origin server, while a ping is sent to the directly connected endpoint). Shoehorning them into the request/response mechanism will break at latest when working with proxies.
Before we jump to implementation choices, my question is still: What is the problem you are trying to solve by using Ping/Pong? That can then guide us to the adaequate API.
Well actually I'm having a CoAP server with CoAP clients which are in a NAT or private network, so in fact there a firewall in front of them before they reach an other network. So only the client can take the initiative to connect to the coap server. The server is not able to reach them except there is port forwarding enabled in the firewall/router. So I'm want to use Ping and Pong messages on the client initiative to ensure that the transport level is reliable and that the coap server is healthy.
I agree that for transport only I can use empty messages as well. However I was not able yet to get them work because due to a similar issue of ping/pong. Empty has code 0.00 or integer value 0. So it's not in the is_request
range either, so I don't know yet how to use them. Except of hacking the tcp connection and use the _send_message
function.
So what I'm actually want to do is build a mechanism were I can send ping/pong messages and if I don't get a reply withing X ping messages I consider the connection and/or server endpoint as dead. When a connection is being marked as "dead" I'll try to recreate a connection.
My primary goal is to ensure that the transport is reliable without sending "real requests". That's something that is mentioned in the signalling rfc 8323: https://tools.ietf.org/html/rfc8323#section-5.4 .
Do you have any purposes on how to implement them without breaking the request/response semantics?
So just to make sure I understand your particular setup:
Server --- Internet --- Local router --- Client
with NAT
where the local router closes connections even though they are perfectly alive.
Do you do role reversal? (I.e. does your server send requests to the client?) Do you have observations running? Do you have requests that take so long to respond to that the connection is closed between request and response? If neither: Why does it matter whether the connection stays on -- for latency? How do you determine how often to send pings?
As long as aiocoap has no support for pings (which I'm fully for adding and appreciative of patches, but would first like to understand the problem to solve), what I'd suggest is a GET to a short or non-existing resource (eg. ctx.request(Message(code=GET, uri=hostname), handle_blockwise=False)
), if you need to ship something fast; but I'm confident we can get to the point of what we really need here with a few exchanges.
Well actually I make a distinction between tcp and "coap" terms. I have a tcp client which acts as coap server and client over the same "coap context". I use a server context on both sides. (I've already described my setup in https://github.com/chrysn/aiocoap/pull/132)
The tcp client, which is a coap client and server, connects to a tcp server which also acts as a coap client and server. So the connection that is initiated by the client can be used in both directions, the tcp "server" can make requests to the "tcp client" and reversed.
Without persistent connection the server can't make a request if the client is not connected. Except port forwarding is configured on the router and the possibly client address(es) are known on the server.
In tcp terms:
Server -- Internet -- NAT -- Client
Coap:
Server/Client -- Internet -- NAT -- Client/Server
I'm aware of the fact that I can send GET messages to an "empty" resource, but I don't want to generate unnecessary load on the coap server.
I'm encountering issues when sending PING messages on a tcp coap (coap+tcp schema) connection. I expected this to be quite simply so I started from the client example.
Replacing
Code.PING
withCODE.GET
works, but without expected result obviously. I don't understand how it's possible that a property request is expected or can be expected on a Message class.I'm using the current master.
What am I doing wrong?