%% Copyright (C) 2024-2025 by sysmocom - s.f.m.c. GmbH %% Author: Vadim Yanitskiy %% %% All Rights Reserved %% %% SPDX-License-Identifier: AGPL-3.0-or-later %% %% This program is free software; you can redistribute it and/or modify %% it under the terms of the GNU Affero General Public License as %% published by the Free Software Foundation; either version 3 of the %% License, or (at your option) any later version. %% %% This program is distributed in the hope that it will be useful, %% but WITHOUT ANY WARRANTY; without even the implied warranty of %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %% GNU General Public License for more details. %% %% You should have received a copy of the GNU Affero General Public License %% along with this program. If not, see . %% %% Additional Permission under GNU AGPL version 3 section 7: %% %% If you modify this Program, or any covered work, by linking or %% combining it with runtime libraries of Erlang/OTP as released by %% Ericsson on https://www.erlang.org (or a modified version of these %% libraries), containing parts covered by the terms of the Erlang Public %% License (https://www.erlang.org/EPLICENSE), the licensors of this %% Program grant you additional permission to convey the resulting work %% without the need to license the runtime libraries of Erlang/OTP under %% the GNU Affero General Public License. Corresponding Source for a %% non-source form of such a combination shall include the source code %% for the parts of the runtime libraries of Erlang/OTP used as well as %% that of the covered work. -module(s1ap_proxy). -behaviour(gen_server). -export([init/1, handle_info/2, handle_call/3, handle_cast/2, terminate/2]). -export([start_link/1, process_pdu/2, fetch_erab/2, fetch_erab_list/1, shutdown/1]). -include_lib("kernel/include/logger.hrl"). -include("s1gw_metrics.hrl"). -include("S1AP-PDU-Descriptions.hrl"). -include("S1AP-PDU-Contents.hrl"). -include("S1AP-Containers.hrl"). -include("S1AP-Constants.hrl"). -include("S1AP-IEs.hrl"). -type s1ap_pdu() :: {initiatingMessage, #'InitiatingMessage'{}} | {successfulOutcome, #'SuccessfulOutcome'{}} | {unsuccessfulOutcome, #'UnsuccessfulOutcome'{}}. -type s1ap_ie_id() :: non_neg_integer(). -type s1ap_ie_val() :: tuple(). -type mme_ue_id() :: 0..16#ffffffff. -type enb_ue_id() :: 0..16#ffffff. -type erab_id() :: 0..16#ff. -type erab_uid() :: {mme_ue_id(), erab_id()}. -type plmn_id() :: {MCC :: nonempty_string(), MNC :: nonempty_string()}. -record(proxy_state, {conn_info :: sctp_server:conn_info(), erabs :: dict:dict(K :: erab_uid(), V :: pid()), enb_id :: undefined | non_neg_integer(), plmn_id :: undefined | plmn_id(), genb_id_str :: undefined | string(), mme_ue_id :: undefined | mme_ue_id(), enb_ue_id :: undefined | enb_ue_id(), erab_id :: undefined | erab_id(), path :: [s1ap_ie_id()], enb_uptime :: pid() }). -type proxy_state() :: #proxy_state{}. -type proxy_action() :: forward | reply | drop. -export_type([proxy_action/0]). %% ------------------------------------------------------------------ %% public API %% ------------------------------------------------------------------ -spec start_link(ConnInfo) -> Result when ConnInfo :: sctp_server:conn_info(), Result :: gen_server:start_ret(). start_link(ConnInfo) -> gen_server:start_link(?MODULE, [ConnInfo], []). -type process_pdu_result() :: {proxy_action(), binary()}. -spec process_pdu(pid(), binary()) -> process_pdu_result(). process_pdu(Pid, PDU) -> gen_server:call(Pid, {?FUNCTION_NAME, PDU}). %% Fetch a single E-RAB from the registry (by UID) -spec fetch_erab(pid(), erab_uid()) -> {ok, pid()} | error. fetch_erab(Pid, UID) -> gen_server:call(Pid, {?FUNCTION_NAME, UID}). %% Fetch all E-RABs from the registry in form of a list -spec fetch_erab_list(pid()) -> [{erab_uid(), pid()}]. fetch_erab_list(Pid) -> gen_server:call(Pid, ?FUNCTION_NAME). -spec shutdown(pid()) -> ok. shutdown(Pid) -> gen_server:stop(Pid). %% ------------------------------------------------------------------ %% gen_server API %% ------------------------------------------------------------------ init([ConnInfo]) -> process_flag(trap_exit, true), {ok, #proxy_state{enb_uptime = enb_uptime:start_link(), conn_info = ConnInfo, erabs = dict:new(), path = []}}. handle_call({process_pdu, OrigData}, _From, #proxy_state{} = S0) -> {Reply, S1} = handle_pdu_bin(OrigData, S0), {reply, Reply, S1}; handle_call({fetch_erab, UID}, _From, #proxy_state{erabs = ERABs} = S) -> {reply, dict:find(UID, ERABs), S}; handle_call(fetch_erab_list, _From, #proxy_state{erabs = ERABs} = S) -> {reply, dict:to_list(ERABs), S}; handle_call(Info, From, #proxy_state{} = S) -> ?LOG_ERROR("unknown ~p() from ~p: ~p", [?FUNCTION_NAME, From, Info]), {reply, {error, not_implemented}, S}. handle_cast(Info, #proxy_state{} = S) -> ?LOG_ERROR("unknown ~p(): ~p", [?FUNCTION_NAME, Info]), {noreply, S}. handle_info({'EXIT', Pid, Reason}, #proxy_state{erabs = ERABs} = S) -> ?LOG_DEBUG("Child process ~p terminated with reason ~p", [Pid, Reason]), Fun = fun(_Key, Val) -> Val =/= Pid end, {noreply, S#proxy_state{erabs = dict:filter(Fun, ERABs)}}; handle_info(Info, #proxy_state{} = S) -> ?LOG_ERROR("unknown ~p(): ~p", [?FUNCTION_NAME, Info]), {noreply, S}. terminate(Reason, #proxy_state{} = S) -> enb_uptime:shutdown(S#proxy_state.enb_uptime), ?LOG_NOTICE("Terminating, reason ~p", [Reason]), ok. %% ------------------------------------------------------------------ %% private API %% ------------------------------------------------------------------ -spec set_logging_prefix(string()) -> ok. set_logging_prefix(Prefix) when is_list(Prefix) -> logger:set_process_metadata(#{prefix => Prefix}). -spec erab_uid(erab_id(), proxy_state()) -> erab_uid(). erab_uid(ERABId, #proxy_state{mme_ue_id = MmeUeId}) -> {MmeUeId, ERABId}. -spec erab_for_each(MMEUeId, Fun, ERABs) -> ok when MMEUeId :: mme_ue_id(), Fun :: fun((pid()) -> term()), ERABs :: [{erab_uid(), pid()}]. erab_for_each(MMEUEId, Fun, [{{MMEUEId, _}, Pid} | ERABs]) -> Fun(Pid), %% it's a match! erab_for_each(MMEUEId, Fun, ERABs); erab_for_each(MMEUeId, Fun, [_ERAB | ERABs]) -> erab_for_each(MMEUeId, Fun, ERABs); erab_for_each(_MMEUeId, _Fun, []) -> ok. %% Parse PLMN-ID as per 3GPP TS 24.008, Figure 10.5.13 %% | MCC digit 2 | MCC digit 1 | octet 1 %% | MNC digit 3 | MCC digit 3 | octet 2 %% | MNC digit 2 | MNC digit 1 | octet 3 -spec parse_plmn_id(<< _:24 >>) -> plmn_id(). parse_plmn_id(<< MCC2:4, MCC1:4, MNC3:4, MCC3:4, MNC2:4, MNC1:4 >>) -> MCC = parse_mcc_mnc(MCC1, MCC2, MCC3), MNC = parse_mcc_mnc(MNC1, MNC2, MNC3), {MCC, MNC}. -define(UNHEX(H), H + 48). parse_mcc_mnc(D1, D2, 16#f) -> [?UNHEX(D1), ?UNHEX(D2)]; parse_mcc_mnc(D1, D2, D3) -> [?UNHEX(D1), ?UNHEX(D2), ?UNHEX(D3)]. -spec parse_enb_id(tuple()) -> non_neg_integer(). parse_enb_id({'macroENB-ID', << ID:20 >>}) -> ID; parse_enb_id({'homeENB-ID', << ID:28 >>}) -> ID; parse_enb_id({'short-macroENB-ID', << ID:18 >>}) -> ID; parse_enb_id({'long-macroENB-ID', << ID:21 >>}) -> ID. -spec genb_id_str(proxy_state()) -> string(). genb_id_str(#proxy_state{plmn_id = {MCC, MNC}, enb_id = ENBId}) -> MCC ++ "-" ++ MNC ++ "-" ++ integer_to_list(ENBId). %% register a single per-eNB counter -spec ctr_reg(C, GlobalENBId) -> C when C :: [ctr | _], GlobalENBId :: string(). ctr_reg([ctr, s1ap, proxy | _] = C0, GlobalENBId) -> C1 = s1gw_metrics:enb_metric(C0, GlobalENBId), %% counter may already exist, so catch exceptions here catch exometer:new(C1, counter), C0; ctr_reg(C0, _GlobalENBId) -> C0. %% register all per-eNB counters -spec ctr_reg_all(string()) -> list(). ctr_reg_all(GlobalENBId) -> Ctrs = s1gw_metrics:ctr_list(), lists:map(fun(Name) -> ctr_reg(Name, GlobalENBId) end, Ctrs). %% increment the global and/or per-eNB counters -spec ctr_inc(C0, S) -> term() when C0 :: [ctr | _], S :: proxy_state(). ctr_inc([ctr | _] = C0, S) -> case S#proxy_state.genb_id_str of undefined -> %% increment the global counter only s1gw_metrics:ctr_inc(C0); GlobalENBId -> %% increment both the global and per-eNB counters C1 = s1gw_metrics:enb_metric(C0, GlobalENBId), s1gw_metrics:ctr_inc(C0), s1gw_metrics:ctr_inc(C1) end. -spec tla_str(binary()) -> string(). tla_str(TLA0) -> TLA1 = list_to_tuple(binary_to_list(TLA0)), inet:ntoa(TLA1). %% Encode an S1AP PDU -spec encode_pdu(s1ap_pdu()) -> {ok, binary()} | {error, {asn1, tuple()}}. encode_pdu(Pdu) -> 'S1AP-PDU-Descriptions':encode('S1AP-PDU', Pdu). %% Decode an S1AP PDU -spec decode_pdu(binary()) -> {ok, s1ap_pdu()} | {error, {asn1, tuple()}}. decode_pdu(Data) -> 'S1AP-PDU-Descriptions':decode('S1AP-PDU', Data). %% Process an S1AP PDU (binary) -spec handle_pdu_bin(binary(), proxy_state()) -> {process_pdu_result(), proxy_state()}. handle_pdu_bin(OrigData, S0) -> ctr_inc(?S1GW_CTR_S1AP_PROXY_IN_PKT_ALL, S0), try decode_pdu(OrigData) of {ok, PDU} -> ?LOG_DEBUG("Rx S1AP PDU: ~p", [PDU]), try handle_pdu(PDU, S0) of {{Action, NewPDU}, S1} -> {ok, NewData} = encode_pdu(NewPDU), ?LOG_DEBUG("Tx (~p) S1AP PDU: ~p", [Action, NewPDU]), case Action of forward -> ctr_inc(?S1GW_CTR_S1AP_PROXY_OUT_PKT_FWD_ALL, S1), ctr_inc(?S1GW_CTR_S1AP_PROXY_OUT_PKT_FWD_PROC, S1); reply -> ctr_inc(?S1GW_CTR_S1AP_PROXY_OUT_PKT_REPLY_ALL, S1) end, {{Action, NewData}, S1}; {forward, S1} -> ?LOG_DEBUG("Tx (forward) S1AP PDU unmodified"), ctr_inc(?S1GW_CTR_S1AP_PROXY_OUT_PKT_FWD_ALL, S1), ctr_inc(?S1GW_CTR_S1AP_PROXY_OUT_PKT_FWD_UNMODIFIED, S1), {{forward, OrigData}, S1}; {drop, S1} -> ?LOG_NOTICE("Drop received S1AP PDU: ~p", [PDU]), ctr_inc(?S1GW_CTR_S1AP_PROXY_IN_PKT_DROP_ALL, S1), {{drop, OrigData}, S1} catch Exception:Reason:StackTrace -> ?LOG_ERROR("An exception occurred: ~p, ~p, ~p", [Exception, Reason, StackTrace]), ctr_inc(?S1GW_CTR_S1AP_PROXY_EXCEPTION, S0), ctr_inc(?S1GW_CTR_S1AP_PROXY_IN_PKT_PROC_ERROR, S0), ctr_inc(?S1GW_CTR_S1AP_PROXY_IN_PKT_DROP_ALL, S0), {{drop, OrigData}, S0} end; {error, {asn1, Error}} -> ?LOG_ERROR("S1AP PDU decoding failed: ~p", [Error]), ctr_inc(?S1GW_CTR_S1AP_PROXY_IN_PKT_DECODE_ERROR, S0), ctr_inc(?S1GW_CTR_S1AP_PROXY_IN_PKT_DROP_ALL, S0), {{drop, OrigData}, S0} catch Exception:Reason:StackTrace -> ?LOG_ERROR("An exception occurred: ~p, ~p, ~p", [Exception, Reason, StackTrace]), ctr_inc(?S1GW_CTR_S1AP_PROXY_EXCEPTION, S0), ctr_inc(?S1GW_CTR_S1AP_PROXY_IN_PKT_DECODE_ERROR, S0), ctr_inc(?S1GW_CTR_S1AP_PROXY_IN_PKT_DROP_ALL, S0), {{drop, OrigData}, S0} end. %% Process an S1AP PDU (decoded) -spec handle_pdu(PDU, S0) -> {Result, S1} when PDU :: s1ap_pdu(), S0 :: proxy_state(), S1 :: proxy_state(), Result :: {forward | reply, s1ap_pdu()} | forward | drop. %% 9.1.8.4 S1 SETUP REQUEST handle_pdu({initiatingMessage, #'InitiatingMessage'{procedureCode = ?'id-S1Setup', value = C0}}, S0) -> ?LOG_DEBUG("Processing S1 SETUP REQUEST"), %% there's nothing to patch in this PDU, so we forward it as-is %% TODO: check result of handle_ies(), inc. ?S1GW_CTR_S1AP_PROXY_IN_PKT_PROC_ERROR {_, S1} = handle_ies(?'id-Global-ENB-ID', C0#'S1SetupRequest'.protocolIEs, S0), {forward, S1}; %% 9.1.8.5 S1 SETUP RESPONSE handle_pdu({successfulOutcome, #'SuccessfulOutcome'{procedureCode = ?'id-S1Setup'}}, S) -> ?LOG_DEBUG("Processing S1 SETUP RESPONSE"), gtpu_kpi_enb_register(S), enb_uptime:genb_id_ind(S#proxy_state.enb_uptime, S#proxy_state.genb_id_str), %% there's nothing to patch in this PDU, so we forward it as-is {forward, S}; %% 9.1.3.1 E-RAB SETUP REQUEST handle_pdu({Outcome = initiatingMessage, #'InitiatingMessage'{procedureCode = ?'id-E-RABSetup', value = C0} = Msg}, S0) -> ?LOG_DEBUG("Processing E-RAB SETUP REQUEST"), ctr_inc(?S1GW_CTR_S1AP_PROXY_IN_PKT_ERAB_SETUP_REQ, S0), case handle_ies(?'id-E-RABToBeSetupListBearerSUReq', C0#'E-RABSetupRequest'.protocolIEs, S0) of {{ok, IEs}, S1} -> C1 = C0#'E-RABSetupRequest'{protocolIEs = IEs}, PDU = {Outcome, Msg#'InitiatingMessage'{value = C1}}, {{forward, PDU}, S1}; %% forward patched PDU {{error, Reason}, S1} -> ?LOG_NOTICE("Failed to process E-RAB SETUP REQUEST: ~p", [Reason]), ctr_inc(?S1GW_CTR_S1AP_PROXY_IN_PKT_PROC_ERROR, S1), PDU = build_erab_setup_response_failure(S1), ctr_inc(?S1GW_CTR_S1AP_PROXY_OUT_PKT_REPLY_ERAB_SETUP_RSP, S1), {{reply, PDU}, S1} %% reply PDU back to sender end; %% 9.1.3.2 E-RAB SETUP RESPONSE handle_pdu({Outcome = successfulOutcome, #'SuccessfulOutcome'{procedureCode = ?'id-E-RABSetup', value = C0} = Msg}, S0) -> ?LOG_DEBUG("Processing E-RAB SETUP RESPONSE"), ctr_inc(?S1GW_CTR_S1AP_PROXY_IN_PKT_ERAB_SETUP_RSP, S0), case handle_ies(?'id-E-RABSetupListBearerSURes', C0#'E-RABSetupResponse'.protocolIEs, S0) of {{ok, IEs}, S1} -> C1 = C0#'E-RABSetupResponse'{protocolIEs = IEs}, PDU = {Outcome, Msg#'SuccessfulOutcome'{value = C1}}, {{forward, PDU}, S1}; %% forward patched PDU {{error, Reason}, S1} -> ?LOG_NOTICE("Failed to process E-RAB SETUP RESPONSE: ~p", [Reason]), ctr_inc(?S1GW_CTR_S1AP_PROXY_IN_PKT_PROC_ERROR, S1), {drop, S1} %% drop this PDU end; %% 9.1.3.3 E-RAB MODIFY REQUEST handle_pdu({Outcome = initiatingMessage, #'InitiatingMessage'{procedureCode = ?'id-E-RABModify', value = C0} = Msg}, S0) -> ?LOG_DEBUG("Processing E-RAB MODIFY REQUEST"), ctr_inc(?S1GW_CTR_S1AP_PROXY_IN_PKT_ERAB_MODIFY_REQ, S0), case handle_ies(?'id-E-RABToBeModifiedListBearerModReq', C0#'E-RABModifyRequest'.protocolIEs, S0) of {{ok, IEs}, S1} -> C1 = C0#'E-RABModifyRequest'{protocolIEs = IEs}, PDU = {Outcome, Msg#'InitiatingMessage'{value = C1}}, {{forward, PDU}, S1}; %% forward patched PDU {{error, Reason}, S1} -> ?LOG_NOTICE("Failed to process E-RAB MODIFY REQUEST: ~p", [Reason]), ctr_inc(?S1GW_CTR_S1AP_PROXY_IN_PKT_PROC_ERROR, S1), {drop, S1} %% drop this PDU end; %% 9.1.3.4 E-RAB MODIFY RESPONSE handle_pdu({successfulOutcome, #'SuccessfulOutcome'{procedureCode = ?'id-E-RABModify', value = C0}}, S0) -> ?LOG_DEBUG("Processing E-RAB MODIFY RESPONSE"), ctr_inc(?S1GW_CTR_S1AP_PROXY_IN_PKT_ERAB_MODIFY_RSP, S0), %% there's nothing to patch in this PDU, so we forward it as-is %% TODO: check result of handle_ies(), inc. ?S1GW_CTR_S1AP_PROXY_IN_PKT_PROC_ERROR {_, S1} = handle_ies(?'id-E-RABModifyListBearerModRes', C0#'E-RABModifyResponse'.protocolIEs, S0), {_, S2} = handle_ies(?'id-E-RABFailedToModifyList', C0#'E-RABModifyResponse'.protocolIEs, S1), {forward, S2}; %% 9.1.3.5 E-RAB RELEASE COMMAND handle_pdu({initiatingMessage, #'InitiatingMessage'{procedureCode = ?'id-E-RABRelease', value = C0}}, S0) -> ?LOG_DEBUG("Processing E-RAB RELEASE COMMAND"), ctr_inc(?S1GW_CTR_S1AP_PROXY_IN_PKT_ERAB_RELEASE_CMD, S0), %% there's nothing to patch in this PDU, so we forward it as-is case handle_ies(?'id-E-RABToBeReleasedList', C0#'E-RABReleaseCommand'.protocolIEs, S0) of {{ok, _}, S1} -> {forward, S1}; {{error, Reason}, S1} -> ?LOG_NOTICE("Failed to process E-RAB RELEASE COMMAND: ~p", [Reason]), ctr_inc(?S1GW_CTR_S1AP_PROXY_IN_PKT_PROC_ERROR, S1), {forward, S1} end; %% 9.1.3.6 E-RAB RELEASE RESPONSE handle_pdu({successfulOutcome, #'SuccessfulOutcome'{procedureCode = ?'id-E-RABRelease', value = C0}}, S0) -> ?LOG_DEBUG("Processing E-RAB RELEASE RESPONSE"), ctr_inc(?S1GW_CTR_S1AP_PROXY_IN_PKT_ERAB_RELEASE_RSP, S0), %% there's nothing to patch in this PDU, so we forward it as-is case handle_ies(?'id-E-RABReleaseListBearerRelComp', C0#'E-RABReleaseResponse'.protocolIEs, S0) of {{ok, _}, S1} -> {forward, S1}; {{error, Reason}, S1} -> ?LOG_NOTICE("Failed to process E-RAB RELEASE RESPONSE: ~p", [Reason]), ctr_inc(?S1GW_CTR_S1AP_PROXY_IN_PKT_PROC_ERROR, S1), {forward, S1} end; %% 9.1.3.7 E-RAB RELEASE INDICATION handle_pdu({initiatingMessage, #'InitiatingMessage'{procedureCode = ?'id-E-RABReleaseIndication', value = C0}}, S0) -> ?LOG_DEBUG("Processing E-RAB RELEASE INDICATION"), ctr_inc(?S1GW_CTR_S1AP_PROXY_IN_PKT_ERAB_RELEASE_IND, S0), %% there's nothing to patch in this PDU, so we forward it as-is case handle_ies(?'id-E-RABReleasedList', C0#'E-RABReleaseIndication'.protocolIEs, S0) of {{ok, _}, S1} -> {forward, S1}; {{error, Reason}, S1} -> ?LOG_NOTICE("Failed to process E-RAB RELEASE INDICATION: ~p", [Reason]), ctr_inc(?S1GW_CTR_S1AP_PROXY_IN_PKT_PROC_ERROR, S1), {forward, S1} end; %% 9.1.3.8 E-RAB MODIFICATION INDICATION handle_pdu({Outcome = initiatingMessage, #'InitiatingMessage'{procedureCode = ?'id-E-RABModificationIndication', value = C0} = Msg}, S0) -> ?LOG_DEBUG("Processing E-RAB MODIFICATION INDICATION"), ctr_inc(?S1GW_CTR_S1AP_PROXY_IN_PKT_ERAB_MOD_IND, S0), IEs0 = C0#'E-RABModificationIndication'.protocolIEs, %% E-RAB to be Modified List %% TODO: handle {error, Reason} {{ok, IEs1}, S1} = handle_ies(?'id-E-RABToBeModifiedListBearerModInd', IEs0, S0), %% E-RAB not to be Modified List %% TODO: handle {error, Reason} {{ok, IEs2}, S2} = handle_ies(?'id-E-RABNotToBeModifiedListBearerModInd', IEs1, S1), C1 = C0#'E-RABModificationIndication'{protocolIEs = IEs2}, PDU = {Outcome, Msg#'InitiatingMessage'{value = C1}}, {{forward, PDU}, S2}; %% 9.1.3.9 E-RAB MODIFICATION CONFIRM handle_pdu({successfulOutcome, #'SuccessfulOutcome'{procedureCode = ?'id-E-RABModificationIndication', value = C0}}, S0) -> ?LOG_DEBUG("Processing E-RAB MODIFICATION CONFIRM"), ctr_inc(?S1GW_CTR_S1AP_PROXY_IN_PKT_ERAB_MOD_CNF, S0), IEs = C0#'E-RABModificationConfirm'.protocolIEs, %% E-RAB Modify List %% TODO: handle {error, Reason} {_, S1} = handle_ies(?'id-E-RABModifyListBearerModConf', IEs, S0), %% E-RAB Failed to Modify List %% TODO: handle {error, Reason} {_, S2} = handle_ies(?'id-E-RABFailedToModifyListBearerModConf', IEs, S1), %% E-RAB To Be Released List %% TODO: handle {error, Reason} {_, S3} = handle_ies(?'id-E-RABToBeReleasedListBearerModConf', IEs, S2), %% there's nothing to patch in this PDU, so we forward it as-is {forward, S3}; %% 9.1.4.1 INITIAL CONTEXT SETUP REQUEST handle_pdu({Outcome = initiatingMessage, #'InitiatingMessage'{procedureCode = ?'id-InitialContextSetup', value = C0} = Msg}, S0) -> ?LOG_DEBUG("Processing INITIAL CONTEXT SETUP REQUEST"), ctr_inc(?S1GW_CTR_S1AP_PROXY_IN_PKT_INIT_CTX_REQ, S0), case handle_ies(?'id-E-RABToBeSetupListCtxtSUReq', C0#'InitialContextSetupRequest'.protocolIEs, S0) of {{ok, IEs}, S1} -> C1 = C0#'InitialContextSetupRequest'{protocolIEs = IEs}, PDU = {Outcome, Msg#'InitiatingMessage'{value = C1}}, {{forward, PDU}, S1}; %% forward patched PDU {{error, Reason}, S1} -> ?LOG_NOTICE("Failed to process INITIAL CONTEXT SETUP REQUEST: ~p", [Reason]), ctr_inc(?S1GW_CTR_S1AP_PROXY_IN_PKT_PROC_ERROR, S1), {drop, S1} %% drop this PDU end; %% 9.1.4.3 INITIAL CONTEXT SETUP RESPONSE handle_pdu({Outcome = successfulOutcome, #'SuccessfulOutcome'{procedureCode = ?'id-InitialContextSetup', value = C0} = Msg}, S0) -> ?LOG_DEBUG("Processing INITIAL CONTEXT SETUP RESPONSE"), ctr_inc(?S1GW_CTR_S1AP_PROXY_IN_PKT_INIT_CTX_RSP, S0), %% TODO: handle optional E-RAB Failed to Setup List IE case handle_ies(?'id-E-RABSetupListCtxtSURes', C0#'InitialContextSetupResponse'.protocolIEs, S0) of {{ok, IEs}, S1} -> C1 = C0#'InitialContextSetupResponse'{protocolIEs = IEs}, PDU = {Outcome, Msg#'SuccessfulOutcome'{value = C1}}, {{forward, PDU}, S1}; %% forward patched PDU {{error, Reason}, S1} -> ?LOG_NOTICE("Failed to process INITIAL CONTEXT SETUP RESPONSE: ~p", [Reason]), ctr_inc(?S1GW_CTR_S1AP_PROXY_IN_PKT_PROC_ERROR, S1), {drop, S1} %% drop this PDU end; %% 9.1.4.5 UE CONTEXT RELEASE REQUEST handle_pdu({initiatingMessage, #'InitiatingMessage'{procedureCode = ?'id-UEContextReleaseRequest', value = #'UEContextReleaseRequest'{protocolIEs = IEs}}}, #proxy_state{erabs = ERABs} = S) -> ?LOG_DEBUG("Processing UE CONTEXT RELEASE REQUEST"), ctr_inc(?S1GW_CTR_S1AP_PROXY_IN_PKT_RELEASE_CTX_REQ, S), %% fetch MME-UE-S1AP-ID value (mandatory IE) #'ProtocolIE-Field'{id = ?'id-MME-UE-S1AP-ID', value = MMEUEId} = lists:nth(1, IEs), %% poke E-RAB FSMs with the matching MME-UE-S1AP-ID erab_for_each(MMEUEId, fun erab_fsm:erab_release_ind/1, dict:to_list(ERABs)), {forward, S}; %% forward as-is, there's nothing to patch %% 9.1.4.6 UE CONTEXT RELEASE COMMAND handle_pdu({initiatingMessage, #'InitiatingMessage'{procedureCode = ?'id-UEContextRelease', value = #'UEContextReleaseCommand'{protocolIEs = IEs}}}, #proxy_state{erabs = ERABs} = S) -> ?LOG_DEBUG("Processing UE CONTEXT RELEASE COMMAND"), ctr_inc(?S1GW_CTR_S1AP_PROXY_IN_PKT_RELEASE_CTX_CMD, S), %% fetch MME-UE-S1AP-ID value from UE-S1AP-IDs (mandatory IE) #'ProtocolIE-Field'{id = ?'id-UE-S1AP-IDs', value = S1APIDs} = lists:nth(1, IEs), MMEUEId = case S1APIDs of {'uE-S1AP-ID-pair', #'UE-S1AP-ID-pair'{'mME-UE-S1AP-ID' = Val}} -> Val; {'mME-UE-S1AP-ID', Val} -> Val end, %% poke E-RAB FSMs with the matching MME-UE-S1AP-ID erab_for_each(MMEUEId, fun erab_fsm:erab_release_cmd/1, dict:to_list(ERABs)), {forward, S}; %% forward as-is, there's nothing to patch %% 9.1.4.7 UE CONTEXT RELEASE COMPLETE handle_pdu({successfulOutcome, #'SuccessfulOutcome'{procedureCode = ?'id-UEContextRelease', value = #'UEContextReleaseComplete'{protocolIEs = IEs}}}, #proxy_state{erabs = ERABs} = S) -> ?LOG_DEBUG("Processing UE CONTEXT RELEASE COMPLETE"), ctr_inc(?S1GW_CTR_S1AP_PROXY_IN_PKT_RELEASE_CTX_COMPL, S), %% fetch MME-UE-S1AP-ID value (mandatory IE) #'ProtocolIE-Field'{id = ?'id-MME-UE-S1AP-ID', value = MMEUEId} = lists:nth(1, IEs), %% poke E-RAB FSMs with the matching MME-UE-S1AP-ID erab_for_each(MMEUEId, fun erab_fsm:erab_release_rsp/1, dict:to_list(ERABs)), {forward, S}; %% forward as-is, there's nothing to patch %% TODO: 9.1.5.2 HANDOVER COMMAND :: (O) UL/DL Transport Layer Address %% TODO: 9.1.5.4 HANDOVER REQUEST :: (M) Transport Layer Address %% TODO: 9.1.5.5 HANDOVER REQUEST ACKNOWLEDGE :: (M) Transport Layer Address, %% (O) UL/DL Transport Layer Address %% TODO: 9.1.5.8 PATH SWITCH REQUEST :: (M) Transport Layer Address %% TODO: 9.1.5.9 PATH SWITCH REQUEST ACKNOWLEDGE :: (M) Transport Layer Address %% Proxy all other messages unmodified handle_pdu(_PDU, S) -> {forward, S}. %% Handle a single IE (Information Element) -type handle_ie_result() :: {ok, s1ap_ie_val()} | {error, term()}. -spec handle_ie(Path, Content, State) -> Result when Path :: [s1ap_ie_id()], Content :: s1ap_ie_val(), State :: proxy_state(), Result :: {handle_ie_result(), proxy_state()}. handle_ie([?'id-Global-ENB-ID'], #'Global-ENB-ID'{'pLMNidentity' = PLMNId, 'eNB-ID' = ENBId} = C, S0) -> %% store PLMNId/ENBId S1 = S0#proxy_state{plmn_id = parse_plmn_id(PLMNId), enb_id = parse_enb_id(ENBId)}, ?LOG_INFO("Global-ENB-ID: PLMN-ID=~p, eNB-ID=~p", [S1#proxy_state.plmn_id, S1#proxy_state.enb_id]), %% use that as a context for logging GlobalENBId = genb_id_str(S1), set_logging_prefix("eNB " ++ GlobalENBId), %% register per-eNB metrics ctr_reg_all(GlobalENBId), %% increment per-eNB ?S1GW_CTR_S1AP_PROXY_IN_PKT_ALL, if needed %% this is needed to count S1 Setup Req correctly S2 = case S0#proxy_state.genb_id_str of GlobalENBId -> S1; _ -> Ctr = s1gw_metrics:enb_metric(?S1GW_CTR_S1AP_PROXY_IN_PKT_ALL, GlobalENBId), s1gw_metrics:ctr_inc(Ctr), S1#proxy_state{genb_id_str = GlobalENBId} end, {{ok, C}, S2}; %% E-RAB SETUP REQUEST related IEs handle_ie([?'id-E-RABToBeSetupListBearerSUReq'], C, S) -> %% This IE contains a list of BearerSUReq, so patch inner IEs handle_ies(?'id-E-RABToBeSetupItemBearerSUReq', C, S); handle_ie([?'id-E-RABToBeSetupItemBearerSUReq', ?'id-E-RABToBeSetupListBearerSUReq'], #'E-RABToBeSetupItemBearerSUReq'{'e-RAB-ID' = ERABId, 'transportLayerAddress' = TLA_In, 'gTP-TEID' = << TEID_In:32/big >>} = C0, S0) -> %% start and register an E-RAB FSM {Pid, S1} = erab_fsm_start_reg(ERABId, S0), case erab_fsm:erab_setup_req(Pid, {TEID_In, TLA_In}) of {ok, {TEID_Out, TLA_Out}} -> C1 = C0#'E-RABToBeSetupItemBearerSUReq'{'transportLayerAddress' = TLA_Out, 'gTP-TEID' = << TEID_Out:32/big >>}, {{ok, C1}, S1}; {error, Reason} -> {{error, Reason}, S1} end; %% E-RAB SETUP RESPONSE related IEs handle_ie([?'id-E-RABSetupListBearerSURes'], C, S) -> %% This IE contains a list of BearerSURes, so patch inner IEs handle_ies(?'id-E-RABSetupItemBearerSURes', C, S); handle_ie([?'id-E-RABSetupItemBearerSURes', ?'id-E-RABSetupListBearerSURes'], #'E-RABSetupItemBearerSURes'{'e-RAB-ID' = ERABId, 'transportLayerAddress' = TLA_In, 'gTP-TEID' = << TEID_In:32/big >>} = C0, S) -> %% indicate eNB's address to the gtpu_kpi module gtpu_kpi_enb_set_addr({s1ap, tla_str(TLA_In)}), %% poke E-RAB FSM case erab_fsm_find(ERABId, S) of {ok, Pid} -> case erab_fsm:erab_setup_rsp(Pid, {TEID_In, TLA_In}) of {ok, {TEID_Out, TLA_Out}} -> C1 = C0#'E-RABSetupItemBearerSURes'{'transportLayerAddress' = TLA_Out, 'gTP-TEID' = << TEID_Out:32/big >>}, {{ok, C1}, S}; {error, Reason} -> {{error, Reason}, S} end; error -> ?LOG_ERROR("E-RAB-ID ~p is not registered", [erab_uid(ERABId, S)]), {{error, erab_not_registered}, S} end; %% 9.1.3.3 E-RAB MODIFY REQUEST related IEs handle_ie([?'id-E-RABToBeModifiedListBearerModReq'], C, S) -> %% This IE contains a list of BearerModReq, so patch inner IEs handle_ies(?'id-E-RABToBeModifiedItemBearerModReq', C, S); handle_ie([?'id-E-RABToBeModifiedItemBearerModReq', ?'id-E-RABToBeModifiedListBearerModReq'], #'E-RABToBeModifiedItemBearerModReq'{'e-RAB-ID' = ERABId, 'iE-Extensions' = E0} = C0, S0) -> %% The IE-Extensions may optionally contain F-TEID, so patch inner IEs case E0 of %% no extensions means no F-TEID, so nothing to patch asn1_NOVALUE -> {{ok, C0}, S0}; _ -> case handle_ies(?'id-TransportInformation', E0, S0#proxy_state{erab_id = ERABId}) of {{ok, E1}, S1} -> C1 = C0#'E-RABToBeModifiedItemBearerModReq'{'iE-Extensions' = E1}, {{ok, C1}, S1}; Error -> Error end end; handle_ie([?'id-TransportInformation', ?'id-E-RABToBeModifiedItemBearerModReq', ?'id-E-RABToBeModifiedListBearerModReq'], #'TransportInformation'{'transportLayerAddress' = TLA_In, 'uL-GTP-TEID' = << TEID_In:32/big >>} = C0, S) -> %% poke E-RAB FSM case erab_fsm_find(S#proxy_state.erab_id, S) of {ok, Pid} -> case erab_fsm:erab_modify_req(Pid, {TEID_In, TLA_In}) of {ok, {TEID_Out, TLA_Out}} -> C1 = C0#'TransportInformation'{'transportLayerAddress' = TLA_Out, 'uL-GTP-TEID' = << TEID_Out:32/big >>}, {{ok, C1}, S}; {error, Reason} -> {{error, Reason}, S} end; error -> ?LOG_ERROR("E-RAB-ID ~p is not registered", [erab_uid(S#proxy_state.erab_id, S)]), {{error, erab_not_registered}, S} end; %% 9.1.3.4 E-RAB MODIFY RESPONSE related IEs handle_ie([?'id-E-RABModifyListBearerModRes'], C, S) -> %% This IE contains a list of BearerModRes, so patch inner IEs handle_ies(?'id-E-RABModifyItemBearerModRes', C, S); handle_ie([?'id-E-RABModifyItemBearerModRes', ?'id-E-RABModifyListBearerModRes'], #'E-RABModifyItemBearerModRes'{'e-RAB-ID' = ERABId} = C, S) -> %% poke E-RAB FSM case erab_fsm_find(ERABId, S) of {ok, Pid} -> ok = erab_fsm:erab_modify_rsp(Pid, ack), {{ok, C}, S}; error -> ?LOG_ERROR("E-RAB-ID ~p is not registered", [erab_uid(ERABId, S)]), {{error, erab_not_registered}, S} end; handle_ie([?'id-E-RABFailedToModifyList'], C, S) -> %% This IE contains a list of E-RABItem, so patch inner IEs handle_ies(?'id-E-RABItem', C, S); handle_ie([?'id-E-RABItem', ?'id-E-RABFailedToModifyList'], #'E-RABItem'{'e-RAB-ID' = ERABId} = C, S) -> %% poke E-RAB FSM case erab_fsm_find(ERABId, S) of {ok, Pid} -> ok = erab_fsm:erab_modify_rsp(Pid, nack), {{ok, C}, S}; error -> ?LOG_ERROR("E-RAB-ID ~p is not registered", [erab_uid(ERABId, S)]), {{error, erab_not_registered}, S} end; %% 9.1.3.5 E-RAB RELEASE COMMAND related IEs handle_ie([?'id-E-RABToBeReleasedList'], C, S) -> %% This IE contains a list of E-RABItem handle_ies(?'id-E-RABItem', C, S); handle_ie([?'id-E-RABItem', ?'id-E-RABToBeReleasedList'], #'E-RABItem'{'e-RAB-ID' = ERABId} = C, S) -> %% poke E-RAB FSM case erab_fsm_find(ERABId, S) of {ok, Pid} -> ok = erab_fsm:erab_release(Pid, cmd), {{ok, C}, S}; error -> ?LOG_ERROR("E-RAB ~p is not registered", [erab_uid(ERABId, S)]), {{error, erab_not_registered}, S} end; %% 9.1.3.6 E-RAB RELEASE RESPONSE related IEs handle_ie([?'id-E-RABReleaseListBearerRelComp'], C, S) -> %% This IE contains a list of E-RABReleaseItemBearerRelComp handle_ies(?'id-E-RABReleaseItemBearerRelComp', C, S); handle_ie([?'id-E-RABReleaseItemBearerRelComp', ?'id-E-RABReleaseListBearerRelComp'], #'E-RABReleaseItemBearerRelComp'{'e-RAB-ID' = ERABId} = C, S) -> %% poke E-RAB FSM case erab_fsm_find(ERABId, S) of {ok, Pid} -> ok = erab_fsm:erab_release_rsp(Pid), {{ok, C}, S}; error -> ?LOG_ERROR("E-RAB ~p is not registered", [erab_uid(ERABId, S)]), {{error, erab_not_registered}, S} end; %% 9.1.3.7 E-RAB RELEASE INDICATION related IEs handle_ie([?'id-E-RABReleasedList'], C, S) -> %% This IE contains a list of E-RABItem handle_ies(?'id-E-RABItem', C, S); handle_ie([?'id-E-RABItem', ?'id-E-RABReleasedList'], #'E-RABItem'{'e-RAB-ID' = ERABId} = C, S) -> %% poke E-RAB FSM case erab_fsm_find(ERABId, S) of {ok, Pid} -> ok = erab_fsm:erab_release(Pid, ind), {{ok, C}, S}; error -> ?LOG_ERROR("E-RAB ~p is not registered", [erab_uid(ERABId, S)]), {{error, erab_not_registered}, S} end; %% 9.1.3.8 E-RAB MODIFICATION INDICATION related IEs handle_ie([?'id-E-RABToBeModifiedListBearerModInd'], C, S) -> %% This IE contains a list of BearerModInd, so patch inner IEs handle_ies(?'id-E-RABToBeModifiedItemBearerModInd', C, S); handle_ie([?'id-E-RABToBeModifiedItemBearerModInd', ?'id-E-RABToBeModifiedListBearerModInd'], #'E-RABToBeModifiedItemBearerModInd'{'e-RAB-ID' = ERABId, 'transportLayerAddress' = TLA_In, 'dL-GTP-TEID' = << TEID_In:32/big >>} = C0, S) -> %% poke E-RAB FSM case erab_fsm_find(ERABId, S) of {ok, Pid} -> case erab_fsm:erab_modify_ind(Pid, {TEID_In, TLA_In}) of {ok, {TEID_Out, TLA_Out}} -> C1 = C0#'E-RABToBeModifiedItemBearerModInd'{'transportLayerAddress' = TLA_Out, 'dL-GTP-TEID' = << TEID_Out:32/big >>}, {{ok, C1}, S}; {error, Reason} -> {{error, Reason}, S} end; error -> ?LOG_ERROR("E-RAB-ID ~p is not registered", [erab_uid(ERABId, S)]), {{error, erab_not_registered}, S} end; handle_ie([?'id-E-RABNotToBeModifiedListBearerModInd'], C, S) -> %% This IE contains a list of BearerModInd, so patch inner IEs handle_ies(?'id-E-RABNotToBeModifiedItemBearerModInd', C, S); handle_ie([?'id-E-RABNotToBeModifiedItemBearerModInd', ?'id-E-RABNotToBeModifiedListBearerModInd'], #'E-RABNotToBeModifiedItemBearerModInd'{'e-RAB-ID' = ERABId} = C0, S) -> %% poke E-RAB FSM case erab_fsm_find(ERABId, S) of {ok, Pid} -> %% Ignore F-TEID in the original message, just replace it with C2U F-TEID Info = erab_fsm:fetch_info(Pid), case proplists:get_value(f_teid_c2u, Info) of {TEID, TLA} -> C1 = C0#'E-RABNotToBeModifiedItemBearerModInd'{'transportLayerAddress' = TLA, 'dL-GTP-TEID' = << TEID:32/big >>}, {{ok, C1}, S}; undefined -> ?LOG_ERROR("E-RAB-ID ~p :: missing C2U F-TEID", [erab_uid(ERABId, S)]), {{error, missing_f_teid}, S} end; error -> ?LOG_ERROR("E-RAB-ID ~p is not registered", [erab_uid(ERABId, S)]), {{error, erab_not_registered}, S} end; %% 9.1.3.9 E-RAB MODIFICATION CONFIRM related IEs handle_ie([?'id-E-RABModifyListBearerModConf'], C, S) -> %% This IE contains a list of BearerModConf, so patch inner IEs handle_ies(?'id-E-RABModifyItemBearerModConf', C, S); handle_ie([?'id-E-RABModifyItemBearerModConf', ?'id-E-RABModifyListBearerModConf'], #'E-RABModifyItemBearerModConf'{'e-RAB-ID' = ERABId} = C, S) -> %% poke E-RAB FSM case erab_fsm_find(ERABId, S) of {ok, Pid} -> ok = erab_fsm:erab_modify_cnf(Pid, ack), {{ok, C}, S}; error -> ?LOG_ERROR("E-RAB-ID ~p is not registered", [erab_uid(ERABId, S)]), {{error, erab_not_registered}, S} end; handle_ie([?'id-E-RABFailedToModifyListBearerModConf'], C, S) -> %% This IE contains a list of E-RABItem, so patch inner IEs handle_ies(?'id-E-RABItem', C, S); handle_ie([?'id-E-RABItem', ?'id-E-RABFailedToModifyListBearerModConf'], #'E-RABItem'{'e-RAB-ID' = ERABId} = C, S) -> %% poke E-RAB FSM case erab_fsm_find(ERABId, S) of {ok, Pid} -> ok = erab_fsm:erab_modify_cnf(Pid, nack), {{ok, C}, S}; error -> ?LOG_ERROR("E-RAB-ID ~p is not registered", [erab_uid(ERABId, S)]), {{error, erab_not_registered}, S} end; handle_ie([?'id-E-RABToBeReleasedListBearerModConf'], C, S) -> %% This IE contains a list of E-RABItem, so patch inner IEs handle_ies(?'id-E-RABItem', C, S); handle_ie([?'id-E-RABItem', ?'id-E-RABToBeReleasedListBearerModConf'], #'E-RABItem'{'e-RAB-ID' = ERABId} = C, S) -> %% poke E-RAB FSM case erab_fsm_find(ERABId, S) of {ok, Pid} -> ok = erab_fsm:erab_release_ind(Pid), {{ok, C}, S}; error -> ?LOG_ERROR("E-RAB-ID ~p is not registered", [erab_uid(ERABId, S)]), {{error, erab_not_registered}, S} end; %% INITIAL CONTEXT SETUP REQUEST related IEs handle_ie([?'id-E-RABToBeSetupListCtxtSUReq'], C, S) -> %% This IE contains a list of CtxtSUReq, so patch inner IEs handle_ies(?'id-E-RABToBeSetupItemCtxtSUReq', C, S); handle_ie([?'id-E-RABToBeSetupItemCtxtSUReq', ?'id-E-RABToBeSetupListCtxtSUReq'], #'E-RABToBeSetupItemCtxtSUReq'{'e-RAB-ID' = ERABId, 'transportLayerAddress' = TLA_In, 'gTP-TEID' = << TEID_In:32/big >>} = C0, S0) -> %% start and register an E-RAB FSM {Pid, S1} = erab_fsm_start_reg(ERABId, S0), case erab_fsm:erab_setup_req(Pid, {TEID_In, TLA_In}) of {ok, {TEID_Out, TLA_Out}} -> C1 = C0#'E-RABToBeSetupItemCtxtSUReq'{'transportLayerAddress' = TLA_Out, 'gTP-TEID' = << TEID_Out:32/big >>}, {{ok, C1}, S1}; {error, Reason} -> {{error, Reason}, S1} end; %% INITIAL CONTEXT SETUP RESPONSE related IEs handle_ie([?'id-E-RABSetupListCtxtSURes'], C, S) -> %% This IE contains a list of CtxtSURes, so patch inner IEs handle_ies(?'id-E-RABSetupItemCtxtSURes', C, S); handle_ie([?'id-E-RABSetupItemCtxtSURes', ?'id-E-RABSetupListCtxtSURes'], #'E-RABSetupItemCtxtSURes'{'e-RAB-ID' = ERABId, 'transportLayerAddress' = TLA_In, 'gTP-TEID' = << TEID_In:32/big >>} = C0, S) -> %% indicate eNB's address to the gtpu_kpi module gtpu_kpi_enb_set_addr({s1ap, tla_str(TLA_In)}), %% poke E-RAB FSM case erab_fsm_find(ERABId, S) of {ok, Pid} -> case erab_fsm:erab_setup_rsp(Pid, {TEID_In, TLA_In}) of {ok, {TEID_Out, TLA_Out}} -> C1 = C0#'E-RABSetupItemCtxtSURes'{'transportLayerAddress' = TLA_Out, 'gTP-TEID' = << TEID_Out:32/big >>}, {{ok, C1}, S}; {error, Reason} -> {{error, Reason}, S} end; error -> ?LOG_ERROR("E-RAB ~p is not registered", [erab_uid(ERABId, S)]), {{error, erab_not_registered}, S} end; %% Catch-all variant, which should not be called normally handle_ie(P, C, S) -> ?LOG_ERROR("[BUG] Unhandled S1AP IE: ~p, ~p", [P, C]), {{error, unhandled_ie}, S}. %% Iterate over the given list of 'ProtocolIE-Field' IEs, %% calling function handle_ie/3 for IEs matching the given IEI. %% Additionally look for {MME,eNB}-UE-S1AP-ID IEs and store their values. -type handle_ies_result() :: {ok, list()} | {error, term()}. -spec handle_ies(s1ap_ie_id(), list(), proxy_state()) -> {handle_ies_result(), proxy_state()}. handle_ies(IEI, IEs, #proxy_state{path = P} = S0) -> %% prepend IEI to the path and call handle_ies/4 {Result, S1} = handle_ies([], IEI, IEs, S0#proxy_state{path = [IEI | P]}), %% remove IEI from the path and return the result {Result, S1#proxy_state{path = P}}. handle_ies(Acc, IEI, [IE | IEs], #proxy_state{path = P} = S0) -> case IE of #'ProtocolIE-Field'{id = IEI, value = C0} -> case handle_ie(P, C0, S0) of {{ok, C1}, S1} -> NewIE = IE#'ProtocolIE-Field'{value = C1}, handle_ies([NewIE | Acc], IEI, IEs, S1); {{error, Reason}, S1} -> {{error, Reason}, S1} end; #'ProtocolExtensionField'{id = IEI, extensionValue = C0} -> case handle_ie(P, C0, S0) of {{ok, C1}, S1} -> NewIE = IE#'ProtocolExtensionField'{extensionValue = C1}, handle_ies([NewIE | Acc], IEI, IEs, S1); {{error, Reason}, S1} -> {{error, Reason}, S1} end; #'ProtocolIE-Field'{id = ?'id-MME-UE-S1AP-ID', value = Id} -> S1 = S0#proxy_state{mme_ue_id = Id}, handle_ies([IE | Acc], IEI, IEs, S1); #'ProtocolIE-Field'{id = ?'id-eNB-UE-S1AP-ID', value = Id} -> S1 = S0#proxy_state{enb_ue_id = Id}, handle_ies([IE | Acc], IEI, IEs, S1); _ -> handle_ies([IE | Acc], IEI, IEs, S0) end; handle_ies(Acc, _IEI, [], S) -> IEs = lists:reverse(Acc), {{ok, IEs}, S}. build_erab_setup_response_failure(#proxy_state{erabs = ERABs, mme_ue_id = MmeUeId, enb_ue_id = EnbUeId}) -> %% FIXME: Currently we respond with E-RAB-ID of the first E-RAB in the registry. %% Instead, we need to iterate over E-RABs in the REQUEST and reject them all. [{_, FirstERABid}|_] = dict:fetch_keys(ERABs), Cause = {transport, 'transport-resource-unavailable'}, ERABitem = #'E-RABItem'{'e-RAB-ID' = FirstERABid, cause = Cause}, ERABlist = [#'ProtocolIE-Field'{id = ?'id-E-RABItem', criticality = ignore, value = ERABitem}], IEs = [#'ProtocolIE-Field'{id = ?'id-MME-UE-S1AP-ID', criticality = ignore, value = MmeUeId}, #'ProtocolIE-Field'{id = ?'id-eNB-UE-S1AP-ID', criticality = ignore, value = EnbUeId}, #'ProtocolIE-Field'{id = ?'id-E-RABFailedToSetupListBearerSURes', criticality = ignore, value = ERABlist}], {successfulOutcome, #'SuccessfulOutcome'{procedureCode = ?'id-E-RABSetup', criticality = reject, value = #'E-RABSetupResponse'{protocolIEs = IEs}}}. -spec erab_fsm_start_reg(erab_id(), proxy_state()) -> {pid(), proxy_state()}. erab_fsm_start_reg(RABId, #proxy_state{erabs = ERABs} = S) -> UID = erab_uid(RABId, S), case dict:find(UID, ERABs) of {ok, Pid} -> ?LOG_ERROR("E-RAB ~p is already registered?!?", [UID]), {Pid, S}; %% return Pid of the existing erab_fsm process error -> {ok, Pid} = erab_fsm:start_link(UID), {Pid, S#proxy_state{erabs = dict:store(UID, Pid, ERABs)}} end. -spec erab_fsm_find(erab_id(), proxy_state()) -> {ok, pid()} | error. erab_fsm_find(RABId, #proxy_state{erabs = ERABs} = S) -> UID = erab_uid(RABId, S), dict:find(UID, ERABs). -spec gtpu_kpi_enb_register(proxy_state()) -> ok. gtpu_kpi_enb_register(#proxy_state{conn_info = ConnInfo} = S) -> %% register eNB using its Global-eNB-ID ok = gtpu_kpi:enb_register(genb_id_str(S)), %% indicate UL/DL addresses EnbAddr = inet:ntoa(maps:get(addr, ConnInfo)), gtpu_kpi_enb_set_addr({sctp, EnbAddr}). -spec gtpu_kpi_enb_set_addr({Source, EnbAddr}) -> ok when Source :: s1ap | sctp, EnbAddr :: string(). gtpu_kpi_enb_set_addr({Source, EnbAddr}) -> gtpu_kpi_enb_set_addr(gtpu_kpi_ul_addr, {Source, ul, EnbAddr}), gtpu_kpi_enb_set_addr(gtpu_kpi_dl_addr, {Source, dl, EnbAddr}), ok. -spec gtpu_kpi_enb_set_addr(EnvParam, {Source, ULDL, EnbAddr}) -> ok when EnvParam :: atom(), Source :: s1ap | sctp, ULDL :: ul | dl, EnbAddr :: string(). gtpu_kpi_enb_set_addr(EnvParam, {Source, ULDL, EnbAddr}) -> case osmo_s1gw:get_env(EnvParam, s1ap) of Source -> ?LOG_DEBUG("GTP-U KPI ~p address ~p learned from ~p", [ULDL, EnbAddr, Source]), gtpu_kpi:enb_set_addr({ULDL, EnbAddr}); Mode -> ?LOG_DEBUG("GTP-U KPI ~p address mode ~p != ~p", [ULDL, Mode, Source]) end. %% vim:set ts=4 sw=4 et: