Accept DTLS replies from any port on the target host - #73
Merged
Conversation
RT-OCF binds its unicast and DTLS sockets to port 0 (rt_udp.c, rt_udp_open_server), so an appliance's secure port is kernel-assigned and it answers from a port the client never dialled. A connected UDP socket drops those datagrams and the kernel returns ICMP port unreachable, which made dtls_probe report a live appliance as dead and left the session retransmitting a cookie-less ClientHello until its deadline. Add HostFilteredUdpSocket and open_host_filtered_udp_socket, which bind without connecting and filter inbound datagrams on host alone, matching what ocf_discovery already does. Sending stays on the port originally dialled, since issue #66's capture shows the appliance still accepting there. open_connected_udp_socket is unchanged. Off-path spoofing resistance drops from address-and-port to address alone; the DTLS cookie exchange and handshake authentication remain the real protection.
The share-safety check rejects non-documentation addresses, and the scope comparison behaves identically on 2001:db8::/32.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #66.
The failure
dtls_probeandDtlsCoapSessionopened a connected UDP socket, which accepts datagrams only from the exact port it dialled. @elirnyk's tcpdump on an NQ8300T oven shows the reply arriving from a different port:The kernel discarded that 60-byte reply and answered it with ICMP, so the probe reported a live appliance as
dead.Why an appliance does this
Stock RT-OCF,
messaging/transport/rt_udp.c,rt_udp_open_server():Multicast takes the only fixed port. The unicast and DTLS sockets bind port 0, so the kernel assigns them.
rt_udp_get_secure_port_v4()reads that assigned port back offdtls_v4, and that is the value advertised asdportin/oic/res.So a secure port is ephemeral by design, it is reassigned across a reboot, and a reply from a port nobody dialled is the appliance's real DTLS socket. This is also why the advertised port in #16 moved from 46060 to 39181 over five days.
The change
HostFilteredUdpSocketandopen_host_filtered_udp_socketinendpoint.py. The socket binds without connecting, keeps sending to the resolved destination, and filters inbound datagrams on host alone, which is whatocf_discoveryalready does. Bothdtls_probecall sites and theDtlsCoapSessioncall site use it.open_connected_udp_socketis unchanged and still covered by its own tests.Sending stays on the port originally dialled. Following the reply port would be a behaviour change with nothing behind it, and #66's capture shows the oven still accepting on 5684.
Measured against a responder that answers from another source port
dead, 3 ClientHellos discardedlive 8ms HelloVerifyRequest, 1 ClientHellolivelive0, 0, 00, 32, 32, 32The last row is the session half: before this, the client never saw the HelloVerifyRequest, so every ClientHello went out without a cookie. The responder used for those runs binds one socket for receiving and answers from a second, unbound one; the behaviour it exercises is covered by the unit tests below.
Two things to be aware of
Off-path spoofing resistance drops from address-and-port to address alone. The DTLS cookie exchange and handshake authentication are the real protection either way, and
ocf_discoveryalready accepted this, but it now applies on the session path too._ADVISORY_ERRNOSindtls_session.pycovered ICMP errors that only a connected socket surfaces. The comment there already recorded that the reader survived unconnected sockets before d677c72, so that handling now costs nothing and catches nothing on this path. I updated the comment and kept the handling for callers supplying their own socket adapter.676 tests pass, 6 of them new: acceptance from another port, send-target stability, rejection of another host, IPv6 scope as part of host identity, and a bound deadline under a flood of foreign datagrams.