373 lines
16 KiB
Erlang
373 lines
16 KiB
Erlang
% 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(aaa_ue_fsm).
|
|
-behaviour(gen_statem).
|
|
-define(NAME, aaa_ue_fsm).
|
|
|
|
-include_lib("diameter/include/diameter.hrl").
|
|
-include_lib("diameter_3gpp_ts29_229.hrl").
|
|
-include_lib("diameter_3gpp_ts29_273_s6b.hrl").
|
|
-include("conv.hrl").
|
|
|
|
-export([start/1, stop/1]).
|
|
-export([init/1,callback_mode/0,terminate/3]).
|
|
-export([get_server_name_by_imsi/1, get_pid_by_imsi/1]).
|
|
-export([ev_swm_auth_req/2, ev_swm_auth_compl/2, ev_rx_swm_str/1, ev_rx_swm_asa/1,
|
|
ev_rx_swx_maa/2, ev_rx_swx_saa/2, ev_rx_swx_rtr/1,
|
|
ev_rx_s6b_aar/2, ev_rx_s6b_str/1, ev_rx_s6b_asa/2]).
|
|
-export([state_new/3,
|
|
state_wait_swx_maa/3,
|
|
state_wait_swx_saa/3,
|
|
state_authenticated/3,
|
|
state_authenticated_wait_swx_saa/3,
|
|
state_dereg_net_initiated_wait_s6b_asa/3,
|
|
state_dereg_net_initiated_wait_swm_asa/3]).
|
|
|
|
-define(TIMEOUT_VAL_WAIT_S6b_ANSWER, 10000).
|
|
-define(TIMEOUT_VAL_WAIT_SWm_ANSWER, 10000).
|
|
|
|
-record(ue_fsm_data, {
|
|
imsi = unknown :: string(),
|
|
apn :: string(),
|
|
epdg_sess_active = false :: boolean(),
|
|
pgw_sess_active = false :: boolean(),
|
|
s6b_resp_pid :: pid()
|
|
}).
|
|
|
|
get_server_name_by_imsi(Imsi) ->
|
|
ServerName = lists:concat([?NAME, "_", Imsi]),
|
|
list_to_atom(ServerName).
|
|
|
|
get_pid_by_imsi(Imsi) ->
|
|
ServerName = get_server_name_by_imsi(Imsi),
|
|
whereis(ServerName).
|
|
|
|
start(Imsi) ->
|
|
ServerName = get_server_name_by_imsi(Imsi),
|
|
lager:info("ue_fsm start_link(~p)~n", [ServerName]),
|
|
gen_statem:start({local, ServerName}, ?MODULE, Imsi, [{debug, [trace]}]).
|
|
|
|
stop(SrvRef) ->
|
|
try
|
|
gen_statem:stop(SrvRef)
|
|
catch
|
|
exit:Err ->
|
|
{error, Err}
|
|
end.
|
|
|
|
ev_swm_auth_req(Pid, {PdpTypeNr, Apn, EAP}) ->
|
|
lager:info("ue_fsm ev_swm_auth_req~n", []),
|
|
try
|
|
gen_statem:call(Pid, {swm_auth_req, PdpTypeNr, Apn, EAP})
|
|
catch
|
|
exit:Err ->
|
|
{error, Err}
|
|
end.
|
|
|
|
ev_swm_auth_compl(Pid, Apn) ->
|
|
lager:info("ue_fsm ev_swm_auth_compl~n", []),
|
|
try
|
|
gen_statem:call(Pid, {swm_auth_compl, Apn})
|
|
catch
|
|
exit:Err ->
|
|
{error, Err}
|
|
end.
|
|
|
|
ev_rx_swm_str(Pid) ->
|
|
lager:info("ue_fsm ev_rx_swm_str~n", []),
|
|
try
|
|
gen_statem:call(Pid, rx_swm_str)
|
|
catch
|
|
exit:Err ->
|
|
{error, Err}
|
|
end.
|
|
|
|
ev_rx_swm_asa(Pid) ->
|
|
lager:info("ue_fsm ev_rx_swm_asa~n", []),
|
|
try
|
|
gen_statem:call(Pid, rx_swm_asa)
|
|
catch
|
|
exit:Err ->
|
|
{error, Err}
|
|
end.
|
|
|
|
ev_rx_swx_maa(Pid, Result) ->
|
|
lager:info("ue_fsm ev_rx_swx_maa~n", []),
|
|
try
|
|
gen_statem:call(Pid, {rx_swx_maa, Result})
|
|
catch
|
|
exit:Err ->
|
|
{error, Err}
|
|
end.
|
|
|
|
ev_rx_swx_saa(Pid, Result) ->
|
|
lager:info("ue_fsm ev_rx_swx_saa~n", []),
|
|
try
|
|
gen_statem:call(Pid, {rx_swx_saa, Result})
|
|
catch
|
|
exit:Err ->
|
|
{error, Err}
|
|
end.
|
|
|
|
ev_rx_swx_rtr(Pid) ->
|
|
lager:info("ue_fsm ev_rx_swx_rtr~n", []),
|
|
try
|
|
gen_statem:call(Pid, rx_swx_rtr)
|
|
catch
|
|
exit:Err ->
|
|
{error, Err}
|
|
end.
|
|
|
|
ev_rx_s6b_aar(Pid, {Apn, AgentInfoOpt}) ->
|
|
lager:info("ue_fsm ev_rx_s6b_aar: ~p ~p~n", [Apn, AgentInfoOpt]),
|
|
try
|
|
gen_statem:call(Pid, {rx_s6b_aar, Apn, AgentInfoOpt})
|
|
catch
|
|
exit:Err ->
|
|
{error, Err}
|
|
end.
|
|
|
|
ev_rx_s6b_asa(Pid, Result) ->
|
|
lager:info("ue_fsm ev_rx_s6b_asa: ~p~n", [Result]),
|
|
try
|
|
gen_statem:call(Pid, {rx_s6b_asa, Result})
|
|
catch
|
|
exit:Err ->
|
|
{error, Err}
|
|
end.
|
|
|
|
ev_rx_s6b_str(Pid) ->
|
|
lager:info("ue_fsm ev_rx_s6b_str~n", []),
|
|
try
|
|
gen_statem:call(Pid, rx_s6b_str)
|
|
catch
|
|
exit:Err ->
|
|
{error, Err}
|
|
end.
|
|
|
|
%% ------------------------------------------------------------------
|
|
%% Internal helpers
|
|
%% ------------------------------------------------------------------
|
|
|
|
%% ------------------------------------------------------------------
|
|
%% gen_statem Function Definitions
|
|
%% ------------------------------------------------------------------
|
|
|
|
init(Imsi) ->
|
|
lager:info("ue_fsm init(~p)~n", [Imsi]),
|
|
Data = #ue_fsm_data{imsi = Imsi},
|
|
{ok, state_new, Data}.
|
|
|
|
callback_mode() ->
|
|
[state_functions, state_enter].
|
|
|
|
terminate(Reason, State, Data) ->
|
|
lager:info("terminating ~p with reason ~p state=~p, ~p~n", [?MODULE, Reason, State, Data]),
|
|
ok.
|
|
|
|
state_new(enter, _OldState, Data) ->
|
|
{keep_state, Data};
|
|
|
|
state_new({call, From}, {swm_auth_req, PdpTypeNr, Apn, EAP}, Data) ->
|
|
lager:info("ue_fsm state_new event=swm_auth_req {~p, ~p, ~p}, ~p~n", [PdpTypeNr, Apn, EAP, Data]),
|
|
case maps:find(authorization, EAP) of
|
|
{ok, Authorization} when is_binary(Authorization) -> Authorization;
|
|
error -> Authorization = []
|
|
end,
|
|
case aaa_diameter_swx:multimedia_auth_request(Data#ue_fsm_data.imsi, 1, 1, "EAP-AKA", PdpTypeNr, Authorization) of
|
|
ok -> {next_state, state_wait_swx_maa, Data, [{reply,From,ok}]};
|
|
{error, Err} -> {keep_state, Data, [{reply,From,{error, Err}}]}
|
|
end;
|
|
|
|
state_new({call, From}, {swm_auth_compl, Apn}, Data) ->
|
|
lager:info("ue_fsm state_new event=swm_auth_compl, ~p~n", [Data]),
|
|
case aaa_diameter_swx:server_assignment_request(Data#ue_fsm_data.imsi, 1, Apn, []) of
|
|
ok -> {next_state, state_wait_swx_saa, Data, [{reply,From,ok}]};
|
|
{error, Err} -> {keep_state, Data, [{reply,From,{error, Err}}]}
|
|
end.
|
|
|
|
state_wait_swx_maa(enter, _OldState, Data) ->
|
|
{keep_state, Data};
|
|
|
|
state_wait_swx_maa({call, From}, {rx_swx_maa, Result}, Data) ->
|
|
lager:info("ue_fsm state_wait_swx_maa event=rx_swx_maa, ~p~n", [Data]),
|
|
aaa_diameter_swm:auth_response(Data#ue_fsm_data.imsi, Result),
|
|
{next_state, state_new, Data, [{reply,From,ok}]}.
|
|
|
|
state_wait_swx_saa(enter, _OldState, Data) ->
|
|
{keep_state, Data};
|
|
|
|
state_wait_swx_saa({call, From}, {rx_swx_saa, Result}, Data) ->
|
|
lager:info("ue_fsm state_wait_swx_saa event=rx_swx_saa ~p, ~p~n", [Result, Data]),
|
|
case Result of
|
|
{error, _SAType, DiaRC} ->
|
|
aaa_diameter_swm:auth_compl_response(Data#ue_fsm_data.imsi, {error, DiaRC}),
|
|
{next_state, state_new, Data, [{reply,From,ok}]};
|
|
{ok, _SAType, ResInfo} ->
|
|
aaa_diameter_swm:auth_compl_response(Data#ue_fsm_data.imsi, {ok, ResInfo}),
|
|
{next_state, state_authenticated, Data, [{reply,From,ok}]}
|
|
end.
|
|
|
|
state_authenticated(enter, _OldState, Data) ->
|
|
% Mark ePDG session as active:
|
|
Data1 = Data#ue_fsm_data{epdg_sess_active = true},
|
|
{keep_state, Data1};
|
|
|
|
state_authenticated({call, {Pid, _Tag} = From}, {rx_s6b_aar, Apn, AgentInfoOpt}, Data) ->
|
|
lager:info("ue_fsm state_authenticated event=rx_s6b_aar Apn=~p AgentInfo=~p, ~p~n", [Apn, AgentInfoOpt, Data]),
|
|
case aaa_diameter_swx:server_assignment_request(Data#ue_fsm_data.imsi,
|
|
?'DIAMETER_CX_SERVER-ASSIGNMENT-TYPE_PGW_UPDATE',
|
|
Apn, AgentInfoOpt) of
|
|
ok -> Data1 = Data#ue_fsm_data{s6b_resp_pid = Pid, apn = Apn},
|
|
{next_state, state_authenticated_wait_swx_saa, Data1, [{reply,From,ok}]};
|
|
{error, Err} -> {keep_state, Data, [{reply,From,{error, Err}}]}
|
|
end;
|
|
|
|
state_authenticated({call, From}, rx_swm_str, Data) ->
|
|
lager:info("ue_fsm state_authenticated event=rx_swm_str, ~p~n", [Data]),
|
|
case {Data#ue_fsm_data.epdg_sess_active, Data#ue_fsm_data.pgw_sess_active} of
|
|
{false, _} -> %% The SWm session is not active...
|
|
DiaRC = 5002, %% UNKNOWN_SESSION_ID
|
|
{keep_state, Data, [{reply,From,{error, DiaRC}}]};
|
|
{true, true} -> %% The other session is still active, no need to send SAR Type=USER_DEREGISTRATION
|
|
lager:info("ue_fsm state_authenticated event=rx_swm_str: PGW session still active, skip updating the HSS~n", []),
|
|
Data1 = Data#ue_fsm_data{epdg_sess_active = false},
|
|
{keep_state, Data1, [{reply,From,{ok, 2001}}]};
|
|
{true, false} -> %% All sessions will now be gone, trigger SAR Type=USER_DEREGISTRATION
|
|
case aaa_diameter_swx:server_assignment_request(Data#ue_fsm_data.imsi,
|
|
?'DIAMETER_CX_SERVER-ASSIGNMENT-TYPE_USER_DEREGISTRATION',
|
|
Data#ue_fsm_data.apn, []) of
|
|
ok -> {next_state, state_authenticated_wait_swx_saa, Data, [{reply,From,ok}]};
|
|
{error, _Err} ->
|
|
DiaRC = 5002, %% UNKNOWN_SESSION_ID
|
|
{keep_state, Data, [{reply,From,{error, DiaRC}}]}
|
|
end
|
|
end;
|
|
|
|
state_authenticated({call, {Pid, _Tag} = From}, rx_s6b_str, Data) ->
|
|
lager:info("ue_fsm state_authenticated event=rx_s6b_str, ~p~n", [Data]),
|
|
case {Data#ue_fsm_data.pgw_sess_active, Data#ue_fsm_data.epdg_sess_active} of
|
|
{false, _} -> %% The S6b session is not active...
|
|
DiaRC = #epdg_dia_rc{result_code = 5002}, %% UNKNOWN_SESSION_ID
|
|
{keep_state, Data, [{reply,From,{error, DiaRC}}]};
|
|
{true, true} -> %% The other session is still active, no need to send SAR Type=USER_DEREGISTRATION
|
|
lager:info("ue_fsm state_authenticated event=rx_s6b_str: ePDG session still active, skip updating the HSS~n", []),
|
|
Data1 = Data#ue_fsm_data{pgw_sess_active = false},
|
|
DiaRC = #epdg_dia_rc{result_code = 2001}, %% SUCCESS
|
|
{keep_state, Data1, [{reply,From,{ok, DiaRC}}]};
|
|
{true, false} -> %% All sessions will now be gone, trigger SAR Type=USER_DEREGISTRATION
|
|
case aaa_diameter_swx:server_assignment_request(Data#ue_fsm_data.imsi,
|
|
?'DIAMETER_CX_SERVER-ASSIGNMENT-TYPE_USER_DEREGISTRATION',
|
|
Data#ue_fsm_data.apn, []) of
|
|
ok -> Data1 = Data#ue_fsm_data{s6b_resp_pid = Pid},
|
|
{next_state, state_authenticated_wait_swx_saa, Data1, [{reply,From,ok}]};
|
|
{error, _Err} ->
|
|
DiaRC = #epdg_dia_rc{result_code = 5002}, %% UNKNOWN_SESSION_ID
|
|
{keep_state, Data, [{reply,From,{error, DiaRC}}]}
|
|
end
|
|
end;
|
|
|
|
state_authenticated({call, _From}, {swm_auth_req, PdpTypeNr, Apn, EAP}, Data) ->
|
|
lager:info("ue_fsm state_authenticated event=swm_auth_req {~p, ~p, ~p}, ~p~n", [PdpTypeNr, Apn, EAP, Data]),
|
|
{next_state, state_new, Data, [postpone]};
|
|
|
|
state_authenticated({call, From}, rx_swx_rtr, Data) ->
|
|
lager:info("ue_fsm state_authenticated event=rx_swx_rtr ~p~n", [Data]),
|
|
case {Data#ue_fsm_data.pgw_sess_active, Data#ue_fsm_data.epdg_sess_active} of
|
|
{true, _} -> {next_state, state_dereg_net_initiated_wait_s6b_asa, Data, [{reply,From,ok}]};
|
|
{false, _} -> {next_state, state_dereg_net_initiated_wait_s6b_asa, Data, [{reply,From,ok}]} %% TODO: proper state for s6b
|
|
end;
|
|
|
|
state_authenticated({call, From}, Ev, Data) ->
|
|
lager:info("ue_fsm state_authenticated: Unexpected call event ~p, ~p~n", [Ev, Data]),
|
|
{keep_state, Data, [{reply,From,ok}]}.
|
|
|
|
state_authenticated_wait_swx_saa(enter, _OldState, Data) ->
|
|
{keep_state, Data};
|
|
|
|
state_authenticated_wait_swx_saa({call, From}, {rx_swx_saa, Result}, Data) ->
|
|
case Result of
|
|
{error, SAType, DiaRC} -> DiaRC;
|
|
{ok, SAType, _ResInfo} -> DiaRC = #epdg_dia_rc{result_code = 2001}
|
|
end,
|
|
lager:info("ue_fsm state_authenticated_wait_swx_saa event=rx_swx_saa SAType=~p ResulCode=~p, ~p~n", [SAType, DiaRC, Data]),
|
|
case SAType of
|
|
?'DIAMETER_CX_SERVER-ASSIGNMENT-TYPE_PGW_UPDATE' ->
|
|
aaa_diameter_s6b:tx_aa_answer(Data#ue_fsm_data.s6b_resp_pid, DiaRC),
|
|
Data1 = Data#ue_fsm_data{pgw_sess_active = true, s6b_resp_pid = undefined},
|
|
{next_state, state_authenticated, Data1, [{reply,From,ok}]};
|
|
?'DIAMETER_CX_SERVER-ASSIGNMENT-TYPE_USER_DEREGISTRATION' ->
|
|
case Data#ue_fsm_data.s6b_resp_pid of
|
|
undefined -> %% SWm initiated
|
|
aaa_diameter_swm:session_termination_answer(Data#ue_fsm_data.imsi, DiaRC),
|
|
Data1 = Data#ue_fsm_data{epdg_sess_active = false},
|
|
{next_state, state_new, Data1, [{reply,From,ok}]};
|
|
_ -> %% S6b initiated
|
|
aaa_diameter_s6b:tx_st_answer(Data#ue_fsm_data.s6b_resp_pid, DiaRC),
|
|
Data1 = Data#ue_fsm_data{pgw_sess_active = false, s6b_resp_pid = undefined},
|
|
{next_state, state_new, Data1, [{reply,From,ok}]}
|
|
end
|
|
end.
|
|
|
|
%% HSS asked us to do deregistration towards the user.
|
|
%% Transmit S6b ASR towards PGW and wait for ASA back.
|
|
state_dereg_net_initiated_wait_s6b_asa(enter, _OldState, Data) ->
|
|
aaa_diameter_s6b:tx_as_request(Data#ue_fsm_data.imsi),
|
|
{keep_state, Data, {state_timeout,?TIMEOUT_VAL_WAIT_S6b_ANSWER,s6b_asa_timeout}};
|
|
|
|
state_dereg_net_initiated_wait_s6b_asa({call, From}, {rx_s6b_asa, _Result}, Data) ->
|
|
{next_state, state_dereg_net_initiated_wait_swm_asa, Data, [{reply,From,ok}]};
|
|
|
|
state_dereg_net_initiated_wait_s6b_asa({call, From}, Ev, Data) ->
|
|
lager:info("ue_fsm state_dereg_net_initiated_wait_s6b_asa: Unexpected call event ~p, ~p~n", [Ev, Data]),
|
|
{keep_state, Data, [{reply,From,ok}]};
|
|
|
|
state_dereg_net_initiated_wait_s6b_asa(state_timeout, s6b_asa_timeout, Data) ->
|
|
{next_state, state_dereg_net_initiated_wait_swm_asa, Data}.
|
|
|
|
%% HSS asked us to do deregistration towards the user.
|
|
%% S6b (PGW) was already torn down. Now transmit SWm ASR towards ePDG and wait for ASA back.
|
|
state_dereg_net_initiated_wait_swm_asa(enter, _OldState, Data) ->
|
|
aaa_diameter_swm:tx_as_request(Data#ue_fsm_data.imsi),
|
|
{keep_state, Data, {state_timeout,?TIMEOUT_VAL_WAIT_SWm_ANSWER,swm_asa_timeout}};
|
|
|
|
state_dereg_net_initiated_wait_swm_asa({call, From}, {rx_swm_asa, _Result}, Data) ->
|
|
{stop_and_reply, normal, [{reply,From,ok}], Data};
|
|
|
|
state_dereg_net_initiated_wait_swm_asa({call, From}, Ev, Data) ->
|
|
lager:info("ue_fsm state_dereg_net_initiated_wait_swm_asa: Unexpected call event ~p, ~p~n", [Ev, Data]),
|
|
{keep_state, Data, [{reply,From,ok}]};
|
|
|
|
state_dereg_net_initiated_wait_swm_asa(state_timeout, swm_asa_timeout, _Data) ->
|
|
{stop, normal}.
|