fixeria has submitted this change. ( https://gerrit.osmocom.org/c/erlang/osmo-s1gw/+/37924?usp=email )
Change subject: s1ap_proxy: Support replying errors ......................................................................
s1ap_proxy: Support replying errors
Sometimes it is needed not only to forward, but to transmit messages initiated at the S1GW. This is the case, for instance, if the MME wants to initiate an E-RAB establishment, but the co-located UPF is not available at the time or responds with an error.
This patch adds the generic infrastructure to be able to reply (errors) and implements the above mentioned specific case for the S1AP E-RAB SETUP REQUEST procedure.
Change-Id: I242e84fb09b00f4794b6e1aa770f348a0e60aea4 --- M src/s1ap_proxy.erl M src/sctp_proxy.erl M test/s1ap_proxy_test.erl 3 files changed, 281 insertions(+), 147 deletions(-)
Approvals: Jenkins Builder: Verified pespin: Looks good to me, approved
diff --git a/src/s1ap_proxy.erl b/src/s1ap_proxy.erl index a98514c..0faa42f 100644 --- a/src/s1ap_proxy.erl +++ b/src/s1ap_proxy.erl @@ -36,7 +36,7 @@
-export([init/0, deinit/1, - handle_pdu/2, + process_pdu/2, handle_exit/2, encode_pdu/1, decode_pdu/1]). @@ -67,6 +67,9 @@ }).
-type proxy_state() :: #proxy_state{}. +-type proxy_action() :: forward | reply. + +-export_type([proxy_action/0]).
%% ------------------------------------------------------------------ %% public API @@ -85,15 +88,23 @@
%% Process an S1AP PDU --spec handle_pdu(binary(), proxy_state()) -> {binary(), proxy_state()}. -handle_pdu(Data, S) when is_binary(Data) -> - case decode_pdu(Data) of - {ok, Pdu} -> - ?LOG_DEBUG("Rx S1AP PDU: ~p", [Pdu]), - handle_pdu(Data, Pdu, S); +-spec process_pdu(binary(), proxy_state()) -> {{proxy_action(), binary()}, proxy_state()}. +process_pdu(OrigData, S0) -> + case decode_pdu(OrigData) of + {ok, PDU} -> + ?LOG_DEBUG("Rx S1AP PDU: ~p", [PDU]), + case handle_pdu(PDU, S0) of + {{Action, NewPDU}, S1} -> + {ok, NewData} = encode_pdu(NewPDU), + ?LOG_DEBUG("Tx (~p) S1AP PDU: ~p", [Action, NewPDU]), + {{Action, NewData}, S1}; + {forward, S1} -> + ?LOG_DEBUG("Tx (forward) S1AP PDU unmodified"), + {{forward, OrigData}, S1} + end; {error, {asn1, Error}} -> ?LOG_ERROR("S1AP PDU decoding failed: ~p", [Error]), - {Data, S} + {{forward, OrigData}, S0} %% XXX: forward as-is or drop? end.
@@ -128,96 +139,124 @@ 'S1AP-PDU-Descriptions':decode('S1AP-PDU', Data).
-%% Helper function for handle_pdu/3. -%% Attempt to encode a new (modified) S1AP PDU, -%% return a new binary() on success or Data on error. --spec handle_pdu_new(binary(), s1ap_pdu(), proxy_state()) -> {binary(), proxy_state()}. -handle_pdu_new(Data, NewPdu, S) -> - case encode_pdu(NewPdu) of - {ok, NewData} -> - {NewData, S}; - {error, {asn1, Error}} -> - ?LOG_ERROR("S1AP PDU encoding failed: ~p", [Error]), - {Data, S} - end. - - --spec handle_pdu(binary(), s1ap_pdu(), proxy_state()) -> {binary(), proxy_state()}. +%% Process an S1AP PDU +-spec handle_pdu(s1ap_pdu(), proxy_state()) -> {{proxy_action(), s1ap_pdu()}, proxy_state()} | + {forward, proxy_state()}.
%% 9.1.3.1 E-RAB SETUP REQUEST -handle_pdu(Data, {Outcome = initiatingMessage, - #'InitiatingMessage'{procedureCode = ?'id-E-RABSetup', - value = Content} = Pdu}, S0) -> - ?LOG_DEBUG("Patching E-RAB SETUP REQUEST"), - {IEs, S1} = handle_ies(Content#'E-RABSetupRequest'.protocolIEs, - ?'id-E-RABToBeSetupListBearerSUReq', S0), - NewContent = Content#'E-RABSetupRequest'{protocolIEs = IEs}, - handle_pdu_new(Data, {Outcome, Pdu#'InitiatingMessage'{value = NewContent}}, S1); +handle_pdu({Outcome = initiatingMessage, + #'InitiatingMessage'{procedureCode = ?'id-E-RABSetup', + value = C0} = Msg}, S0) -> + ?LOG_DEBUG("Processing E-RAB SETUP REQUEST"), + case handle_ies(C0#'E-RABSetupRequest'.protocolIEs, + ?'id-E-RABToBeSetupListBearerSUReq', 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]), + PDU = build_erab_setup_response_failure(S1), + {{reply, PDU}, S1} %% reply PDU back to sender + end;
%% 9.1.3.2 E-RAB SETUP RESPONSE -handle_pdu(Data, {Outcome = successfulOutcome, - #'SuccessfulOutcome'{procedureCode = ?'id-E-RABSetup', - value = Content} = Pdu}, S0) -> - ?LOG_DEBUG("Patching E-RAB SETUP RESPONSE"), - {IEs, S1} = handle_ies(Content#'E-RABSetupResponse'.protocolIEs, - ?'id-E-RABSetupListBearerSURes', S0), - NewContent = Content#'E-RABSetupResponse'{protocolIEs = IEs}, - handle_pdu_new(Data, {Outcome, Pdu#'SuccessfulOutcome'{value = NewContent}}, S1); +handle_pdu({Outcome = successfulOutcome, + #'SuccessfulOutcome'{procedureCode = ?'id-E-RABSetup', + value = C0} = Msg}, S0) -> + ?LOG_DEBUG("Processing E-RAB SETUP RESPONSE"), + case handle_ies(C0#'E-RABSetupResponse'.protocolIEs, + ?'id-E-RABSetupListBearerSURes', 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]), + {forward, S1} %% XXX: forward as-is or drop? + end;
%% TODO: 9.1.3.3 E-RAB MODIFY REQUEST / (Optional) Transport Information
%% 9.1.3.5 E-RAB RELEASE COMMAND -handle_pdu(Data, {Outcome = initiatingMessage, - #'InitiatingMessage'{procedureCode = ?'id-E-RABRelease', - value = Content} = Pdu}, S0) -> +handle_pdu({Outcome = initiatingMessage, + #'InitiatingMessage'{procedureCode = ?'id-E-RABRelease', + value = C0} = Msg}, S0) -> ?LOG_DEBUG("Processing E-RAB RELEASE COMMAND"), - {IEs, S1} = handle_ies(Content#'E-RABReleaseCommand'.protocolIEs, - ?'id-E-RABToBeReleasedList', S0), - NewContent = Content#'E-RABReleaseCommand'{protocolIEs = IEs}, - handle_pdu_new(Data, {Outcome, Pdu#'InitiatingMessage'{value = NewContent}}, S1); + case handle_ies(C0#'E-RABReleaseCommand'.protocolIEs, + ?'id-E-RABToBeReleasedList', S0) of + {{ok, IEs}, S1} -> + C1 = C0#'E-RABReleaseCommand'{protocolIEs = IEs}, + PDU = {Outcome, Msg#'InitiatingMessage'{value = C1}}, + {{forward, PDU}, S1}; %% forward patched PDU + {{error, Reason}, S1} -> + ?LOG_NOTICE("Failed to process E-RAB RELEASE COMMAND: ~p", [Reason]), + {forward, S1} %% XXX: forward as-is or drop? + end;
%% 9.1.3.6 E-RAB RELEASE RESPONSE -handle_pdu(Data, {Outcome = successfulOutcome, - #'SuccessfulOutcome'{procedureCode = ?'id-E-RABRelease', - value = Content} = Pdu}, S0) -> +handle_pdu({Outcome = successfulOutcome, + #'SuccessfulOutcome'{procedureCode = ?'id-E-RABRelease', + value = C0} = Msg}, S0) -> ?LOG_DEBUG("Processing E-RAB RELEASE RESPONSE"), - {IEs, S1} = handle_ies(Content#'E-RABReleaseResponse'.protocolIEs, - ?'id-E-RABReleaseListBearerRelComp', S0), - NewContent = Content#'E-RABReleaseResponse'{protocolIEs = IEs}, - handle_pdu_new(Data, {Outcome, Pdu#'SuccessfulOutcome'{value = NewContent}}, S1); + case handle_ies(C0#'E-RABReleaseResponse'.protocolIEs, + ?'id-E-RABReleaseListBearerRelComp', S0) of + {{ok, IEs}, S1} -> + C1 = C0#'E-RABReleaseResponse'{protocolIEs = IEs}, + PDU = {Outcome, Msg#'SuccessfulOutcome'{value = C1}}, + {{forward, PDU}, S1}; %% forward patched PDU + {{error, Reason}, S1} -> + ?LOG_NOTICE("Failed to process E-RAB RELEASE RESPONSE: ~p", [Reason]), + {forward, S1} %% XXX: forward as-is or drop? + end;
%% 9.1.3.8 E-RAB MODIFICATION INDICATION -handle_pdu(Data, {Outcome = initiatingMessage, - #'InitiatingMessage'{procedureCode = ?'id-E-RABModificationIndication', - value = Content} = Pdu}, S0) -> - ?LOG_DEBUG("Patching E-RAB MODIFICATION INDICATION"), - IEs = Content#'E-RABModificationIndication'.protocolIEs, +handle_pdu({Outcome = initiatingMessage, + #'InitiatingMessage'{procedureCode = ?'id-E-RABModificationIndication', + value = C0} = Msg}, S0) -> + ?LOG_DEBUG("Processing E-RAB MODIFICATION INDICATION"), + IEs0 = C0#'E-RABModificationIndication'.protocolIEs, %% E-RAB to be Modified List - {IEs1, S1} = handle_ies(IEs, ?'id-E-RABToBeModifiedListBearerModInd', S0), + %% TODO: handle {error, Reason} + {{ok, IEs1}, S1} = handle_ies(IEs0, ?'id-E-RABToBeModifiedListBearerModInd', S0), %% E-RAB not to be Modified List - {IEs2, S2} = handle_ies(IEs1, ?'id-E-RABNotToBeModifiedListBearerModInd', S1), - NewContent = Content#'E-RABModificationIndication'{protocolIEs = IEs2}, - handle_pdu_new(Data, {Outcome, Pdu#'InitiatingMessage'{value = NewContent}}, S2); + %% TODO: handle {error, Reason} + {{ok, IEs2}, S2} = handle_ies(IEs1, ?'id-E-RABNotToBeModifiedListBearerModInd', S1), + C1 = C0#'E-RABModificationIndication'{protocolIEs = IEs2}, + PDU = {Outcome, Msg#'InitiatingMessage'{value = C1}}, + {{forward, PDU}, S2};
%% 9.1.4.1 INITIAL CONTEXT SETUP REQUEST -handle_pdu(Data, {Outcome = initiatingMessage, - #'InitiatingMessage'{procedureCode = ?'id-InitialContextSetup', - value = Content} = Pdu}, S0) -> - ?LOG_DEBUG("Patching INITIAL CONTEXT SETUP REQUEST"), - {IEs, S1} = handle_ies(Content#'InitialContextSetupRequest'.protocolIEs, - ?'id-E-RABToBeSetupListCtxtSUReq', S0), - NewContent = Content#'InitialContextSetupRequest'{protocolIEs = IEs}, - handle_pdu_new(Data, {Outcome, Pdu#'InitiatingMessage'{value = NewContent}}, S1); +handle_pdu({Outcome = initiatingMessage, + #'InitiatingMessage'{procedureCode = ?'id-InitialContextSetup', + value = C0} = Msg}, S0) -> + ?LOG_DEBUG("Processing INITIAL CONTEXT SETUP REQUEST"), + case handle_ies(C0#'InitialContextSetupRequest'.protocolIEs, + ?'id-E-RABToBeSetupListCtxtSUReq', 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]), + {forward, S1} %% XXX: forward as-is or drop? + end;
%% 9.1.4.3 INITIAL CONTEXT SETUP RESPONSE -handle_pdu(Data, {Outcome = successfulOutcome, - #'SuccessfulOutcome'{procedureCode = ?'id-InitialContextSetup', - value = Content} = Pdu}, S0) -> - ?LOG_DEBUG("Patching INITIAL CONTEXT SETUP RESPONSE"), - {IEs, S1} = handle_ies(Content#'InitialContextSetupResponse'.protocolIEs, - ?'id-E-RABSetupListCtxtSURes', S0), - NewContent = Content#'InitialContextSetupResponse'{protocolIEs = IEs}, - handle_pdu_new(Data, {Outcome, Pdu#'SuccessfulOutcome'{value = NewContent}}, S1); +handle_pdu({Outcome = successfulOutcome, + #'SuccessfulOutcome'{procedureCode = ?'id-InitialContextSetup', + value = C0} = Msg}, S0) -> + ?LOG_DEBUG("Processing INITIAL CONTEXT SETUP RESPONSE"), + case handle_ies(C0#'InitialContextSetupResponse'.protocolIEs, + ?'id-E-RABSetupListCtxtSURes', 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]), + {forward, S1} %% XXX: forward as-is or drop? + end;
%% TODO: 9.1.5.2 HANDOVER COMMAND :: (O) UL/DL Transport Layer Address %% TODO: 9.1.5.4 HANDOVER REQUEST :: (M) Transport Layer Address @@ -227,12 +266,13 @@ %% TODO: 9.1.5.9 PATH SWITCH REQUEST ACKNOWLEDGE :: (M) Transport Layer Address
%% Proxy all other messages unmodified -handle_pdu(Data, _Pdu, S) -> - {Data, S}. +handle_pdu(_PDU, S) -> + {forward, S}.
%% Handle a single IE (Information Element) --spec handle_ie(s1ap_ie(), proxy_state()) -> {s1ap_ie_val(), proxy_state()}. +-type handle_ie_result() :: {ok, s1ap_ie_val()} | {error, term()}. +-spec handle_ie(s1ap_ie(), proxy_state()) -> {handle_ie_result(), proxy_state()}.
%% E-RAB SETUP REQUEST related IEs handle_ie(#'ProtocolIE-Field'{id = ?'id-E-RABToBeSetupListBearerSUReq', @@ -247,10 +287,14 @@ 'transportLayerAddress' = TLA_In, 'gTP-TEID' = << TEID_In:32/big >>} = C0, {Pid, S1} = erab_fsm_start_reg(ERABId, S0), - {ok, {TEID_Out, TLA_Out}} = erab_fsm:erab_setup_req(Pid, {TEID_In, TLA_In}), - C1 = C0#'E-RABToBeSetupItemBearerSUReq'{'transportLayerAddress' = TLA_Out, - 'gTP-TEID' = << TEID_Out:32/big >>}, - {C1, S1}; + 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(#'ProtocolIE-Field'{id = ?'id-E-RABSetupListBearerSURes', @@ -264,16 +308,20 @@ #'E-RABSetupItemBearerSURes'{'e-RAB-ID' = ERABId, 'transportLayerAddress' = TLA_In, 'gTP-TEID' = << TEID_In:32/big >>} = C0, - C1 = case erab_fsm_find(ERABId, S) of + case erab_fsm_find(ERABId, S) of {ok, Pid} -> - {ok, {TEID_Out, TLA_Out}} = erab_fsm:erab_setup_rsp(Pid, {TEID_In, TLA_In}), - C0#'E-RABSetupItemBearerSURes'{'transportLayerAddress' = TLA_Out, - 'gTP-TEID' = << TEID_Out:32/big >>}; + 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", [ERABId]), - C0 %% XXX: proxy as-is or drop? - end, - {C1, S}; + {{error, erab_not_registered}, S} + end;
%% 9.1.3.5 E-RAB RELEASE COMMAND related IEs handle_ie(#'ProtocolIE-Field'{id = ?'id-E-RABToBeReleasedList', @@ -282,16 +330,17 @@ handle_ies(Content, ?'id-E-RABItem', S);
handle_ie(#'ProtocolIE-Field'{id = ?'id-E-RABItem', - value = Content}, S) -> + value = C}, S) -> %% poke E-RAB FSM - #'E-RABItem'{'e-RAB-ID' = ERABId} = Content, + #'E-RABItem'{'e-RAB-ID' = ERABId} = C, case erab_fsm_find(ERABId, S) of {ok, Pid} -> - ok = erab_fsm:erab_release_req(Pid); + ok = erab_fsm:erab_release_req(Pid), + {{ok, C}, S}; error -> - ?LOG_ERROR("E-RAB ~p is not registered", [erab_uid(ERABId, S)]) - end, - {Content, S}; + ?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(#'ProtocolIE-Field'{id = ?'id-E-RABReleaseListBearerRelComp', @@ -300,16 +349,17 @@ handle_ies(Content, ?'id-E-RABReleaseItemBearerRelComp', S);
handle_ie(#'ProtocolIE-Field'{id = ?'id-E-RABReleaseItemBearerRelComp', - value = Content}, S) -> + value = C}, S) -> %% poke E-RAB FSM - #'E-RABReleaseItemBearerRelComp'{'e-RAB-ID' = ERABId} = Content, + #'E-RABReleaseItemBearerRelComp'{'e-RAB-ID' = ERABId} = C, case erab_fsm_find(ERABId, S) of {ok, Pid} -> - ok = erab_fsm:erab_release_rsp(Pid); + ok = erab_fsm:erab_release_rsp(Pid), + {{ok, C}, S}; error -> - ?LOG_ERROR("E-RAB ~p is not registered", [erab_uid(ERABId, S)]) - end, - {Content, S}; + ?LOG_ERROR("E-RAB ~p is not registered", [erab_uid(ERABId, S)]), + {{error, erab_not_registered}, S} + end;
%% E-RAB MODIFICATION INDICATION related IEs handle_ie(#'ProtocolIE-Field'{id = ?'id-E-RABToBeModifiedListBearerModInd', @@ -318,9 +368,9 @@ handle_ies(Content, ?'id-E-RABToBeModifiedItemBearerModInd', S);
handle_ie(#'ProtocolIE-Field'{id = ?'id-E-RABToBeModifiedItemBearerModInd', - value = Content}, S) -> + value = C}, S) -> %% TODO: find and poke an E-RAB FSM associated with this E-RAB - {Content#'E-RABToBeModifiedItemBearerModInd'{}, S}; + {{ok, C}, S};
handle_ie(#'ProtocolIE-Field'{id = ?'id-E-RABNotToBeModifiedListBearerModInd', value = Content}, S) -> @@ -328,9 +378,9 @@ handle_ies(Content, ?'id-E-RABNotToBeModifiedItemBearerModInd', S);
handle_ie(#'ProtocolIE-Field'{id = ?'id-E-RABNotToBeModifiedItemBearerModInd', - value = Content}, S) -> + value = C}, S) -> %% TODO: find and poke an E-RAB FSM associated with this E-RAB - {Content#'E-RABNotToBeModifiedItemBearerModInd'{}, S}; + {{ok, C}, S};
%% INITIAL CONTEXT SETUP REQUEST related IEs handle_ie(#'ProtocolIE-Field'{id = ?'id-E-RABToBeSetupListCtxtSUReq', @@ -345,10 +395,14 @@ 'transportLayerAddress' = TLA_In, 'gTP-TEID' = << TEID_In:32/big >>} = C0, {Pid, S1} = erab_fsm_start_reg(ERABId, S0), - {ok, {TEID_Out, TLA_Out}} = erab_fsm:erab_setup_req(Pid, {TEID_In, TLA_In}), - C1 = C0#'E-RABToBeSetupItemCtxtSUReq'{'transportLayerAddress' = TLA_Out, - 'gTP-TEID' = << TEID_Out:32/big >>}, - {C1, S1}; + 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(#'ProtocolIE-Field'{id = ?'id-E-RABSetupListCtxtSURes', @@ -362,36 +416,45 @@ #'E-RABSetupItemCtxtSURes'{'e-RAB-ID' = ERABId, 'transportLayerAddress' = TLA_In, 'gTP-TEID' = << TEID_In:32/big >>} = C0, - C1 = case erab_fsm_find(ERABId, S) of + case erab_fsm_find(ERABId, S) of {ok, Pid} -> - {ok, {TEID_Out, TLA_Out}} = erab_fsm:erab_setup_rsp(Pid, {TEID_In, TLA_In}), - C0#'E-RABSetupItemCtxtSURes'{'transportLayerAddress' = TLA_Out, - 'gTP-TEID' = << TEID_Out:32/big >>}; + 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)]), - C0 %% XXX: proxy as-is or drop? - end, - {C1, S}; + {{error, erab_not_registered}, S} + end;
%% Catch-all variant, which should not be called normally -handle_ie(#'ProtocolIE-Field'{value = Content} = IE, S) -> +handle_ie(#'ProtocolIE-Field'{} = IE, S) -> ?LOG_ERROR("[BUG] Unhandled S1AP IE: ~p", [IE]), - {Content, S}. + {{error, unhandled_ie}, S}.
%% Iterate over the given list of 'ProtocolIE-Field' IEs, %% calling function handle_ie/1 for IEs matching the given IEI. %% Additionally look for {MME,eNB}-UE-S1AP-ID IEs and store their values. --spec handle_ies(list(), integer(), proxy_state()) -> {list(), proxy_state()}. +-type handle_ies_result() :: {ok, list()} | {error, term()}. +-spec handle_ies(list(), integer(), proxy_state()) -> {handle_ies_result(), proxy_state()}. handle_ies(IEs, IEI, S) -> handle_ies([], IEs, IEI, S).
handle_ies(Acc, [IE | IEs], IEI, S0) -> case IE of #'ProtocolIE-Field'{id = IEI} -> - {Content, S1} = handle_ie(IE, S0), - NewIE = IE#'ProtocolIE-Field'{value = Content}, - handle_ies([NewIE | Acc], IEs, IEI, S1); + case handle_ie(IE, S0) of + {{ok, C}, S1} -> + NewIE = IE#'ProtocolIE-Field'{value = C}, + handle_ies([NewIE | Acc], IEs, IEI, 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], IEs, IEI, S1); @@ -403,7 +466,35 @@ end;
handle_ies(Acc, [], _IEI, S) -> - {lists:reverse(Acc), 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()}. diff --git a/src/sctp_proxy.erl b/src/sctp_proxy.erl index 3ebabbd..b37bead 100644 --- a/src/sctp_proxy.erl +++ b/src/sctp_proxy.erl @@ -161,11 +161,19 @@ %% Handle an #sctp_sndrcvinfo event (MME -> eNB data) connected(info, {sctp, _Socket, MmeAddr, MmePort, {[#sctp_sndrcvinfo{assoc_id = Aid}], Data}}, - #{enb_aid := EnbAid, priv := Priv} = S) -> + #{sock := Sock, + enb_aid := EnbAid, + mme_aid := Aid, + priv := Priv} = S) -> ?LOG_DEBUG("MME connection (id=~p, ~p:~p) -> eNB: ~p", [Aid, MmeAddr, MmePort, Data]), - {NewData, NewPriv} = handle_pdu(Data, Priv), - sctp_server:send_data(EnbAid, NewData), + {Action, NewPriv} = handle_pdu(Data, Priv), + case Action of + {forward, FwdData} -> + sctp_server:send_data(EnbAid, FwdData); + {reply, ReData} -> + ok = sctp_client:send_data({Sock, Aid}, ReData) + end, {keep_state, S#{priv := NewPriv}};
%% Handle termination events of the child processes @@ -212,25 +220,31 @@ %% private API %% ------------------------------------------------------------------
-%% A safe wrapper for s1ap_proxy:handle_pdu/1 --spec handle_pdu(binary(), term()) -> {binary(), term()}. -handle_pdu(Data, Priv) when is_binary(Data) -> - try s1ap_proxy:handle_pdu(Data, Priv) of - Result -> Result %% {NewData, NewPriv} +%% A safe wrapper for s1ap_proxy:proc/2 +-spec handle_pdu(binary(), term()) -> {{s1ap_proxy:proxy_action(), binary()}, term()}. +handle_pdu(OrigData, Priv) when is_binary(OrigData) -> + try s1ap_proxy:process_pdu(OrigData, Priv) of + Result -> Result %% {Action, NewPriv} catch Exception:Reason:StackTrace -> ?LOG_ERROR("An exception occurred: ~p, ~p, ~p", [Exception, Reason, StackTrace]), - {Data, Priv} %% proxy as-is + {{forward, OrigData}, Priv} %% XXX: proxy as-is or drop? end.
%% Send a single message to the MME sctp_send(Data, #{sock := Sock, + enb_aid := EnbAid, mme_aid := Aid, priv := Priv} = S) -> - {NewData, NewPriv} = handle_pdu(Data, Priv), - ok = sctp_client:send_data({Sock, Aid}, NewData), + {Action, NewPriv} = handle_pdu(Data, Priv), + case Action of + {forward, FwdData} -> + ok = sctp_client:send_data({Sock, Aid}, FwdData); + {reply, ReData} -> + sctp_server:send_data(EnbAid, ReData) + end, S#{priv := NewPriv}.
diff --git a/test/s1ap_proxy_test.erl b/test/s1ap_proxy_test.erl index 5f6167d..aa3763e 100644 --- a/test/s1ap_proxy_test.erl +++ b/test/s1ap_proxy_test.erl @@ -32,6 +32,8 @@ ?TC(fun test_s1_setup_req/1)}, {"E-RAB SETUP REQUEST/RESPONSE", ?TC(fun test_e_rab_setup/1)}, + {"E-RAB SETUP REQUEST (failure)", + ?TC(fun test_e_rab_setup_req_fail/1)}, {"E-RAB RELEASE COMMAND/RESPONSE", ?TC(fun test_e_rab_release/1)}, {"E-RAB MODIFICATION INDICATION", @@ -47,40 +49,57 @@ test_s1_setup_req(S0) -> SetupReq = s1_setup_req_pdu(), %% Expect the PDU to be proxied unmodified - [?_assertEqual({SetupReq, S0}, s1ap_proxy:handle_pdu(SetupReq, S0))]. + [?_assertEqual({{forward, SetupReq}, S0}, s1ap_proxy:process_pdu(SetupReq, S0))].
test_e_rab_setup(S0) -> %% [eNB <- MME] E-RAB SETUP REQUEST SetupReqIn = e_rab_setup_req_pdu(?ADDR_U2C, ?TEID_U2C), SetupReqExp = e_rab_setup_req_pdu(?ADDR_A2U, ?TEID_A2U), - {SetupReqOut, S1} = s1ap_proxy:handle_pdu(SetupReqIn, S0), + {{forward, SetupReqOut}, S1} = s1ap_proxy:process_pdu(SetupReqIn, S0),
%% [eNB -> MME] E-RAB SETUP RESPONSE SetupRspIn = e_rab_setup_rsp_pdu(?ADDR_U2A, ?TEID_U2A), SetupRspExp = e_rab_setup_rsp_pdu(?ADDR_C2U, ?TEID_C2U), - {SetupRspOut, _S2} = s1ap_proxy:handle_pdu(SetupRspIn, S1), + {{forward, SetupRspOut}, _S2} = s1ap_proxy:process_pdu(SetupRspIn, S1),
[?_assertEqual(SetupReqExp, SetupReqOut), ?_assertEqual(SetupRspExp, SetupRspOut)].
+test_e_rab_setup_req_fail(S0) -> + %% pfcp_peer:session_establish_req/3 responds with a reject + PDU = pfcp_mock:pdu_rsp_reject(session_establishment_response, ?SEID_Loc), + pfcp_mock:mock_req(session_establish_req, PDU), + %% the linked erab_fsm will terminate abnormally, so trap this + process_flag(trap_exit, true), + %% eNB <- [S1GW <- MME] E-RAB SETUP REQUEST + %% eNB -- [S1GW -> MME] E-RAB SETUP RESPONSE (failure) + SetupReqIn = e_rab_setup_req_pdu(?ADDR_U2C, ?TEID_U2C), + SetupRspExp = e_rab_setup_rsp_fail_pdu(), + {{reply, SetupRspOut}, _S1} = s1ap_proxy:process_pdu(SetupReqIn, S0), + + %% TODO: make sure that the E-RAB FSM has been terminated + + [?_assertEqual(SetupRspExp, SetupRspOut)]. + + test_e_rab_release(S0) -> %% [eNB <- MME] E-RAB SETUP REQUEST SetupReqIn = e_rab_setup_req_pdu(?ADDR_U2C, ?TEID_U2C), - {_, S1} = s1ap_proxy:handle_pdu(SetupReqIn, S0), + {_, S1} = s1ap_proxy:process_pdu(SetupReqIn, S0),
%% [eNB -> MME] E-RAB SETUP RESPONSE SetupRspIn = e_rab_setup_rsp_pdu(?ADDR_U2A, ?TEID_U2A), - {_, S2} = s1ap_proxy:handle_pdu(SetupRspIn, S1), + {_, S2} = s1ap_proxy:process_pdu(SetupRspIn, S1),
%% [eNB <- MME] E-RAB RELEASE COMMAND ReleaseCmd = e_rab_release_cmd_pdu(), - {ReleaseCmdOut, S3} = s1ap_proxy:handle_pdu(ReleaseCmd, S2), + {{forward, ReleaseCmdOut}, S3} = s1ap_proxy:process_pdu(ReleaseCmd, S2),
%% [eNB -> MME] E-RAB RELEASE RESPONSE ReleaseRsp = e_rab_release_rsp_pdu(), - {ReleaseRspOut, _S4} = s1ap_proxy:handle_pdu(ReleaseRsp, S3), + {{forward, ReleaseRspOut}, _S4} = s1ap_proxy:process_pdu(ReleaseRsp, S3),
%% TODO: make sure that the E-RAB FSM has been terminated
@@ -93,7 +112,7 @@ ModifyIndIn = e_rab_modify_ind_pdu(?ADDR_U2A, ?TEID_U2A), %% XXX: not implemented, we should actually expect ?ADDR_C2U, ?TEID_C2U ModifyIndExp = e_rab_modify_ind_pdu(?ADDR_U2A, ?TEID_U2A), - {ModifyIndOut, _S1} = s1ap_proxy:handle_pdu(ModifyIndIn, S0), + {{forward, ModifyIndOut}, _S1} = s1ap_proxy:process_pdu(ModifyIndIn, S0),
[?_assertEqual(ModifyIndExp, ModifyIndOut)].
@@ -101,12 +120,12 @@ %% [eNB <- MME] INITIAL CONTEXT SETUP REQUEST InitCtxSetupReqIn = initial_context_setup_req_pdu(?ADDR_U2C, ?TEID_U2C), InitCtxSetupReqExp = initial_context_setup_req_pdu(?ADDR_A2U, ?TEID_A2U), - {InitCtxSetupReqOut, S1} = s1ap_proxy:handle_pdu(InitCtxSetupReqIn, S0), + {{forward, InitCtxSetupReqOut}, S1} = s1ap_proxy:process_pdu(InitCtxSetupReqIn, S0),
%% [eNB -> MME] INITIAL CONTEXT SETUP RESPONSE InitCtxSetupRspIn = initial_context_setup_rsp_pdu(?ADDR_U2A, ?TEID_U2A), InitCtxSetupRspExp = initial_context_setup_rsp_pdu(?ADDR_C2U, ?TEID_C2U), - {InitCtxSetupRspOut, _S2} = s1ap_proxy:handle_pdu(InitCtxSetupRspIn, S1), + {{forward, InitCtxSetupRspOut}, _S2} = s1ap_proxy:process_pdu(InitCtxSetupRspIn, S1),
[?_assertEqual(InitCtxSetupReqExp, InitCtxSetupReqOut), ?_assertEqual(InitCtxSetupRspExp, InitCtxSetupRspOut)]. @@ -165,6 +184,16 @@ >>.
+%% [S1GW -> MME] E-RAB SETUP RESPONSE (failure) +%% Transport-cause: transport-resource-unavailable +e_rab_setup_rsp_fail_pdu() -> + << 16#20, 16#05, 16#00, 16#1a, 16#00, 16#00, 16#03, 16#00, + 16#00, 16#40, 16#02, 16#00, 16#07, 16#00, 16#08, 16#40, + 16#02, 16#00, 16#09, 16#00, 16#1d, 16#40, 16#07, 16#00, + 16#00, 16#23, 16#40, 16#02, 16#0c, 16#20 + >>. + + %% [eNB <- MME] E-RAB RELEASE COMMAND %% TODO: make E-RAB IDs configurable e_rab_release_cmd_pdu() ->