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