enb_proxy: fix stale SCTP events misprocessed during MME pool selection
When closing a connection to one MME and opening a new one to the next, a SHUTDOWN_COMP event from the old (now-closed) socket can still be pending in the process mailbox. The sctp_assoc_change handlers in the 'connecting' and 'wait_s1setup_rsp' states were matching '_Socket', ignoring the socket identity. A stale SHUTDOWN_COMP would therefore fall into the '_ -> repeat_state_and_data' branch, triggering a spurious re-entry of 'connecting', which immediately closed the newly established connection to the next MME and skipped to yet another pool entry.
Fix this by matching the Socket against the current S#state.sock in both handlers. Events arriving on a previously closed socket no longer match these clauses and are silently dropped by the catch-all handle_event/4.
This was found thanks to the MME pooling TCs in ttcn3-s1gw-test.
Rework the CONNECTING state to dynamically select an MME from the pool via mme_registry:mme_select/1, passing the eNB's Tracking Area Codes (from the ?'id-SupportedTAs' IE of the S1 SETUP REQUEST) and a list of already-tried MMEs, so successive attempts pick a different candidate.
On connection failure (SCTP establishment timeout or error), or when the selected MME rejects the S1 SETUP REQUEST or fails to respond in time, the FSM re-enters the CONNECTING state rather than terminating. This triggers another mme_select/1 call with the failed MME added to the tried_mmes list. S1 SETUP FAILURE PDUs from the MME are intentionally not forwarded to the eNB, so the retry is fully transparent.
Once mme_select/1 exhausts all candidates it returns 'error'; at that point the FSM builds and sends an S1 SETUP FAILURE PDU to the eNB and terminates.
mme_registry: add backwards compat for old sctp_client config
When 'mme_pool' is absent from the config, automatically populate the MME pool with a single 'default' entry derived from the 'sctp_client' section (including legacy mme_loc_addr/mme_rem_addr params), and emit a deprecation warning. osmo_s1gw_sup:init/1 normalises 'sctp_client' in the app env (with all defaults applied) before children start, so mme_registry can safely read it without duplicating the parsing logic.
Rework the CONNECTING state to dynamically select an MME from the pool via mme_registry:mme_select/1, passing the eNB's Tracking Area Codes (from the ?'id-SupportedTAs' IE of the S1 SETUP REQUEST) and a list of already-tried MMEs, so successive attempts pick a different candidate.
On connection failure (SCTP establishment timeout or error), or when the selected MME rejects the S1 SETUP REQUEST or fails to respond in time, the FSM re-enters the CONNECTING state rather than terminating. This triggers another mme_select/1 call with the failed MME added to the tried_mmes list. S1 SETUP FAILURE PDUs from the MME are intentionally not forwarded to the eNB, so the retry is fully transparent.
Once mme_select/1 exhausts all candidates it returns 'error'; at that point the FSM builds and sends an S1 SETUP FAILURE PDU to the eNB and terminates.
enb_registry: rework handling of eNB property updates
The idea of an enb_event/0 containing the current MME connection state and the associated information (Global-eNB-ID, eNB/MME conn info) looked promising initially, however turned out to be impractical in light of ongoing MME pooling related changes.
* remove type enb_state/0 * rename type enb_event/0 -> enb_prop/0 * rename function enb_event/2 -> enb_update/2 (now private) * enb_prop/0: separate the state from other properties * enb_update/2: accept a list of enb_prop/0 - enb_proplist/0 * add typed notify_*() helpers that group related property updates, ensuring consistency and serving as the sole public call sites * notify_mme_connecting(): explicitly clears mme_conn_info * openapi: EnbItem.state reflects the actual enb_proxy FSM state
enb_registry: rework handling of eNB property updates
The idea of an enb_event/0 containing the current MME connection state and the associated information (Global-eNB-ID, eNB/MME conn info) looked promising initially, however turned out to be impractical in light of ongoing MME pooling related changes.
* remove type enb_state/0 * rename type enb_event/0 -> enb_prop/0 * rename function enb_event/2 -> enb_update/2 (now private) * enb_prop/0: separate the state from other properties * enb_update/2: accept a list of enb_prop/0 - enb_proplist/0 * add typed notify_*() helpers that group related property updates, ensuring consistency and serving as the sole public call sites * notify_mme_connecting(): explicitly clears mme_conn_info * openapi: EnbItem.state reflects the actual enb_proxy FSM state
enb_registry: fix duplicate registration check in enb_register/0
next_handle was bound in the function head and reused in the case pattern {ok, Handle}, turning what looks like a binding into an equality test against next_handle. The duplicate check therefore only fired if the previously assigned handle happened to equal the current next_handle counter, which is essentially never true.
Fix by removing next_handle from the function head pattern and reading it with S#state.next_handle inside the error branch, so that Handle in {ok, Handle} is always a fresh binding that matches any existing handle.
In Erlang a function body returns only its last expression. The comma between the two enb_filter_by_sub_field/2 calls caused the result of the addr check to be silently discarded, so the filter only tested the port. Replace the comma with andalso to require both to match.
s1ap_proxy: fix discarded result of handle_ies/3 in HO REQ ACK handler
The call processing the optional E-RABFailedToSetupListHOReqAck IE did not pattern-match its return value, causing any errors returned by handle_ies/3 to be silently swallowed. Bind the result to {_, S2} to make the intent explicit.
The watchdog process is spawned bare (no link/monitor), so if it crashes its failure goes unnoticed, and if pfcp_peer restarts the orphaned watchdog may fire a stale cast at the new process.
The watchdog process exists solely to send a delayed message to pfcp_peer on timeout, and to be cancelled (by receiving heartbeat_response) when the response arrives. That's exactly what erlang:start_timer/3 does natively - no separate process needed.
enb_registry: fix pattern match in handle_info 'DOWN' handler
PIDs maps pid() => enb_handle() (an integer), so maps:find/2 returns {ok, SomeHandle}. The old pattern {ok, Pid} tried to match an integer against the already-bound pid() variable, which never succeeds.
This handler is only called on abnormal termination (process crash), so the broken match caused the registry entry to never be cleaned up in that case. Fix by using a fresh {ok, Handle} binding and removing the now-redundant follow-up maps:get/2 call.
Two comments copy-pasted from session_establish were left saying "ESTABLISH Req" in session_modify and session_delete handlers where "MODIFY Req" and "DELETE Req" are correct respectively. Also fix typo "unieue" -> "unique" in init/1.
PFCP sequence numbers are 24-bit (3GPP TS 29.244 section 7.2.2.2), but the counter was incremented without wrapping, eventually exceeding the type spec after 16,777,215 requests. Introduce PFCP_SEQ_NR_MAX and use it in the type spec and in the increment expression.
pfcp_peer: replace deprecated erlang:timestamp/0 with os:system_time/1
erlang:timestamp/0 is deprecated since OTP 18. os:system_time(second) is the modern replacement and yields the result directly in seconds, removing the need to reassemble the {MegaSec, Sec, _} tuple.