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)].
--
To view, visit
https://gerrit.osmocom.org/c/erlang/osmo-s1gw/+/37924?usp=email
To unsubscribe, or for help writing mail filters, visit
https://gerrit.osmocom.org/settings?usp=email
Gerrit-MessageType: newchange
Gerrit-Project: erlang/osmo-s1gw
Gerrit-Branch: master
Gerrit-Change-Id: I242e84fb09b00f4794b6e1aa770f348a0e60aea4
Gerrit-Change-Number: 37924
Gerrit-PatchSet: 1
Gerrit-Owner: pespin <pespin(a)sysmocom.de>