DTLS establishing a new association with existing host/port quartet
Hello, I'm working to build a CoAP server that supports DTLS using BC 2.2.1. When the client reboots, it often selects the same source port number. In my server, this appears as the same session as the quartet is the same. Per RFC6347 section 4.2.8 (DTLS 1.2) and RFC9147 section 5.11 (DTLS 1.3), establishing a new association using the existing values is allowed. According to the RFC, what should happen is that the existing session should remain until a new handshake is completed successfully; then the old session can be replaced by the new.
This condition does not seem to be caught by the existing code. It seems that in DtlsRecordLayer.ReceivedRecord it simply fails the check for the epoch and returns -1. What I would like is a way to detect this condition and handle it properly in my session management code.
/// <exception cref="IOException"/>
private int ReceiveRecord(byte[] buf, int off, int len, int waitMillis)
{
if (m_recordQueue.Available > 0)
return ReceivePendingRecord(buf, off, len);
int received = ReceiveDatagram(buf, off, len, waitMillis);
if (received >= RECORD_HEADER_LENGTH)
{
this.m_inConnection = true;
short recordType = TlsUtilities.ReadUint8(buf, off);
int epoch = TlsUtilities.ReadUint16(buf, off + 3);
DtlsEpoch recordEpoch = null;
if (epoch == m_readEpoch.Epoch)
{
recordEpoch = m_readEpoch;
}
else if (recordType == ContentType.handshake && null != m_retransmitEpoch
&& epoch == m_retransmitEpoch.Epoch) // ** epoch check fails here **
{
recordEpoch = m_retransmitEpoch;
}
if (null == recordEpoch)
return -1;
Probably this should be managed using DtlsVerifier.
Simplistically every incoming packet gets submitted to DtlsVerifier.VerifyRequest. If it returns null, then try to dispatch the packet to any existing association instead. If it returns a DtlsRequest instance then remove any existing association and start a new handshake using the DtlsServerProtocol.Accept variant that accepts a DtlsRequest.
I say "simplistically" because looking at the implementation that will be a bit expensive to do for every packet, so as the current code stands you may want to first inspect each packet to see if it looks like a new ClientHello before using the full DtlsVerifier check. We can look at improving the performance and/or API with this sort of usage in mind.
Thanks Peter.
I removed my temporary hack and replaced it with a call to DtlsVerifier.VerifyRequest for each incoming packet as a test. The first time the device connects, it works correctly as before as you can see in the trace below:
I've traced through the code and VerifyRequest is returning
null as expected.
When my device reboots I see that VerifyRequest seems to properly detect the same quartet is being re-used. It also sends a HelloVerify request message to the device as can be see in this following trace:
In my case, I'm not getting anything back from my device. Tracing the code I see that VerifyRequest is still returning null (likely because my device is not responding to the request). This gets me into an unproductive loop as before due to the session not matching properly. Looking over the RFC it seems that the client (my device in this case) MUST respond to the HelloVerify. I'm going to see if I can get our device firmware to enable this. In the meantime, is my assumption correct here?
As for performance, I'd like to better understand what should be the appropriate option here.
Thank you in advance.
In the first image above, why is there no HelloVerifyRequest being sent? Are you only using DtlsVerifier when there is an existing association?
We want to first establish that the client actually supports HelloVerifyRequest so I suggest to try and nail down this first connection so that you see ClientHello -> HelloVerifyRequest -> ClientHello -> ServerHello. The second ClientHello should pass DtlsVerifier.VerifyRequest (i.e return a DtlsRequest) because it should have set the cookie according to the HelloVerifyRequest.
If using DtlsVerifier, DtlsServerProtocol should only be created once you have a DtlsRequest.
The second image suggests that maybe the client doesn't support HelloVerifyRequest, but there might be some other error happening preventing it from accepting it. Are you able to debug/log the client to investigate this?
EDIT: In particular, please check (for the second image) if the second ClientHello contains a cookie matching the one in the first HelloVerifyRequest.
I have now improved the performance of DtlsVerifier so that it can reasonably be used for every incoming packet in the way I described above. There will probably be a 2.3.0 release sometime around mid-June, if not before.
Did you make any progress with the HelloVerifyRequest handling of your client?
Hi Peter, I apologize for not responding to the previous comment. I did have an error on my end where I was only sending the HelloVerifyRequest if there was an existing association. I've fixed that now but I'm still not getting a response back from the device. I've confirmed that the device supports this message so I'm not sure where exactly is the issue. I'm working with our device team to debug this at the moment.
Thank you for the new DtlsVerifier. I'll pull that down and add it my current implementation for testing. I'll let you know how it goes.
Thanks for the help!