pespin has submitted this change. ( https://gerrit.osmocom.org/c/erlang/osmo-epdg/+/35659?usp=email )
Change subject: Introduce ue_fsm ...................................................................... Introduce ue_fsm Decouple gsup_server from other protocols. Change-Id: I0c960c4c250458384ed706a99582ec52083019f6 --- M src/gsup_server.erl A src/ue_fsm.erl 2 files changed, 269 insertions(+), 70 deletions(-) Approvals: laforge: Looks good to me, but someone else must approve Jenkins Builder: Verified dexter: Looks good to me, but someone else must approve pespin: Looks good to me, approved diff --git a/src/gsup_server.erl b/src/gsup_server.erl index b8ed637..5fe227c 100644 --- a/src/gsup_server.erl +++ b/src/gsup_server.erl @@ -41,19 +41,28 @@ -include_lib("osmo_gsup/include/gsup_protocol.hrl"). -include_lib("gtplib/include/gtp_packet.hrl"). +-define(SERVER, ?MODULE). + -define(IPAC_PROTO_EXT_GSUP, {osmo, 5}). -record(gsups_state, { lsocket, % listening socket lport, % local port. only interesting if we bind with port 0 socket, % current active socket. we only support a single tcp connection - ccm_options % ipa ccm options + ccm_options, % ipa ccm options + ues = sets:new() + }). + +-record(gsups_ue, { + imsi :: binary(), + pid :: pid() }). -export([start_link/3]). -export([init/1, handle_call/3, handle_cast/2, handle_info/2]). -export([code_change/3, terminate/2]). +-export([auth_response/2, lu_response/2, tunnel_response/2]). % TODO: -spec dia_sip2gsup('SIP-Auth-Data-Item'()) -> #'GSUPAuthTuple'{}. dia_sip2gsup(#'SIP-Auth-Data-Item'{'SIP-Authenticate' = [Authenticate], 'SIP-Authorization' = [Authorization], @@ -71,7 +80,7 @@ %% ------------------------------------------------------------------ start_link(ServerAddr, ServerPort, Options) -> - gen_server:start_link(?MODULE, [ServerAddr, ServerPort, Options], [{debug, [trace]}]). + gen_server:start_link({local, ?SERVER}, ?MODULE, [ServerAddr, ServerPort, Options], [{debug, [trace]}]). %% ------------------------------------------------------------------ %% gen_server Function Definitions @@ -105,20 +114,76 @@ end. % send a given GSUP message and synchronously wait for message type ExpRes or ExpErr -handle_call({transceive_gsup, GsupMsgTx, ExpRes, ExpErr}, _From, State) -> - Socket = State#gsups_state.socket, - {ok, Imsi} = maps:find(imsi, GsupMsgTx), - ipa_proto:send(Socket, ?IPAC_PROTO_EXT_GSUP, GsupMsgTx), - % selective receive for only those GSUP responses we expect - receive - {ipa, Socket, ?IPAC_PROTO_EXT_GSUP, GsupMsgRx = #{message_type := ExpRes, imsi := Imsi}} -> - {reply, GsupMsgRx, State}; +handle_call(Info, _From, State) -> + error_logger:error_report(["unknown handle_call", {module, ?MODULE}, {info, Info}, {state, State}]), + {reply, error, not_implemented}. - {ipa, Socket, ?IPAC_PROTO_EXT_GSUP, GsupMsgRx = #{message_type := ExpErr, imsi := Imsi}} -> - {reply, GsupMsgRx, State} - after 5000 -> - {reply, timeout, State} - end. +handle_cast({auth_response, {Imsi, Auth}}, State) -> + lager:info("auth_response for ~p: ~p~n", [Imsi, Auth]), + Socket = State#gsups_state.socket, + case Auth of + {ok, Mar} -> SipAuthTuples = Mar#'MAA'.'SIP-Auth-Data-Item', + % AuthTuples = dia_sip2gsup(SipAuthTuples), + Resp = #{message_type => send_auth_info_res, + message_class => 5, + imsi => list_to_binary(Mar#'MAA'.'User-Name'), + auth_tuples => lists:map(fun dia_sip2gsup/1, SipAuthTuples) + }; + {error, _} -> Resp = #{message_type => send_auth_info_err, imsi => Imsi, message_class => 5, cause => 16#11} + end, + lager:info("GSUP: Tx ~p~n", [Resp]), + ipa_proto:send(Socket, ?IPAC_PROTO_EXT_GSUP, Resp), + {noreply, State}; + +handle_cast({lu_response, {Imsi, Result}}, State) -> + lager:info("lu_response for ~p: ~p~n", [Imsi, Result]), + Socket = State#gsups_state.socket, + case Result of + {ok, _Sar} -> Resp = #{message_type => location_upd_res, + imsi => Imsi, + message_class => 5 + }; + {error, _} -> Resp = #{message_type => location_upd_err, + imsi => Imsi, + message_class => 5, + cause => 16#11 % FIXME: Use proper defines as cause code and use Network failure + } + end, + lager:info("GSUP: Tx ~p~n", [Resp]), + ipa_proto:send(Socket, ?IPAC_PROTO_EXT_GSUP, Resp), + {noreply, State}; + +handle_cast({tunnel_response, {Imsi, Result}}, State) -> + lager:info("tunnel_response for ~p: ~p~n", [Imsi, Result]), + Socket = State#gsups_state.socket, + case Result of + {ok, #gtp{version = v2, type = create_session_response}} -> + {ok, CreateSessResp} = Result, + IEs = CreateSessResp#gtp.ie, + %%#{{v2_bearer_context,0} := BearerMap} = IEs, + #{{v2_pdn_address_allocation,0} := Paa} = IEs, + PdpAddress = #{pdp_type_org => 1, pdp_type_nr => 16#21, address => #{ ipv4 => Paa#v2_pdn_address_allocation.address}}, + PdpInfo = #{pdp_context_id => 0, + pdp_address => PdpAddress, + access_point_name => "foobar.apn", + quality_of_service => <<0, 0, 0>>, + pdp_charging => 0}, + Resp = #{message_type => epdg_tunnel_result, + imsi => Imsi, + message_class => 5, + pdp_info_complete => true, + pdp_info_list => [PdpInfo] + }; + {error, _} -> + Resp = #{message_type => epdg_tunnel_error, + imsi => Imsi, + message_class => 5, + cause => 16#11 % FIXME: Use proper defines as cause code and use Network failure + } + end, + lager:info("GSUP: Tx ~p~n", [Resp]), + ipa_proto:send(Socket, ?IPAC_PROTO_EXT_GSUP, Resp), + {noreply, State}; handle_cast(Info, S) -> error_logger:error_report(["unknown handle_cast", {module, ?MODULE}, {info, Info}, {state, S}]), @@ -140,74 +205,47 @@ {noreply, S#gsups_state{socket=Socket}}; % send auth info / requesting authentication tuples -handle_info({ipa, Socket, ?IPAC_PROTO_EXT_GSUP, GsupMsgRx = #{message_type := send_auth_info_req, imsi := Imsi}}, S) -> - Auth = auth_handler:auth_request(Imsi), - case Auth of - {ok, Mar} -> SipAuthTuples = Mar#'MAA'.'SIP-Auth-Data-Item', - % AuthTuples = dia_sip2gsup(SipAuthTuples), - Resp = #{message_type => send_auth_info_res, - message_class => 5, - imsi => list_to_binary(Mar#'MAA'.'User-Name'), - auth_tuples => lists:map(fun dia_sip2gsup/1, SipAuthTuples) - }; - {error, _} -> Resp = #{message_type => send_auth_info_err, imsi => Imsi, message_class => 5, cause => 16#11} - end, - lager:info("auth tuples: ~p ~n", [Resp]), - ipa_proto:send(Socket, ?IPAC_PROTO_EXT_GSUP, Resp), - {noreply, S}; +handle_info({ipa, _Socket, ?IPAC_PROTO_EXT_GSUP, _GsupMsgRx = #{message_type := send_auth_info_req, imsi := Imsi}}, State0) -> + {UE, State1} = find_or_new_gsups_ue(Imsi, State0), + ue_fsm:auth_request(UE#gsups_ue.pid), + {noreply, State1}; % location update request / when a UE wants to connect to a specific APN. This will trigger a AAA->HLR Request Server Assignment Request % FIXME: add APN instead of hardcoded internet -handle_info({ipa, Socket, ?IPAC_PROTO_EXT_GSUP, GsupMsgRx = #{message_type := location_upd_req, imsi := Imsi}}, S) -> - % FIXME: use enum for Server-Assignment-Type => REGISTERING - Result = epdg_diameter_swx:server_assignment_request(Imsi, 1, "internet"), - case Result of - {ok, Sar} -> Resp = #{message_type => location_upd_res, - imsi => Imsi, - message_class => 5 - }; - {error, _} -> Resp = #{message_type => location_upd_err, - imsi => Imsi, - message_class => 5, - cause => 16#11 % FIXME: Use proper defines as cause code and use Network failure - } +handle_info({ipa, Socket, ?IPAC_PROTO_EXT_GSUP, _GsupMsgRx = #{message_type := location_upd_req, imsi := Imsi}}, State) -> + UE = find_gsups_ue_by_imsi(Imsi, State), + case UE of + #gsups_ue{imsi = Imsi} -> + ue_fsm:lu_request(UE#gsups_ue.pid); + undefined -> + Resp = #{message_type => location_upd_err, + imsi => Imsi, + message_class => 5, + cause => 16#11 % FIXME: Use proper defines as cause code and use Network failure + }, + lager:info("GSUP: Tx ~p~n", [Resp]), + ipa_proto:send(Socket, ?IPAC_PROTO_EXT_GSUP, Resp) end, - ipa_proto:send(Socket, ?IPAC_PROTO_EXT_GSUP, Resp), - {noreply, S}; + {noreply, State}; % epdg tunnel request / trigger the establishment to the PGW and prepares everything for the user traffic to flow % When sending a epdg_tunnel_response everything must be ready for the UE traffic -handle_info({ipa, Socket, ?IPAC_PROTO_EXT_GSUP, GsupMsgRx = #{message_type := epdg_tunnel_request, imsi := Imsi}}, S) -> +handle_info({ipa, Socket, ?IPAC_PROTO_EXT_GSUP, GsupMsgRx = #{message_type := epdg_tunnel_request, imsi := Imsi}}, State) -> lager:info("GSUP: Rx ~p~n", [GsupMsgRx]), - Result = epdg_gtpc_s2b:create_session_req(Imsi), - case Result of - {ok, #gtp{version = v2, type = create_session_response}} -> - {ok, CreateSessResp} = Result, - IEs = CreateSessResp#gtp.ie, - %%#{{v2_bearer_context,0} := BearerMap} = IEs, - #{{v2_pdn_address_allocation,0} := Paa} = IEs, - PdpAddress = #{pdp_type_org => 1, pdp_type_nr => 16#21, address => #{ ipv4 => Paa#v2_pdn_address_allocation.address}}, - PdpInfo = #{pdp_context_id => 0, - pdp_address => PdpAddress, - access_point_name => "foobar.apn", - quality_of_service => <<0, 0, 0>>, - pdp_charging => 0}, - Resp = #{message_type => epdg_tunnel_result, - imsi => Imsi, - message_class => 5, - pdp_info_complete => true, - pdp_info_list => [PdpInfo] - }; - {error, _} -> + UE = find_gsups_ue_by_imsi(Imsi, State), + case UE of + #gsups_ue{imsi = Imsi} -> + ue_fsm:tunnel_request(UE#gsups_ue.pid); + undefined -> Resp = #{message_type => epdg_tunnel_error, imsi => Imsi, message_class => 5, cause => 16#11 % FIXME: Use proper defines as cause code and use Network failure - } + }, + lager:info("GSUP: Tx ~p~n", [Resp]), + ipa_proto:send(Socket, ?IPAC_PROTO_EXT_GSUP, Resp) end, - lager:info("GSUP: Tx ~p~n", [Resp]), - ipa_proto:send(Socket, ?IPAC_PROTO_EXT_GSUP, Resp), - {noreply, S}; + {noreply, State}; handle_info(Info, S) -> error_logger:error_report(["unknown handle_info", {module, ?MODULE}, {info, Info}, {state, S}]), @@ -218,3 +256,43 @@ code_change(_OldVsn, State, _Extra) -> {ok, State}. + +auth_response(Imsi, Auth) -> + lager:info("auth_response(~p): ~p~n", [Imsi, Auth]), + gen_server:cast(?SERVER, {auth_response, {Imsi, Auth}}). + +lu_response(Imsi, Result) -> + lager:info("lu_response(~p): ~p~n", [Imsi, Result]), + gen_server:cast(?SERVER, {lu_response, {Imsi, Result}}). + +tunnel_response(Imsi, Result) -> + lager:info("tunnel_response(~p): ~p~n", [Imsi, Result]), + gen_server:cast(?SERVER, {tunnel_response, {Imsi, Result}}). + +%% ------------------------------------------------------------------ +%% Internal Function Definitions +%% ------------------------------------------------------------------ + +new_gsups_ue(Imsi, State) -> + {ok, Pid} = ue_fsm:start_link(Imsi), + UE = #gsups_ue{imsi = Imsi, pid = Pid}, + NewSt = State#gsups_state{ues = sets:add_element(UE, State#gsups_state.ues)}, + {UE, NewSt}. + +% returns gsups_ue if found, undefined it not +find_gsups_ue_by_imsi(Imsi, State) -> + sets:fold( + fun(UEsIt = #gsups_ue{imsi = Imsi}, _AccIn) -> UEsIt; + (_, AccIn) -> AccIn + end, + undefined, + State#gsups_state.ues). + +find_or_new_gsups_ue(Imsi, State) -> + UE = find_gsups_ue_by_imsi(Imsi, State), + case UE of + #gsups_ue{imsi = Imsi} -> + {UE, State}; + undefined -> + new_gsups_ue(Imsi, State) + end. \ No newline at end of file diff --git a/src/ue_fsm.erl b/src/ue_fsm.erl new file mode 100644 index 0000000..0fe6992 --- /dev/null +++ b/src/ue_fsm.erl @@ -0,0 +1,110 @@ +% UE FSM +% (C) 2023 by sysmocom +% +% All Rights Reserved +% +% This program is free software; you can redistribute it and/or modify +% it under the terms of the GNU Affero General Public License as +% published by the Free Software Foundation; either version 3 of the +% License, or (at your option) any later version. +% +% This program is distributed in the hope that it will be useful, +% but WITHOUT ANY WARRANTY; without even the implied warranty of +% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +% GNU General Public License for more details. +% +% You should have received a copy of the GNU Affero General Public License +% along with this program. If not, see <http://www.gnu.org/licenses/>. +% +% Additional Permission under GNU AGPL version 3 section 7: +% +% If you modify this Program, or any covered work, by linking or +% combining it with runtime libraries of Erlang/OTP as released by +% Ericsson on http://www.erlang.org (or a modified version of these +% libraries), containing parts covered by the terms of the Erlang Public +% License (http://www.erlang.org/EPLICENSE), the licensors of this +% Program grant you additional permission to convey the resulting work +% without the need to license the runtime libraries of Erlang/OTP under +% the GNU Affero General Public License. Corresponding Source for a +% non-source form of such a combination shall include the source code +% for the parts of the runtime libraries of Erlang/OTP used as well as +% that of the covered work. + +-module(ue_fsm). +-behaviour(gen_statem). +-define(NAME, ue_fsm). + +-export([start_link/1]). +-export([init/1,callback_mode/0,terminate/3]). +-export([auth_request/1, lu_request/1, tunnel_request/1]). +-export([state_new/3,state_authenticated/3]). + +-record(ue_fsm_data, { + imsi + }). + +start_link(Imsi) -> + ServerName = lists:concat([?NAME, "_", binary_to_list(Imsi)]), + lager:info("ue_fsm start_link(~p)~n", [ServerName]), + gen_statem:start_link({local, list_to_atom(ServerName)}, ?MODULE, Imsi, [{debug, [trace]}]). + +auth_request(Pid) -> + lager:info("ue_fsm auth_request~n", []), + gen_statem:cast(Pid, auth_request). + +lu_request(Pid) -> + lager:info("ue_fsm lu_request~n", []), + gen_statem:cast(Pid, lu_request). + +tunnel_request(Pid) -> + lager:info("ue_fsm tunnel_request~n", []), + gen_statem:cast(Pid, tunnel_request). + +init(Imsi) -> + lager:info("ue_fsm init(~p)~n", [Imsi]), + Data = #ue_fsm_data{imsi = Imsi}, + {ok, state_new, Data}. + +callback_mode() -> + state_functions. + +terminate(Reason, State, Data) -> + lager:info("terminating ~p with reason ~p state=~p, ~p~n", [?MODULE, Reason, State, Data]), + ok. + +state_new(cast, auth_request, Data) -> + lager:info("ue_fsm state_new event=auth_request, ~p~n", [Data]), + Auth = auth_handler:auth_request(Data#ue_fsm_data.imsi), + gsup_server:auth_response(Data#ue_fsm_data.imsi, Auth), + case Auth of + {ok, _} -> + {next_state, state_authenticated, Data}; + {error, Err} -> + {stop, Err, Data} + end. + +state_authenticated(cast, lu_request, Data) -> + lager:info("ue_fsm state_authenticated event=lu_request, ~p~n", [Data]), + Result = epdg_diameter_swx:server_assignment_request(Data#ue_fsm_data.imsi, 1, "internet"), + gsup_server:lu_response(Data#ue_fsm_data.imsi, Result), + case Result of + {ok, _} -> + {keep_state, Data}; + {error, Err} -> + {stop, Err, Data} + end; + +state_authenticated(cast, tunnel_request, Data) -> + lager:info("ue_fsm state_authenticated event=tunnel_request, ~p~n", [Data]), + Result = epdg_gtpc_s2b:create_session_req(Data#ue_fsm_data.imsi), + gsup_server:tunnel_response(Data#ue_fsm_data.imsi, Result), + case Result of + {ok, _} -> + {keep_state, Data}; + {error, Err} -> + {stop, Err, Data} + end; + +state_authenticated(cast, _Whatever, Data) -> + lager:info("ue_fsm state_authenticated event=auth_request, ~p~n", [Data]), + {keep_state, Data}. \ No newline at end of file -- To view, visit https://gerrit.osmocom.org/c/erlang/osmo-epdg/+/35659?usp=email To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings Gerrit-Project: erlang/osmo-epdg Gerrit-Branch: master Gerrit-Change-Id: I0c960c4c250458384ed706a99582ec52083019f6 Gerrit-Change-Number: 35659 Gerrit-PatchSet: 1 Gerrit-Owner: pespin <pes...@sysmocom.de> Gerrit-Reviewer: Jenkins Builder Gerrit-Reviewer: dexter <pma...@sysmocom.de> Gerrit-Reviewer: laforge <lafo...@osmocom.org> Gerrit-Reviewer: pespin <pes...@sysmocom.de> Gerrit-MessageType: merged