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