pespin has uploaded this change for review. ( 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 a eRAB but the co-located UPF is not available at the time.
This patch all the generic infrastructure to be able to reply (errors) and implements the above mentioned specific case.
Change-Id: I242e84fb09b00f4794b6e1aa770f348a0e60aea4 --- M src/s1ap_proxy.erl M src/sctp_proxy.erl M test/s1ap_proxy_test.erl 3 files changed, 110 insertions(+), 40 deletions(-)
git pull ssh://gerrit.osmocom.org:29418/erlang/osmo-s1gw refs/changes/24/37924/1
diff --git a/src/s1ap_proxy.erl b/src/s1ap_proxy.erl index f28ff56..dd053db 100644 --- a/src/s1ap_proxy.erl +++ b/src/s1ap_proxy.erl @@ -78,7 +78,7 @@
%% Process an S1AP PDU --spec handle_pdu(binary(), proxy_state()) -> {binary(), proxy_state()}. +-spec handle_pdu(binary(), proxy_state()) -> {binary(), binary(), proxy_state()}. handle_pdu(Data, S) when is_binary(Data) -> case decode_pdu(Data) of {ok, Pdu} -> @@ -86,7 +86,7 @@ handle_pdu(Data, Pdu, S); {error, {asn1, Error}} -> ?LOG_ERROR("S1AP PDU decoding failed: ~p", [Error]), - {Data, S} + {Data, undefined, S} end.
@@ -119,13 +119,30 @@ %% Attempt to encode a new (modified) S1AP PDU, %% return a new binary() on success or Data on error. 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. + handle_pdu_new(Data, NewPdu, undefined, S). + +handle_pdu_new(Data, NewPdu, ReplyPdu, S) -> + case NewPdu of + undefined -> NewData = undefined; + _ -> + case encode_pdu(NewPdu) of + {ok, NewData} -> ok; + {error, {asn1, Error1}} -> + ?LOG_ERROR("S1AP PDU encoding failed: ~p -> ~p", [NewPdu, Error1]), + NewData = Data + end + end, + case ReplyPdu of + undefined -> NewReplyData = undefined; + _ -> + case encode_pdu(ReplyPdu) of + {ok, NewReplyData} -> ok; + {error, {asn1, Error2}} -> + ?LOG_ERROR("S1AP PDU encoding failed: ~p -> ~p", [NewPdu, Error2]), + NewReplyData = undefined + end + end, + {NewData, NewReplyData, S}.
%% 9.1.3.1 E-RAB SETUP REQUEST @@ -133,10 +150,20 @@ #'InitiatingMessage'{procedureCode = ?'id-E-RABSetup', value = Content} = Pdu}, S0) -> ?LOG_DEBUG("Patching E-RAB SETUP REQUEST"), - {IEs, S1} = handle_ies(Content#'E-RABSetupRequest'.protocolIEs, + Result = 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); + case Result of + {error, Reason, S1} -> + ?LOG_NOTICE("Patching E-RAB SETUP REQUEST failed: ~p", [Reason]), + %% Send E-RABSetupResponse with error to ENB... + ?LOG_NOTICE("Tx RABSetupResponse(~p, ~p) to MME", [S1#proxy_state.mme_ue_id, S1#proxy_state.enb_ue_id]), + ReplyPdu = build_erab_setup_response_failure(S1), + handle_pdu_new(Data, undefined, ReplyPdu, S1); + {IEs, S1} -> + NewContent = Content#'E-RABSetupRequest'{protocolIEs = IEs}, + NewPdu = {Outcome, Pdu#'InitiatingMessage'{value = NewContent}}, + handle_pdu_new(Data, NewPdu, undefined, S1) + end;
%% 9.1.3.2 E-RAB SETUP RESPONSE handle_pdu(Data, {Outcome = successfulOutcome, @@ -212,11 +239,11 @@
%% Proxy all other messages unmodified handle_pdu(Data, _Pdu, S) -> - {Data, S}. + {Data, undefined, S}.
%% Handle a single IE (Information Element) --spec handle_ie(tuple(), proxy_state()) -> {tuple(), proxy_state()}. +-spec handle_ie(tuple(), proxy_state()) -> {tuple(), proxy_state()} | {error, tuple(), proxy_state()}.
%% E-RAB SETUP REQUEST related IEs handle_ie(#'ProtocolIE-Field'{id = ?'id-E-RABToBeSetupListBearerSUReq', @@ -231,10 +258,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}; + Result = erab_fsm:erab_setup_req(Pid, {TEID_In, TLA_In}), + case Result of + {ok, {TEID_Out, TLA_Out}} -> + C1 = C0#'E-RABToBeSetupItemBearerSUReq'{'transportLayerAddress' = TLA_Out, + 'gTP-TEID' = << TEID_Out:32/big >>}, + {C1, S1}; + {error, Reason} -> {error, Reason, S1} + end;
%% E-RAB SETUP RESPONSE related IEs handle_ie(#'ProtocolIE-Field'{id = ?'id-E-RABSetupListBearerSURes', @@ -255,7 +286,7 @@ 'gTP-TEID' = << TEID_Out:32/big >>}; error -> ?LOG_ERROR("E-RAB-ID ~p is not registered", [ERABId]), - C0 %% XXX: proxy as-is or drop? + C0 %% XXX: proxy as-is or drop? {error, Reason, S} end, {C1, S};
@@ -366,16 +397,19 @@ %% 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()}. +-spec handle_ies(list(), integer(), proxy_state()) -> {list(), proxy_state()} | {error, tuple(), 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 + {error, Reason, S1} -> {error, Reason, S1}; + {Content, S1} -> + NewIE = IE#'ProtocolIE-Field'{value = Content}, + handle_ies([NewIE | Acc], IEs, IEI, 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); @@ -390,6 +424,23 @@ {lists:reverse(Acc), S}.
+build_erab_setup_response_failure(S) -> + [{_, _, FirstERABid}|_] = dict:fetch_keys(S#proxy_state.erabs), %% TODO: iterate state erabs... + ERABitem = #'E-RABItem'{'e-RAB-ID' = FirstERABid, cause = {transport, 'transport-resource-unavailable'}}, + ERABlist = [#'ProtocolIE-Field'{id = ?'id-E-RABItem', criticality = ignore, value = ERABitem}], + IEs = [ + #'ProtocolIE-Field'{id = ?'id-MME-UE-S1AP-ID', criticality = ignore, value = S#proxy_state.mme_ue_id}, + #'ProtocolIE-Field'{id = ?'id-eNB-UE-S1AP-ID', criticality = ignore, value = S#proxy_state.enb_ue_id}, + #'ProtocolIE-Field'{id = ?'id-E-RABFailedToSetupListBearerSURes', criticality = ignore, value = ERABlist} + ], + ReplyContent = #'E-RABSetupResponse'{protocolIEs = IEs}, + ReplyPdu = {successfulOutcome, + #'SuccessfulOutcome'{procedureCode = ?'id-E-RABSetup', + criticality = reject, + value = ReplyContent}}, + ReplyPdu. + + -spec erab_fsm_start_reg(erab_id(), proxy_state()) -> {pid(), proxy_state()}. erab_fsm_start_reg(RABId, #proxy_state{erabs = ERABs, mme_ue_id = MmeUeId, diff --git a/src/sctp_proxy.erl b/src/sctp_proxy.erl index 3ebabbd..83a5bc9 100644 --- a/src/sctp_proxy.erl +++ b/src/sctp_proxy.erl @@ -161,11 +161,22 @@ %% 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), + {NewData, ReplyData, NewPriv} = handle_pdu(Data, Priv), + case {NewData, ReplyData} of + {undefined, undefined} -> ok; + {undefined, ReplyData} -> ok = sctp_client:send_data({Sock, Aid}, ReplyData); + {NewData, undefined} -> sctp_server:send_data(EnbAid, NewData); + {NewData, ReplyData} -> + sctp_server:send_data(EnbAid, NewData), + ok = sctp_client:send_data({Sock, Aid}, ReplyData) + end, + {keep_state, S#{priv := NewPriv}};
%% Handle termination events of the child processes @@ -213,10 +224,10 @@ %% ------------------------------------------------------------------
%% A safe wrapper for s1ap_proxy:handle_pdu/1 --spec handle_pdu(binary(), term()) -> {binary(), term()}. +-spec handle_pdu(binary(), term()) -> {binary(), binary(), term()}. handle_pdu(Data, Priv) when is_binary(Data) -> try s1ap_proxy:handle_pdu(Data, Priv) of - Result -> Result %% {NewData, NewPriv} + Result -> Result %% {NewData, ReplyData, NewPriv} catch Exception:Reason:StackTrace -> ?LOG_ERROR("An exception occurred: ~p, ~p, ~p", [Exception, Reason, StackTrace]), @@ -227,10 +238,18 @@ %% 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), + {NewData, ReplyData, NewPriv} = handle_pdu(Data, Priv), + case {NewData, ReplyData} of + {undefined, undefined} -> ok; + {undefined, ReplyData} -> sctp_server:send_data(EnbAid, ReplyData); + {NewData, undefined} -> ok = sctp_client:send_data({Sock, Aid}, NewData); + {NewData, ReplyData} -> + sctp_server:send_data(EnbAid, ReplyData), + ok = sctp_client:send_data({Sock, Aid}, NewData) + end, S#{priv := NewPriv}.
diff --git a/test/s1ap_proxy_test.erl b/test/s1ap_proxy_test.erl index 9534980..f53ce5c 100644 --- a/test/s1ap_proxy_test.erl +++ b/test/s1ap_proxy_test.erl @@ -47,19 +47,19 @@ 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({SetupReq, undefined, S0}, s1ap_proxy:handle_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), + {SetupReqOut, undefined, S1} = s1ap_proxy:handle_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), + {SetupRspOut, undefined, _S2} = s1ap_proxy:handle_pdu(SetupRspIn, S1),
[?_assertEqual(SetupReqExp, SetupReqOut), ?_assertEqual(SetupRspExp, SetupRspOut)]. @@ -68,19 +68,19 @@ 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), + {_, undefined, S1} = s1ap_proxy:handle_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), + {_, undefined, S2} = s1ap_proxy:handle_pdu(SetupRspIn, S1),
%% [eNB <- MME] E-RAB RELEASE COMMAND ReleaseCmd = e_rab_release_cmd_pdu(), - {ReleaseCmdOut, S3} = s1ap_proxy:handle_pdu(ReleaseCmd, S2), + {ReleaseCmdOut, undefined, S3} = s1ap_proxy:handle_pdu(ReleaseCmd, S2),
%% [eNB -> MME] E-RAB RELEASE RESPONSE ReleaseRsp = e_rab_release_rsp_pdu(), - {ReleaseRspOut, _S4} = s1ap_proxy:handle_pdu(ReleaseRsp, S3), + {ReleaseRspOut, undefined, _S4} = s1ap_proxy:handle_pdu(ReleaseRsp, S3),
%% TODO: make sure that the E-RAB FSM has been terminated
@@ -93,7 +93,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), + {ModifyIndOut, undefined, _S1} = s1ap_proxy:handle_pdu(ModifyIndIn, S0),
[?_assertEqual(ModifyIndExp, ModifyIndOut)].
@@ -101,12 +101,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), + {InitCtxSetupReqOut, undefined, S1} = s1ap_proxy:handle_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), + {InitCtxSetupRspOut, undefined, _S2} = s1ap_proxy:handle_pdu(InitCtxSetupRspIn, S1),
[?_assertEqual(InitCtxSetupReqExp, InitCtxSetupReqOut), ?_assertEqual(InitCtxSetupRspExp, InitCtxSetupRspOut)].