CENNSO-3792: Dynamic per-server port pool: structurally safe RADIUS id reuse - #249
Merged
Merged
Conversation
The socket now connects to its server (OS-assigned source port, kernel filters replies to the peer) and keys its pending map on ReqId alone. Groundwork for the per-server port pool. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A retired socket holds its (bound) source port for reqid_reuse_timeout (the quarantine), keeps serving in-flight requests, then exits normally once cooldown elapsed AND pending is empty. Force-closed at 2x cooldown. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
no_ports now means K (concurrent active fillers per server). New opts default to 256 sockets/server cap and a 30s reqid reuse cooldown. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replaces the blind rotating counter + static socket array with per-server pools of K connected 'filler' sockets. Each filler issues ids 0..255 then is retired (cooled+closed by the socket); a fresh one is opened on demand. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Monitors on filler/cooling sockets let the manager drop a socket from its pool when it closes (post-cooldown) or crashes, keeping the cap accounting accurate. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Address change closes all pool sockets (and demonitors) then resets; removed servers have their pools dropped. Legacy socket-array/idcounters test assertions rewritten against the pool model; no_ports_one_wraps removed (superseded by pool_rolls_and_retires). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds rotation_uses_fresh_source_ports (across a 256-id wrap the client
sends from a fresh OS source port; no {srcport,id} pair repeats).
With the K-filler pool a RADIUS server sees the client across several
source ports, so eradius_metrics_SUITE sums the server-side per-nas_ip
request/reply counters instead of expecting a single label-set.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- socket_sup: restart => temporary. Pool sockets are manager-managed (opened on demand, monitored, reclaimed on exit); auto-restart would orphan a socket the manager never learns about. - Refresh the eradius_client_mngr 'Socket pool' moduledoc for the per-server dynamic pool (no_ports=K, reqid_reuse_timeout, max_ports_per_server). - Add max_ports_per_server/reqid_reuse_timeout to client_config() type; tighten no_ports to pos_integer(). - Remove now-unused test-only get_socket_count/1. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The active filler list is a FIFO round-robin (take head, re-enqueue at tail; new fillers appended; retired head dropped). Model it with the queue module: queue:out/in for the rotation, queue:filter for by-pid removal on DOWN. No behaviour change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A filler's pid is unique, so removing it from the active queue on DOWN is a single-element delete; queue:delete_with expresses that and short-circuits instead of rebuilding the whole queue like queue:filter. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
RoadRunnr
force-pushed
the
feature/client-dynamic-port-pool
branch
from
June 4, 2026 10:48
3ec66e2 to
d3a6f68
Compare
RoadRunnr
marked this pull request as ready for review
June 5, 2026 07:57
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.
Summary
Replaces the eradius client's blind rotating Identifier allocator (
next_port_and_req_id/3+ static socket array) with a dynamic per-server pool of connected UDP sockets. Each socket is dedicated +gen_udp:connected to one server (OS-assigned source port; kernel filters replies to the peer), issues request ids0..255exactly once, then is retired: held open forreqid_reuse_timeout(the bound source port is the quarantine), drained, and closed.no_ports, default 10), round-robin; rolls to a fresh socket every 256 ids.max_ports_per_server(default 256) →{error, no_ports}backpressure instead of a silent Identifier collision.reconfigurecloses/rebuilds pools and drops removed servers' pools; pool sockets aretemporary(manager-managed).Why
Under a slow RADIUS server the old allocator re-issued live
{port, id}pairs, causing the client hang (mode A, mitigated in #248), premature reuse vs the server's duplicate-detection window (mode B), and delayed-reply misrouting (mode C). Rotating the source port makes reuse structurally safe: a reused id always lands on a fresh(client IP, port)key, which the server sees as a distinct client. Builds on #248 (the bounded call timeout guarantees in-flight drains within the cooldown).Behavioral change to note
With K active fillers round-robined, a single RADIUS server now sees the client across up to K source ports concurrently (the old code pinned a peer to one source port, rotating every 256). This is intended (throughput / mailbox spread) and safe.
eradius_metrics_SUITEwas updated to sum the server-side per-nas_ipcounters accordingly.Test plan
{error, no_ports};'DOWN'reclaims cooling slots; reconfigure resets pools.{srcport, id}pair repeats within the cooldown.rebar3 ctgreen on OTP 28.3 (153 tests);rebar3 fmtclean.Follow-ups (not in this PR)
active/cooling/totalgauges +no_ports/open/close counters viametrics_callback. This PR ships a minimalno_ports_rejectionsstate counter + warning log; full metric wiring is deferred.eradius_client:send_request_loopmatches{error, close}but the socket returns{error, closed}, so a closed socket surfaces as a generic error rather thansocket_down. Predates this work; worth a small follow-up.Design spec:
docs/superpowers/specs/2026-06-04-eradius-dynamic-port-pool-design.md🤖 Generated with Claude Code