238 lines
7.6 KiB
Erlang
238 lines
7.6 KiB
Erlang
% ePDG implementation of SWm Diameter interface, TS 29.273 section 7
|
|
% This interface is so far implemented through internal erlang messages against
|
|
% the internal AAA Server.
|
|
-module(epdg_diameter_swm).
|
|
-behaviour(gen_server).
|
|
|
|
-include_lib("diameter_3gpp_ts29_273_swx.hrl").
|
|
|
|
-record(swm_state, {
|
|
sessions = sets:new()
|
|
}).
|
|
|
|
-record(swm_session, {
|
|
imsi :: string(),
|
|
pid :: pid()
|
|
}).
|
|
|
|
-export([start_link/0]).
|
|
-export([init/1, handle_call/3, handle_cast/2, handle_info/2]).
|
|
-export([code_change/3, terminate/2]).
|
|
|
|
-export([auth_request/4, auth_compl_request/2,
|
|
session_termination_request/1, abort_session_answer/1]).
|
|
-export([auth_response/2, auth_compl_response/2,
|
|
session_termination_answer/2, abort_session_request/1]).
|
|
|
|
-define(SERVER, ?MODULE).
|
|
|
|
% The ets table contains only IMSIs
|
|
|
|
start_link() ->
|
|
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
|
|
|
|
init([]) ->
|
|
{ok, #swm_state{}}.
|
|
|
|
|
|
%% Swm Diameter message Diameter-EAP-Request, 3GPP TS 29.273 Table 7.1.2.1.1
|
|
auth_request(Imsi, PdpTypeNr, Apn, EAP) ->
|
|
% In Diameter we use Imsi as strings, as done by diameter module.
|
|
ImsiStr = binary_to_list(Imsi),
|
|
% PdpTypeNr: SWm Diameter AVP "UE-Local-IP-Address"
|
|
% Apn: SWm Diameter AVP "Service-Selection"
|
|
% EAP: SWm Diameter AVP EAP-Payload
|
|
Result = gen_server:call(?SERVER, {epdg_auth_req, ImsiStr, PdpTypeNr, Apn, EAP}),
|
|
case Result of
|
|
{ok, _AuthTuples} ->
|
|
epdg_ue_fsm:received_swm_auth_response(self(), Result),
|
|
ok;
|
|
_ -> Result
|
|
end.
|
|
|
|
% Rx "GSUP CEAI LU Req" is our way of saying Rx "Swm Diameter-EAP REQ (DER) with EAP AVP containing successuful auth":
|
|
auth_compl_request(Imsi, Apn) ->
|
|
% In Diameter we use Imsi as strings, as done by diameter module.
|
|
ImsiStr = binary_to_list(Imsi),
|
|
Result = gen_server:call(?SERVER, {epdg_auth_compl_req, ImsiStr, Apn}),
|
|
case Result of
|
|
{ok, _Mar} ->
|
|
epdg_ue_fsm:received_swm_auth_compl_response(self(), Result),
|
|
ok;
|
|
_ -> Result
|
|
end.
|
|
|
|
% 3GPP TS 29.273 7.1.2.3
|
|
session_termination_request(Imsi) ->
|
|
% In Diameter we use Imsi as strings, as done by diameter module.
|
|
ImsiStr = binary_to_list(Imsi),
|
|
Result = gen_server:call(?SERVER, {str, ImsiStr}),
|
|
case Result of
|
|
{ok, _Mar} ->
|
|
epdg_ue_fsm:received_swm_session_terminate_answer(self(), Result),
|
|
ok;
|
|
_ -> Result
|
|
end.
|
|
|
|
% 3GPP TS 29.273 7.1.2.4
|
|
abort_session_answer(Imsi) ->
|
|
% In Diameter we use Imsi as strings, as done by diameter module.
|
|
ImsiStr = binary_to_list(Imsi),
|
|
Result = gen_server:call(?SERVER, {asa, ImsiStr}),
|
|
case Result of
|
|
{ok, _Mar} ->
|
|
ok;
|
|
_ -> Result
|
|
end.
|
|
|
|
handle_call({epdg_auth_req, Imsi, PdpTypeNr, Apn, EAP}, {Pid, _Tag} = _From, State0) ->
|
|
% we yet don't implement the Diameter SWm interface on the wire, we process the call internally:
|
|
{_Sess, State1} = find_or_new_swm_session(Imsi, Pid, State0),
|
|
ok = aaa_diameter_swm:auth_request(Imsi, PdpTypeNr, Apn, EAP),
|
|
{reply, ok, State1};
|
|
|
|
handle_call({epdg_auth_compl_req, Imsi, Apn}, _From, State) ->
|
|
% we yet don't implement the Diameter SWm interface on the wire, we process the call internally:
|
|
Sess = find_swm_session_by_imsi(Imsi, State),
|
|
case Sess of
|
|
#swm_session{imsi = Imsi} ->
|
|
Reply = aaa_diameter_swm:auth_compl_request(Imsi, Apn);
|
|
undefined ->
|
|
Reply = {error,unknown_imsi}
|
|
end,
|
|
{reply, Reply, State};
|
|
|
|
handle_call({str, Imsi}, _From, State) ->
|
|
% we yet don't implement the Diameter SWm interface on the wire, we process the call internally:
|
|
Sess = find_swm_session_by_imsi(Imsi, State),
|
|
case Sess of
|
|
#swm_session{imsi = Imsi} ->
|
|
Reply = aaa_diameter_swm:session_termination_request(Imsi);
|
|
undefined ->
|
|
Reply = {error,unknown_imsi}
|
|
end,
|
|
{reply, Reply, State};
|
|
|
|
handle_call({asa, Imsi}, _From, State) ->
|
|
% we yet don't implement the Diameter SWm interface on the wire, we process the call internally:
|
|
Sess = find_swm_session_by_imsi(Imsi, State),
|
|
case Sess of
|
|
#swm_session{imsi = Imsi} ->
|
|
Reply = aaa_diameter_swm:abort_session_answer(Imsi);
|
|
undefined ->
|
|
Reply = {error,unknown_imsi}
|
|
end,
|
|
{reply, Reply, State}.
|
|
|
|
handle_cast({epdg_auth_resp, Imsi, Result}, State) ->
|
|
Sess = find_swm_session_by_imsi(Imsi, State),
|
|
case Sess of
|
|
#swm_session{imsi = Imsi} ->
|
|
epdg_ue_fsm:received_swm_auth_response(Sess#swm_session.pid, Result);
|
|
undefined ->
|
|
error_logger:error_report(["unknown swm_session", {module, ?MODULE}, {imsi, Imsi}, {state, State}])
|
|
end,
|
|
{noreply, State};
|
|
|
|
handle_cast({epdg_auth_compl_resp, Imsi, Result}, State) ->
|
|
Sess = find_swm_session_by_imsi(Imsi, State),
|
|
case Sess of
|
|
#swm_session{imsi = Imsi} ->
|
|
epdg_ue_fsm:received_swm_auth_compl_response(Sess#swm_session.pid, Result);
|
|
undefined ->
|
|
error_logger:error_report(["unknown swm_session", {module, ?MODULE}, {imsi, Imsi}, {state, State}])
|
|
end,
|
|
{noreply, State};
|
|
|
|
handle_cast({sta, Imsi, Result}, State) ->
|
|
Sess = find_swm_session_by_imsi(Imsi, State),
|
|
case Sess of
|
|
#swm_session{imsi = Imsi} ->
|
|
epdg_ue_fsm:received_swm_session_termination_answer(Sess#swm_session.pid, Result);
|
|
undefined ->
|
|
error_logger:error_report(["unknown swm_session", {module, ?MODULE}, {imsi, Imsi}, {state, State}])
|
|
end,
|
|
{noreply, State};
|
|
|
|
handle_cast({asr, Imsi}, State) ->
|
|
Sess = find_swm_session_by_imsi(Imsi, State),
|
|
case Sess of
|
|
#swm_session{imsi = Imsi} ->
|
|
epdg_ue_fsm:received_swm_abort_session_request(Sess#swm_session.pid);
|
|
undefined ->
|
|
error_logger:error_report(["unknown swm_session", {module, ?MODULE}, {imsi, Imsi}, {state, State}])
|
|
end,
|
|
{noreply, State};
|
|
|
|
handle_cast(Info, S) ->
|
|
error_logger:error_report(["unknown handle_cast", {module, ?MODULE}, {info, Info}, {state, S}]),
|
|
{noreply, S}.
|
|
handle_info(Info, S) ->
|
|
error_logger:error_report(["unknown handle_info", {module, ?MODULE}, {info, Info}, {state, S}]),
|
|
{noreply, S}.
|
|
|
|
|
|
stop() ->
|
|
gen_server:call(?MODULE, stop).
|
|
|
|
code_change(_OldVsn, State, _Extra) ->
|
|
{ok, State}.
|
|
|
|
terminate(Reason, _S) ->
|
|
lager:info("terminating ~p with reason ~p~n", [?MODULE, Reason]).
|
|
|
|
%% Emulation from the wire (DIAMETER SWm), called from internal AAA Server:
|
|
auth_response(Imsi, Result) ->
|
|
ok = gen_server:cast(?SERVER, {epdg_auth_resp, Imsi, Result}).
|
|
|
|
%Rx Swm Diameter-EAP Answer (DEA) containing APN-Configuration, triggered by
|
|
%earlier Tx DER EAP AVP containing successuful auth":
|
|
auth_compl_response(Imsi, Result) ->
|
|
ok = gen_server:cast(?SERVER, {epdg_auth_compl_resp, Imsi, Result}).
|
|
|
|
% Rx SWm Diameter STA:
|
|
session_termination_answer(Imsi, Result) ->
|
|
ok = gen_server:cast(?SERVER, {sta, Imsi, Result}).
|
|
|
|
% Rx SWm Diameter ASR:
|
|
abort_session_request(Imsi) ->
|
|
ok = gen_server:cast(?SERVER, {asr, Imsi}).
|
|
|
|
%% ------------------------------------------------------------------
|
|
%% Internal Function Definitions
|
|
%% ------------------------------------------------------------------
|
|
|
|
new_swm_session(Imsi, Pid, State) ->
|
|
Sess = #swm_session{imsi = Imsi,
|
|
pid = Pid
|
|
},
|
|
NewSt = State#swm_state{sessions = sets:add_element(Sess, State#swm_state.sessions)},
|
|
{Sess, NewSt}.
|
|
|
|
% returns Sess if found, undefined it not
|
|
find_swm_session_by_imsi(Imsi, State) ->
|
|
{Imsi, Res} = sets:fold(
|
|
fun(SessIt = #swm_session{imsi = LookupImsi}, {LookupImsi, _AccIn}) -> {LookupImsi, SessIt};
|
|
(_, AccIn) -> AccIn
|
|
end,
|
|
{Imsi, undefined},
|
|
State#swm_state.sessions),
|
|
Res.
|
|
|
|
find_or_new_swm_session(Imsi, Pid, State) ->
|
|
Sess = find_swm_session_by_imsi(Imsi, State),
|
|
case Sess of
|
|
#swm_session{imsi = Imsi} ->
|
|
% Update Pid since it may have changed:
|
|
Sess1 = Sess#swm_session{pid = Pid},
|
|
State1 = update_swm_session(Sess, Sess1, State),
|
|
{Sess1, State1};
|
|
undefined ->
|
|
new_swm_session(Imsi, Pid, State)
|
|
end.
|
|
|
|
update_swm_session(OldSess, NewSess, State) ->
|
|
SetRemoved = sets:del_element(OldSess, State#swm_state.sessions),
|
|
SetUpdated = sets:add_element(NewSess, SetRemoved),
|
|
State#swm_state{sessions = SetUpdated}.
|