The enb_proxy now captures the local address and port of the S1GW-MME SCTP connection (mme_saddr/mme_sport) at comm_up and includes them in conn_info(). Expose this info through the REST API (EnbItem schema), show it as a new column/row in the CLI (enb_list/enb_info).
The full enb_list table with all address columns is too wide to fit on a page and does not render well in PDF. Collapse the address columns with '...'; add a note that they are omitted for readability.
enb_{proxy,registry}: signal MME conn info on SCTP comm_up
Previously, mme_aid/mme_saddr/mme_sport were only signalled to the enb_registry once the S1 Setup procedure completed. This meant the REST API could not show MME connection details for eNBs stuck in wait_s1setup_rsp state (e.g. due to a slow or retrying MME).
Add notify_mme_comm_up/2, called at SCTP comm_up, which stores the full conn_info (including mme_aid, mme_saddr, mme_sport) in the registry as soon as the SCTP connection is established.
notify_mme_connected/2 is simplified to notify_mme_connected/1: it now only flips the state to 'connected', since conn_info is already stored.
enb_proxy: split conn_info() into mme_conn_info() and proxy_info()
The old conn_info() conflated two distinct concerns: the MME SCTP connection info stored in enb_registry (aid, saddr, sport) and the broader operational state used for introspection (handler pid, enb connection info, etc.). Mixing them forced enb_registry to hold a handler pid it has no business knowing about, and required rest_server to extract that pid just to reach s1ap_proxy for E-RAB listing.
Split into two distinct types:
* mme_conn_info() - pure MME SCTP connection info (aid, saddr, sport), stored in the enb_registry and signalled via notify_mme_comm_up/2. The `mme_` prefix is dropped from field names as the type name provides the context.
* proxy_info() - richer operational snapshot (handler, enb_handle, enb_conn_info, mme_conn_info, genb_id_str, mme_info), returned by fetch_info/1 for introspection/debugging purposes.
Additionally:
* Add fetch_erab_list/1 to enb_proxy, delegating internally to s1ap_proxy:fetch_erab_list/1 via the cached handler pid. This allows the rest_server to obtain a list of E-RAB without having to obtain pid of the s1ap_proxy and interact with it.
* Remove separate enb_aid/mme_aid/mme_saddr/mme_sport state fields; enb_aid is now read directly from enb_conn_info, and the MME fields are grouped in mme_conn_info.
As a bonus, `tried_mmes` now only serves its actual purpose - tracking which MMEs have already been tried for selection filtering - rather than being abused as a way to retrieve the current MME name.
rest_server: fix TOC/TOU race when listing/fetching E-RABs
The list of E-RAB FSM pids is a snapshot taken at one point in time. By the time we interrogate each erab_fsm process individually, any of them may have already terminated (e.g. bearer released mid-request). The current code fails to generate a response if this happens.
* fetch_erab_info/1: add a pid() clause that wraps erab_list_item/1 in a try/catch, returning 'error' if the process is gone.
* fetch_erab_info/1: catch both exit forms ** `{noproc, _}` raised by gen_statem:call/2 on a monitored pid, and ** the bare noproc atom for other code paths.
* fetch_erab_list/1: switch from lists:map to lists:filtermap and call fetch_erab_info/1 per E-RAB, silently dropping any that died between the snapshot and the per-process interrogation.
The new version is using ETS instead of dict for counter lookups. This significantly reduces performance impact when multiple eNBs are registered, since the ETS provides O(1) average-case hash lookups and in-place mutation.
config/sys.config: increase StatsD reporter interval to 10s
When running ttcn3-s1gw-test locally, I noticed OsmoS1GW consuming 30-40% of a CPU core while idle (not serving any eNBs). Profiling with etop pointed to `exometer_report_statsd` as the culprit: with ~1720 metrics registered, it was sending ~1720 datagrams per second.
A 1-second reporting interval causes noticeable CPU overhead as the number of active metrics grows. With per-eNB and per-MME counters, the metric set scales with the number of connected eNBs/MMEs.
Increase the default StatsD reporting interval from 1s to 10s to reduce that overhead. Update the documentation accordingly.
When an MME is selected from the pool, pass the mme_registry:mme_info() to the enb_registry via notify_mme_connecting/2 (replacing /1). This makes all MME configuration details (name, address, port, TAC list) available to consumers such as the REST server, without having to look them up separately from the mme_registry.
enb_proxy: obtain sctp_client sockopts from the env directly
Instead of passing the MmeConnCfg (sctp_client:cfg()) all the way from osmo_s1gw_sup through sctp_server (as priv) into enb_proxy (as state), read the sctp_client configuration in-place via osmo_s1gw:get_env/2 when it is actually needed (connecting/enter).
This works because osmo_s1gw_sup already normalizes and writes back the complete sctp_client config to the application env (set_env/2) before starting the supervision tree.
As a result, mme_conn_cfg is removed from enb_proxy's state record, start_link/2 no longer uses its Priv argument, and server_cfg/1 is simplified to server_cfg/0.
This endpoint returns the effective runtime configuration that OsmoS1GW is currently using, with all defaults applied. This reflects the values read via `osmo_s1gw:get_env/2` at startup.
Add support for selecting an MME or eNB by remote address and port in the REST API and CLI. The selector format is `addr:IP:PORT` for MMEs and `enb-conn:IP:PORT` for eNBs, where IP can be an IPv4 or IPv6 address. The colon is used as the address/port separator.
For each eNB connection, include the name of the MME that was selected from the pool. Update the OpenAPI spec, CLI (enb_list/enb_info tables), and user manual accordingly.
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_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.
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.
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.
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.
enb_registry: call enb_metrics_register/1 from a proper place
enb_metrics_register/1 requires the Global-eNB-ID to register per-eNB metrics. Calling it for every event, which may or may not contain the Global-eNB-ID, is suboptimal.
Instead, invoke it from the enb_handle_event/2 clause that handles the {s1setup, GENBId} event, where the Global-Enb-ID is guaranteed to be available.
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.
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.
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