Skip to content

feat(protocol): resolve and connect UDP endpoints - #24

Open
Jason-Morcos wants to merge 2 commits into
QuiteYellow:mainfrom
Moballo-LLC:codex/py-03-connected-endpoints
Open

feat(protocol): resolve and connect UDP endpoints#24
Jason-Morcos wants to merge 2 commits into
QuiteYellow:mainfrom
Moballo-LLC:codex/py-03-connected-endpoints

Conversation

@Jason-Morcos

@Jason-Morcos Jason-Morcos commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

Dependency

This is a one-commit stack on #23 because the endpoint contract uses its public, redacted EndpointError. The endpoint-specific commit is d677c72. Once #23 lands, this branch can be rebased and the PR diff will contain only that commit.

Problem

DtlsCoapSession currently creates an AF_INET socket unconditionally and carries the unresolved (host, port) tuple through every sendto/recvfrom call.

That has three concrete limitations:

  1. IPv6 and scoped IPv6 endpoints cannot be used.
  2. A datagram from another host using the same destination port reaches the DTLS BIO because the socket is not pinned to a peer.
  3. Fixed source-port binding always uses an IPv4 wildcard, so address family and bind behavior cannot be reviewed or tested independently.

Python's socket documentation defines the IPv4 and IPv6 getaddrinfo tuple shapes, recommends constraining type/protocol, and recommends trying returned candidates in order: https://docs.python.org/3/library/socket.html#socket.getaddrinfo

Public API and behavior

  • Add immutable ResolvedUdpEndpoint, preserving exact IPv4 or IPv6 sockaddr values including IPv6 flow/scope IDs.
  • Keep endpoint repr redacted to address family only.
  • Add ordered, de-duplicated UDP resolution helpers constrained to SOCK_DGRAM / IPPROTO_UDP.
  • Add connected-socket setup that tries the next candidate after socket, bind, or connect failure.
  • Preserve optional fixed local_port behavior with a family-correct wildcard bind.
  • Add an optional family= argument to DtlsCoapSession, defaulting to AF_UNSPEC.
  • Expose the active resolved endpoint as session.endpoint.
  • Clear both endpoint and resolved destination state on close().
  • Move the session transport to connected-socket send/recv; short sends and socket failures become redacted EndpointError instances.

A connected datagram socket sets the peer used by subsequent send operations and limits received datagrams to that peer: https://man7.org/linux/man-pages/man3/connect.3p.html

Compatibility and scope

Existing constructor calls, certificate inputs, local_port, request APIs, and ConnectionError/ OSError catches remain valid. Resolution happens afresh on each connect(), so host address changes are picked up on reconnect.

This PR does not change probing, authentication, handshake timing, retry policy, CoAP behavior, or lifecycle ownership. Probe concurrency and ambiguity remain a separate change.

Validation

  • Full stacked suite, Python 3.14 / OpenSSL 3.6.3: 101 passed
  • Endpoint-focused suite: 20 passed
  • Connected-peer loopback isolation test: passed 25 consecutive runs
  • Deterministic coverage: IPv4, scoped IPv6, resolver ordering/deduplication, family filtering, bind fallback, connect failure cleanup, fixed source-port reuse, same-port/different-host isolation, redacted tracebacks, and connected DTLS send/recv
  • Wheel and sdist include smartthings_local/protocol/endpoint.py; built-wheel import passed
  • New-module Ruff checks, compile check, cached-diff check, and staged share-safety scan: passed

The LibreSSL compatibility fix from #22 and the validation workflow from #21 are now included in upstream main.

@Jason-Morcos
Jason-Morcos marked this pull request as ready for review August 2, 2026 21:54
@Jason-Morcos

Copy link
Copy Markdown
Contributor Author

@codex review

@Jason-Morcos
Jason-Morcos force-pushed the codex/py-03-connected-endpoints branch from 4e43c5c to a833db2 Compare August 2, 2026 23:33
@Jason-Morcos

Copy link
Copy Markdown
Contributor Author

Adversarial follow-up complete for exact head a833db2d1d427fa5a3e0b196b80ffe3d040d2905. Resolver, socket-setup, handshake send/receive, and runtime send failures now discard raw exception context; close() clears both endpoint and the resolved destination; a runtime send regression test covers the redaction boundary; and the scoped-IPv6 fixture uses the documentation range.

Validation: 86 passed on Python 3.14 / OpenSSL 3.6.3; 20 endpoint-focused tests passed; the introduced-content safety and diff checks passed.

@codex review

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant