fixeria has uploaded this change for review. ( https://gerrit.osmocom.org/c/erlang/osmo-s1gw/+/37462?usp=email )
Change subject: sctp_proxy: maintain opaque state for s1ap_proxy ......................................................................
sctp_proxy: maintain opaque state for s1ap_proxy
Change-Id: I6ea8479c1a66bca330a7d4030cc7cdf55a76b12c --- M src/s1ap_proxy.erl M src/sctp_proxy.erl 2 files changed, 131 insertions(+), 91 deletions(-)
git pull ssh://gerrit.osmocom.org:29418/erlang/osmo-s1gw refs/changes/62/37462/1
diff --git a/src/s1ap_proxy.erl b/src/s1ap_proxy.erl index 579df40..eaa7dfd 100644 --- a/src/s1ap_proxy.erl +++ b/src/s1ap_proxy.erl @@ -34,7 +34,9 @@
-module(s1ap_proxy).
--export([handle_pdu/1, +-export([init/0, + deinit/1, + handle_pdu/2, encode_pdu/1, decode_pdu/1]).
@@ -45,20 +47,37 @@ -include("S1AP-Containers.hrl"). -include("S1AP-Constants.hrl").
+ +-record(proxy_state, { }). + +-type proxy_state() :: #proxy_state{}. + %% ------------------------------------------------------------------ %% public API %% ------------------------------------------------------------------
+%% Initialize per-connection data +-spec init() -> proxy_state(). +init() -> + #proxy_state{}. + + +%% De-initialize per-connection data +-spec deinit(proxy_state()) -> ok. +deinit(_S) -> + ok. + + %% Process an S1AP PDU --spec handle_pdu(binary()) -> binary(). -handle_pdu(Data) when is_binary(Data) -> +-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_INFO("S1AP PDU: ~p", [Pdu]), - handle_pdu(Data, Pdu); + handle_pdu(Data, Pdu, S); {error, {asn1, Error}} -> ?LOG_ERROR("S1AP PDU decoding failed: ~p", [Error]), - Data + {Data, S} end.
@@ -80,73 +99,73 @@ 'S1AP-PDU-Descriptions':decode('S1AP-PDU', Data).
-%% Helper function for handle_pdu/2. +%% Helper function for handle_pdu/3. %% Attempt to encode a new (modified) S1AP PDU, %% return a new binary() on success or Data on error. -handle_pdu_new(Data, NewPdu) -> +handle_pdu_new(Data, NewPdu, S) -> case encode_pdu(NewPdu) of {ok, NewData} -> - NewData; + {NewData, S}; {error, {asn1, Error}} -> ?LOG_ERROR("S1AP PDU encoding failed: ~p", [Error]), - Data + {Data, S} end.
%% 9.1.3.1 E-RAB SETUP REQUEST handle_pdu(Data, {Outcome = initiatingMessage, #'InitiatingMessage'{procedureCode = ?'id-E-RABSetup', - value = Content} = Pdu}) -> + value = Content} = Pdu}, S0) -> ?LOG_DEBUG("Patching E-RAB SETUP REQUEST"), - IEs = handle_ies(Content#'E-RABSetupRequest'.protocolIEs, - ?'id-E-RABToBeSetupListBearerSUReq'), + {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}}); + handle_pdu_new(Data, {Outcome, Pdu#'InitiatingMessage'{value = NewContent}}, S1);
%% 9.1.3.2 E-RAB SETUP RESPONSE handle_pdu(Data, {Outcome = successfulOutcome, #'SuccessfulOutcome'{procedureCode = ?'id-E-RABSetup', - value = Content} = Pdu}) -> + value = Content} = Pdu}, S0) -> ?LOG_DEBUG("Patching E-RAB SETUP RESPONSE"), - IEs = handle_ies(Content#'E-RABSetupResponse'.protocolIEs, - ?'id-E-RABSetupListBearerSURes'), + {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}}); + handle_pdu_new(Data, {Outcome, Pdu#'SuccessfulOutcome'{value = NewContent}}, S1);
%% TODO: 9.1.3.3 E-RAB MODIFY REQUEST / (Optional) Transport Information
%% 9.1.3.8 E-RAB MODIFICATION INDICATION handle_pdu(Data, {Outcome = initiatingMessage, #'InitiatingMessage'{procedureCode = ?'id-E-RABModificationIndication', - value = Content} = Pdu}) -> + value = Content} = Pdu}, S0) -> ?LOG_DEBUG("Patching E-RAB MODIFICATION INDICATION"), IEs = Content#'E-RABModificationIndication'.protocolIEs, %% E-RAB to be Modified List - IEs1 = handle_ies(IEs, ?'id-E-RABToBeModifiedListBearerModInd'), + {IEs1, S1} = handle_ies(IEs, ?'id-E-RABToBeModifiedListBearerModInd', S0), %% E-RAB not to be Modified List - IEs2 = handle_ies(IEs1, ?'id-E-RABNotToBeModifiedListBearerModInd'), + {IEs2, S2} = handle_ies(IEs1, ?'id-E-RABNotToBeModifiedListBearerModInd', S1), NewContent = Content#'E-RABModificationIndication'{protocolIEs = IEs2}, - handle_pdu_new(Data, {Outcome, Pdu#'InitiatingMessage'{value = NewContent}}); + handle_pdu_new(Data, {Outcome, Pdu#'InitiatingMessage'{value = NewContent}}, S2);
%% 9.1.4.1 INITIAL CONTEXT SETUP REQUEST handle_pdu(Data, {Outcome = initiatingMessage, #'InitiatingMessage'{procedureCode = ?'id-InitialContextSetup', - value = Content} = Pdu}) -> + value = Content} = Pdu}, S0) -> ?LOG_DEBUG("Patching INITIAL CONTEXT SETUP REQUEST"), - IEs = handle_ies(Content#'InitialContextSetupRequest'.protocolIEs, - ?'id-E-RABToBeSetupListCtxtSUReq'), + {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}}); + handle_pdu_new(Data, {Outcome, Pdu#'InitiatingMessage'{value = NewContent}}, S1);
%% 9.1.4.3 INITIAL CONTEXT SETUP RESPONSE handle_pdu(Data, {Outcome = successfulOutcome, #'SuccessfulOutcome'{procedureCode = ?'id-InitialContextSetup', - value = Content} = Pdu}) -> + value = Content} = Pdu}, S0) -> ?LOG_DEBUG("Patching INITIAL CONTEXT SETUP RESPONSE"), - IEs = handle_ies(Content#'InitialContextSetupResponse'.protocolIEs, - ?'id-E-RABSetupListCtxtSURes'), + {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}}); + handle_pdu_new(Data, {Outcome, Pdu#'SuccessfulOutcome'{value = NewContent}}, S1);
%% TODO: 9.1.5.2 HANDOVER COMMAND :: (O) UL/DL Transport Layer Address %% TODO: 9.1.5.4 HANDOVER REQUEST :: (M) Transport Layer Address @@ -156,107 +175,108 @@ %% TODO: 9.1.5.9 PATH SWITCH REQUEST ACKNOWLEDGE :: (M) Transport Layer Address
%% Proxy all other messages unmodified -handle_pdu(Data, _Pdu) -> - Data. +handle_pdu(Data, _Pdu, S) -> + {Data, S}.
%% Handle a single IE (Information Element) --spec handle_ie(tuple()) -> tuple(). +-spec handle_ie(tuple(), proxy_state()) -> {tuple(), proxy_state()}.
%% E-RAB SETUP REQUEST related IEs handle_ie(#'ProtocolIE-Field'{id = ?'id-E-RABToBeSetupListBearerSUReq', - value = Content}) -> + value = Content}, S) -> %% This IE contains a list of BearerSUReq, so patch inner IEs - handle_ies(Content, ?'id-E-RABToBeSetupItemBearerSUReq'); + handle_ies(Content, ?'id-E-RABToBeSetupItemBearerSUReq', S);
handle_ie(#'ProtocolIE-Field'{id = ?'id-E-RABToBeSetupItemBearerSUReq', - value = Content}) -> + value = Content}, S) -> %% eNB -> MME direction: we pass our MME facing local address TLA = transp_layer_addr(mme_loc_addr), - Content#'E-RABToBeSetupItemBearerSUReq'{transportLayerAddress = TLA}; + {Content#'E-RABToBeSetupItemBearerSUReq'{transportLayerAddress = TLA}, S};
%% E-RAB SETUP RESPONSE related IEs handle_ie(#'ProtocolIE-Field'{id = ?'id-E-RABSetupListBearerSURes', - value = Content}) -> + value = Content}, S) -> %% This IE contains a list of BearerSURes, so patch inner IEs - handle_ies(Content, ?'id-E-RABSetupItemBearerSURes'); + handle_ies(Content, ?'id-E-RABSetupItemBearerSURes', S);
handle_ie(#'ProtocolIE-Field'{id = ?'id-E-RABSetupItemBearerSURes', - value = Content}) -> + value = Content}, S) -> %% MME -> eNB direction: we pass our eNB facing local address TLA = transp_layer_addr(s1gw_bind_addr), - Content#'E-RABSetupItemBearerSURes'{transportLayerAddress = TLA}; + {Content#'E-RABSetupItemBearerSURes'{transportLayerAddress = TLA}, S};
%% E-RAB MODIFICATION INDICATION related IEs handle_ie(#'ProtocolIE-Field'{id = ?'id-E-RABToBeModifiedListBearerModInd', - value = Content}) -> + value = Content}, S) -> %% This IE contains a list of BearerModInd, so patch inner IEs - handle_ies(Content, ?'id-E-RABToBeModifiedItemBearerModInd'); + handle_ies(Content, ?'id-E-RABToBeModifiedItemBearerModInd', S);
handle_ie(#'ProtocolIE-Field'{id = ?'id-E-RABToBeModifiedItemBearerModInd', - value = Content}) -> + value = Content}, S) -> %% eNB -> MME direction: we pass our MME facing local address TLA = transp_layer_addr(mme_loc_addr), - Content#'E-RABToBeModifiedItemBearerModInd'{transportLayerAddress = TLA}; + {Content#'E-RABToBeModifiedItemBearerModInd'{transportLayerAddress = TLA}, S};
handle_ie(#'ProtocolIE-Field'{id = ?'id-E-RABNotToBeModifiedListBearerModInd', - value = Content}) -> + value = Content}, S) -> %% This IE contains a list of BearerModInd, so patch inner IEs - handle_ies(Content, ?'id-E-RABNotToBeModifiedItemBearerModInd'); + handle_ies(Content, ?'id-E-RABNotToBeModifiedItemBearerModInd', S);
handle_ie(#'ProtocolIE-Field'{id = ?'id-E-RABNotToBeModifiedItemBearerModInd', - value = Content}) -> + value = Content}, S) -> %% eNB -> MME direction: we pass our MME facing local address TLA = transp_layer_addr(mme_loc_addr), - Content#'E-RABNotToBeModifiedItemBearerModInd'{transportLayerAddress = TLA}; + {Content#'E-RABNotToBeModifiedItemBearerModInd'{transportLayerAddress = TLA}, S};
%% INITIAL CONTEXT SETUP REQUEST related IEs handle_ie(#'ProtocolIE-Field'{id = ?'id-E-RABToBeSetupListCtxtSUReq', - value = Content}) -> + value = Content}, S) -> %% This IE contains a list of CtxtSUReq, so patch inner IEs - handle_ies(Content, ?'id-E-RABToBeSetupItemCtxtSUReq'); + handle_ies(Content, ?'id-E-RABToBeSetupItemCtxtSUReq', S);
handle_ie(#'ProtocolIE-Field'{id = ?'id-E-RABToBeSetupItemCtxtSUReq', - value = Content}) -> + value = Content}, S) -> %% eNB -> MME direction: we pass our MME facing local address TLA = transp_layer_addr(mme_loc_addr), - Content#'E-RABToBeSetupItemCtxtSUReq'{transportLayerAddress = TLA}; + {Content#'E-RABToBeSetupItemCtxtSUReq'{transportLayerAddress = TLA}, S};
%% INITIAL CONTEXT SETUP RESPONSE related IEs handle_ie(#'ProtocolIE-Field'{id = ?'id-E-RABSetupListCtxtSURes', - value = Content}) -> + value = Content}, S) -> %% This IE contains a list of CtxtSURes, so patch inner IEs - handle_ies(Content, ?'id-E-RABSetupItemCtxtSURes'); + handle_ies(Content, ?'id-E-RABSetupItemCtxtSURes', S);
handle_ie(#'ProtocolIE-Field'{id = ?'id-E-RABSetupItemCtxtSURes', - value = Content}) -> + value = Content}, S) -> %% MME -> eNB direction: we pass our eNB facing local address TLA = transp_layer_addr(s1gw_bind_addr), - Content#'E-RABSetupItemCtxtSURes'{transportLayerAddress = TLA}; + {Content#'E-RABSetupItemCtxtSURes'{transportLayerAddress = TLA}, S};
%% Catch-all variant, which should not be called normally -handle_ie(#'ProtocolIE-Field'{value = Content} = IE) -> +handle_ie(#'ProtocolIE-Field'{value = Content} = IE, S) -> ?LOG_ERROR("[BUG] Unhandled S1AP IE: ~p", [IE]), - Content. + {Content, S}.
%% Iterate over the given list of 'ProtocolIE-Field' IEs, %% calling function handle_ie/1 for IEs matching the given IEI. --spec handle_ies(list(), integer()) -> list(). -handle_ies(IEs, IEI) -> - handle_ies([], IEs, IEI). +-spec handle_ies(list(), integer(), proxy_state()) -> {list(), proxy_state()}. +handle_ies(IEs, IEI, S) -> + handle_ies([], IEs, IEI, S).
-handle_ies(Acc, [IE | IEs], IEI) -> +handle_ies(Acc, [IE | IEs], IEI, S0) -> case IE of #'ProtocolIE-Field'{id = IEI} -> - NewIE = IE#'ProtocolIE-Field'{value = handle_ie(IE)}, - handle_ies([NewIE | Acc], IEs, IEI); + {Content, S1} = handle_ie(IE, S0), + NewIE = IE#'ProtocolIE-Field'{value = Content}, + handle_ies([NewIE | Acc], IEs, IEI, S1); _ -> - handle_ies([IE | Acc], IEs, IEI) + handle_ies([IE | Acc], IEs, IEI, S0) end;
-handle_ies(Acc, [], _) -> - lists:reverse(Acc). +handle_ies(Acc, [], _IEI, S) -> + {lists:reverse(Acc), S}.
%% GTP-U IP address (TransportLayerAddress) to be used while patching diff --git a/src/sctp_proxy.erl b/src/sctp_proxy.erl index ab147e6..148e00a 100644 --- a/src/sctp_proxy.erl +++ b/src/sctp_proxy.erl @@ -79,7 +79,8 @@ #{enb_aid => Aid, mme_addr => MmeAddr, mme_port => MmePort, - tx_queue => []}}. + tx_queue => [], + priv => s1ap_proxy:init()}}.
callback_mode() -> @@ -131,16 +132,16 @@
%% CONNECTED state -connected(enter, OldState, S) -> +connected(enter, OldState, S0) -> ?LOG_INFO("State change: ~p -> ~p", [OldState, ?FUNCTION_NAME]), %% Send pending eNB -> MME messages (if any) - ok = sctp_send_pending(S), - {keep_state, maps:remove(tx_queue, S)}; + S1 = sctp_send_pending(S0), + {keep_state, S1};
%% Handle an eNB -> MME data forwarding request (forward) -connected(cast, {send_data, Data}, S) -> - ok = sctp_send(S, Data), - {keep_state, S}; +connected(cast, {send_data, Data}, S0) -> + S1 = sctp_send(Data, S0), + {keep_state, S1};
%% Handle an #sctp_assoc_change event (MME connection state) connected(info, {sctp, _Socket, MmeAddr, MmePort, @@ -158,11 +159,13 @@
%% Handle an #sctp_sndrcvinfo event (MME -> eNB data) connected(info, {sctp, _Socket, MmeAddr, MmePort, - {[#sctp_sndrcvinfo{assoc_id = Aid}], Data}}, S) -> + {[#sctp_sndrcvinfo{assoc_id = Aid}], Data}}, + #{enb_aid := EnbAid, priv := Priv} = S) -> ?LOG_DEBUG("MME connection (id=~p, ~p:~p) -> eNB: ~p", [Aid, MmeAddr, MmePort, Data]), - sctp_server:send_data(maps:get(enb_aid, S), handle_pdu(Data)), - {keep_state, S}; + {NewData, NewPriv} = handle_pdu(Data, Priv), + sctp_server:send_data(EnbAid, NewData), + {keep_state, S#{priv := NewPriv}};
%% Catch-all for other kinds of SCTP events connected(info, {sctp, _Socket, MmeAddr, MmePort, @@ -185,7 +188,10 @@ terminate(Reason, State, S) -> ?LOG_NOTICE("Terminating in state ~p, reason ~p", [State, Reason]), case S of - #{sock := Sock, mme_aid := Aid} -> + #{sock := Sock, + mme_aid := Aid, + priv := Priv} -> + s1ap_proxy:deinit(Priv), sctp_client:disconnect({Sock, Aid}), gen_sctp:close(Sock); #{sock := Sock} -> @@ -199,31 +205,36 @@ %% ------------------------------------------------------------------
%% A safe wrapper for s1ap_proxy:handle_pdu/1 --spec handle_pdu(binary()) -> binary(). -handle_pdu(Data) when is_binary(Data) -> - try s1ap_proxy:handle_pdu(Data) of - NewData -> NewData +-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} catch Exception:Reason:StackTrace -> ?LOG_ERROR("An exception occurred: ~p, ~p, ~p", [Exception, Reason, StackTrace]), - Data %% proxy as-is + {Data, Priv} %% proxy as-is end.
%% Send a single message to the MME -sctp_send(#{sock := Sock, mme_aid := Aid}, Data) -> - sctp_client:send_data({Sock, Aid}, handle_pdu(Data)). +sctp_send(Data, + #{sock := Sock, + mme_aid := Aid, + priv := Priv} = S) -> + {NewData, NewPriv} = handle_pdu(Data, Priv), + ok = sctp_client:send_data({Sock, Aid}, NewData), + S#{priv := NewPriv}.
%% Send pending messages to the MME sctp_send_pending(#{tx_queue := Pending} = S) -> - sctp_send_pending(S, lists:reverse(Pending)). + sctp_send_pending(lists:reverse(Pending), S).
-sctp_send_pending(S, [Data | Pending]) -> - sctp_send(S, Data), - sctp_send_pending(S, Pending); +sctp_send_pending([Data | Pending], S0) -> + S1 = sctp_send(Data, S0), + sctp_send_pending(Pending, S1);
-sctp_send_pending(_S, []) -> - ok. +sctp_send_pending([], S) -> + S#{tx_queue := []}.
%% vim:set ts=4 sw=4 et: