renaming files
This commit is contained in:
parent
25b3b52424
commit
91a43b40ff
|
@ -1,5 +1,7 @@
|
|||
{application, osmo_dia2gsup, [
|
||||
{description, "Osmocom DIAMETER -> GSUP translator"},
|
||||
%-*- mode: erlang -*-
|
||||
{application, epdg,
|
||||
[
|
||||
{description, "ePDG"},
|
||||
{vsn, "1"},
|
||||
{registered, []},
|
||||
{applications, [
|
||||
|
@ -10,6 +12,6 @@
|
|||
osmo_gsup,
|
||||
osmo_ss7
|
||||
]},
|
||||
{mod, {osmo_dia2gsup_app, []}},
|
||||
{mod, {epdg_app, []}},
|
||||
{env, []}
|
||||
]}.
|
|
@ -1,10 +1,12 @@
|
|||
-module(osmo_dia2gsup_app).
|
||||
|
||||
-module(epdg_app).
|
||||
|
||||
-behaviour(application).
|
||||
|
||||
-export([start/2, stop/1]).
|
||||
|
||||
start(_StartType, _StartArgs) ->
|
||||
osmo_dia2gsup_sup:start_link().
|
||||
epdg_sup:start_link().
|
||||
|
||||
stop(_State) ->
|
||||
ok.
|
|
@ -0,0 +1,184 @@
|
|||
% SWx: AAA side
|
||||
%
|
||||
% TS 29.273
|
||||
%
|
||||
% (C) 2023 by sysmocom - s.m.f.c. GmbH <info@sysmocom.de>
|
||||
% Author: Alexander Couzens <lynxis@fe80.eu>
|
||||
%
|
||||
% 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(epdg_diameter_swx).
|
||||
-author('Alexander Couzens <lynxis@fe80.eu>').
|
||||
|
||||
-behaviour(gen_server).
|
||||
|
||||
-include_lib("diameter_3gpp_ts29_273_swx.hrl").
|
||||
|
||||
%% API Function Exports
|
||||
-export([start_link/0]).
|
||||
-export([start/0, stop/0, terminate/2]).
|
||||
%% gen_server Function Exports
|
||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2]).
|
||||
-export([code_change/3]).
|
||||
-export([media_auth_request/6]).
|
||||
-export([test/0]).
|
||||
|
||||
%% Diameter Application Definitions
|
||||
-define(SERVER, ?MODULE).
|
||||
-define(SVC_NAME, ?MODULE).
|
||||
-define(APP_ALIAS, ?MODULE).
|
||||
-define(CALLBACK_MOD, client_cb).
|
||||
-define(DIAMETER_DICT_SWX, diameter_3gpp_ts29_273_swx).
|
||||
%% The service configuration. As in the server example, a client
|
||||
%% supporting multiple Diameter applications may or may not want to
|
||||
%% configure a common callback module on all applications.
|
||||
-define(SERVICE(Name),
|
||||
[{'Origin-Host', application:get_env(?SERVER, origin_host, "default.com")},
|
||||
{'Origin-Realm', application:get_env(?SERVER, origin_realm, "realm.default.com")},
|
||||
{'Vendor-Id', application:get_env(?SERVER, vendor_id, 0)},
|
||||
{'Product-Name', "Client"},
|
||||
{application,
|
||||
[{alias, ?APP_ALIAS}, {dictionary, ?DIAMETER_DICT_SWX}, {module, ?CALLBACK_MOD}]}]).
|
||||
|
||||
-record(state, {
|
||||
handlers,
|
||||
peers = #{}
|
||||
}).
|
||||
|
||||
%% @doc starts gen_server implementation process
|
||||
-spec start() -> ok | {error, term()}.
|
||||
start() ->
|
||||
application:ensure_all_started(?MODULE),
|
||||
start_link().
|
||||
|
||||
%% @doc stops gen_server implementation process
|
||||
-spec stop() -> ok.
|
||||
stop() ->
|
||||
gen_server:cast(?SERVER, stop).
|
||||
|
||||
start_link() ->
|
||||
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
|
||||
|
||||
peer_down(API, SvcName, {PeerRef, _} = Peer) ->
|
||||
(catch ets:delete(?MODULE, {API, PeerRef})),
|
||||
gen_server:cast(?SERVER, {peer_down, SvcName, Peer}),
|
||||
ok.
|
||||
|
||||
init(State) ->
|
||||
Proto = application:get_env(?SERVER, diameter_proto, sctp),
|
||||
Ip = application:get_env(?SERVER, diameter_server_ip, "192.168.56.132"),
|
||||
Port = application:get_env(?SERVER, diameter_port, 3868),
|
||||
DiaServ = diameter:start_service(?MODULE, ?SERVICE(Name)),
|
||||
lager:info("DiaServices is ~p~n", [DiaServ]),
|
||||
Transport = connect({address, Proto, Ip, Port}),
|
||||
lager:info("DiaTransport is ~p~n", [Transport]),
|
||||
|
||||
{ok, State}.
|
||||
|
||||
test() ->
|
||||
media_auth_request("123456789012345", 3, "AKA", 1, [], []).
|
||||
|
||||
media_auth_request(IMSI, NumAuthItems, AuthScheme, RAT, CKey = [], IntegrityKey = []) ->
|
||||
Res = gen_server:call(?SERVER,
|
||||
{mar, {IMSI, NumAuthItems, AuthScheme, RAT, CKey, IntegrityKey}}),
|
||||
lager:info("Response is ~p~n", [Res]),
|
||||
Res.
|
||||
|
||||
% TODO Sync failure
|
||||
handle_call({mar, {IMSI, NumAuthItems, AuthScheme, RAT, _CKey, _IntegrityKey}}, _From, State) ->
|
||||
SessionId = diameter:session_id(atom_to_list(?SVC_NAME)),
|
||||
MAR = #'MAR'{'Session-Id' = SessionId,
|
||||
'Auth-Session-State' = 1,
|
||||
'User-Name' = IMSI,
|
||||
'SIP-Auth-Data-Item' = #'SIP-Auth-Data-Item'{'SIP-Authentication-Scheme' = AuthScheme},
|
||||
'SIP-Number-Auth-Items' = NumAuthItems,
|
||||
'RAT-Type' = RAT
|
||||
},
|
||||
Ret = diameter:call(?SVC_NAME, ?APP_ALIAS, MAR, []),
|
||||
case Ret of
|
||||
{ok, MAA} ->
|
||||
lager:info("MAR Success"),
|
||||
{reply, {ok, MAA}, State};
|
||||
{error, Err} ->
|
||||
lager:error("Error: ~w~n", [Err]),
|
||||
{reply, {error, Err}, State}
|
||||
end.
|
||||
|
||||
%% @callback gen_server
|
||||
handle_cast(stop, State) ->
|
||||
{stop, normal, State};
|
||||
handle_cast(_Req, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
%% @callback gen_server
|
||||
handle_info(_Info, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
%% @callback gen_server
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
|
||||
%% @callback gen_server
|
||||
terminate(normal, _State) ->
|
||||
diameter:stop_service(?SVC_NAME),
|
||||
ok;
|
||||
terminate(shutdown, _State) ->
|
||||
ok;
|
||||
terminate({shutdown, _Reason}, _State) ->
|
||||
ok;
|
||||
terminate(_Reason, _State) ->
|
||||
ok.
|
||||
|
||||
%% ------------------------------------------------------------------
|
||||
%% Internal Function Definitions
|
||||
%% ------------------------------------------------------------------
|
||||
|
||||
%% connect/2
|
||||
connect(Name, {address, Protocol, IPAddr, Port}) ->
|
||||
{ok, IP} = inet_parse:address(IPAddr),
|
||||
TransportOpts =
|
||||
[{transport_module, tmod(Protocol)},
|
||||
{transport_config,
|
||||
[{reuseaddr, true},
|
||||
{raddr, IP},
|
||||
{rport, Port}]}],
|
||||
diameter:add_transport(Name, {connect, [{reconnect_timer, 1000} | TransportOpts]}).
|
||||
|
||||
connect(Address) ->
|
||||
connect(?SVC_NAME, Address).
|
||||
|
||||
%% Convert connection type
|
||||
tmod(tcp) ->
|
||||
diameter_tcp;
|
||||
tmod(sctp) ->
|
||||
diameter_sctp.
|
||||
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
-module(epdg_sup).
|
||||
-behaviour(supervisor).
|
||||
|
||||
-export([start_link/0]).
|
||||
-export([init/1]).
|
||||
|
||||
-define(SERVER, ?MODULE).
|
||||
start_link() ->
|
||||
supervisor:start_link({local, ?SERVER}, ?MODULE, []).
|
||||
|
||||
init([]) ->
|
||||
% GSUP side
|
||||
%% HlrIp = application:get_env(osmo_dia2gsup, hlr_ip, "127.0.0.1"),
|
||||
%% HlrPort = application:get_env(osmo_dia2gsup, hlr_port, 4222),
|
||||
%% Args = [{local, gsup_client}, gsup_client, [HlrIp, HlrPort, []], [{debug, [trace]}]],
|
||||
%% GsupChild = {gsup_client, {gen_server, start_link, Args}, permanent, 2000, worker, [gsup_client]},
|
||||
% DIAMETER side
|
||||
DiaServer = {epdg_diameter_swx, {epdg_diameter_swx,start_link,[]},
|
||||
permanent,
|
||||
5000,
|
||||
worker,
|
||||
[swx_client_cb]},
|
||||
%% DiaSuper = {diameter, {diameter,start_link,[]},
|
||||
%% permanent,
|
||||
%% 5000,
|
||||
%% worker,
|
||||
%% []},
|
||||
{ok, { {one_for_one, 5, 10}, [DiaServer]} }.
|
|
@ -1,126 +0,0 @@
|
|||
-module(osmo_dia2gsup).
|
||||
-behavior(gen_server).
|
||||
|
||||
-include_lib("diameter/include/diameter.hrl").
|
||||
-include_lib("diameter/include/diameter_gen_base_rfc6733.hrl").
|
||||
%-include_lib("diameter_settings.hrl").
|
||||
|
||||
-export([main/1]).
|
||||
|
||||
% API
|
||||
-export([start_link/0]).
|
||||
-export([start/0, stop/0]).
|
||||
|
||||
% gen_server callbacks
|
||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2]).
|
||||
-export([code_change/3, terminate/2]).
|
||||
|
||||
-define(SERVER, ?MODULE).
|
||||
|
||||
% Diameter application definitions
|
||||
|
||||
-define(DIA_STATS_TAB, iwf_stats).
|
||||
-define(DIA_STATS_COUNTERS, [event_OK, event_ERR]).
|
||||
|
||||
-define(SVC_NAME, ?MODULE).
|
||||
-define(APP_ALIAS, ?MODULE).
|
||||
-define(CALLBACK_MOD, server_cb).
|
||||
-define(DIAMETER_DICT_HSS, diameter_3gpp_ts29_272).
|
||||
|
||||
-define(APPID_S6, #'diameter_base_Vendor-Specific-Application-Id'{'Vendor-Id'=10515, 'Auth-Application-Id'=[16777251]}).
|
||||
-define(SERVICE(Name), [{'Origin-Host', application:get_env(osmo_dia2gsup, origin_host, "hss.localdomain")},
|
||||
{'Origin-Realm', application:get_env(osmo_dia2gsup, origin_realm, "localdomain")},
|
||||
{'Vendor-Id', application:get_env(osmo_dia2gsup, vendor_id, 0)},
|
||||
{'Product-Name', "osmo_dia2gsup"},
|
||||
{'Auth-Application-Id', []},
|
||||
{'Vendor-Specific-Application-Id', [?APPID_S6]},
|
||||
{application,
|
||||
[{alias, ?APP_ALIAS},
|
||||
{dictionary, ?DIAMETER_DICT_HSS},
|
||||
{module, ?CALLBACK_MOD}]
|
||||
}]).
|
||||
|
||||
|
||||
|
||||
%% ------------------------------------------------------------------
|
||||
%% API
|
||||
%% ------------------------------------------------------------------
|
||||
|
||||
start_link() ->
|
||||
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
|
||||
|
||||
start() ->
|
||||
application:ensure_all_started(?MODULE),
|
||||
start_link().
|
||||
|
||||
stop() ->
|
||||
gen_server:cast(?SERVER, stop).
|
||||
|
||||
main(_Args) ->
|
||||
application:ensure_all_started(?MODULE),
|
||||
timer:sleep(infinity).
|
||||
|
||||
%% ------------------------------------------------------------------
|
||||
%% gen_server Function Definitions
|
||||
%% ------------------------------------------------------------------
|
||||
|
||||
%% @callback gen_server
|
||||
init(State) ->
|
||||
% DIAMETER side
|
||||
SvcName = ?MODULE,
|
||||
diameter:start_service(SvcName, ?SERVICE(SvcName)),
|
||||
Ip = application:get_env(osmo_dia2gsup, diameter_ip, "127.0.0.8"),
|
||||
Port = application:get_env(osmo_dia2gsup, diameter_port, 3868),
|
||||
Proto = application:get_env(osmo_dia2gsup, diameter_proto, sctp),
|
||||
listen({address, Proto, element(2,inet:parse_address(Ip)), Port}),
|
||||
lager:info("Diameter HSS Application started on IP ~s, ~p port ~p~n", [Ip, Proto, Port]),
|
||||
{ok, State}.
|
||||
|
||||
%% @callback gen_server
|
||||
handle_call(_Req, _From, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
%% @callback gen_server
|
||||
handle_cast(stop, State) ->
|
||||
{stop, normal, State};
|
||||
handle_cast(_req, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
|
||||
%% @callback gen_server
|
||||
handle_info(_Info, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
%% @callback gen_server
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
|
||||
%% @callback gen_server
|
||||
terminate(normal, _State) ->
|
||||
diameter:stop_service(?SVC_NAME),
|
||||
lager:info("Diameter HSS Application stopped.~n"),
|
||||
ok;
|
||||
terminate(shutdown, _State) ->
|
||||
ok;
|
||||
terminate({shutdown, _Reason}, _State) ->
|
||||
ok;
|
||||
terminate(_Reason, _State) ->
|
||||
ok.
|
||||
|
||||
|
||||
|
||||
%% ------------------------------------------------------------------
|
||||
%% Internal Function Definitions
|
||||
%% ------------------------------------------------------------------
|
||||
|
||||
listen(Name, {address, Protocol, IPAddr, Port}) ->
|
||||
TransOpts = [{transport_module, tmod(Protocol)},
|
||||
{transport_config, [{reuseaddr, true},
|
||||
{ip, IPAddr}, {port, Port}]}],
|
||||
{ok, _} = diameter:add_transport(Name, {listen, TransOpts}).
|
||||
|
||||
listen(Address) ->
|
||||
listen(?SVC_NAME, Address).
|
||||
|
||||
tmod(tcp) -> diameter_tcp;
|
||||
tmod(sctp) -> diameter_sctp.
|
|
@ -1,23 +0,0 @@
|
|||
-module(osmo_dia2gsup_sup).
|
||||
-behaviour(supervisor).
|
||||
|
||||
-export([start_link/0]).
|
||||
-export([init/1]).
|
||||
|
||||
-define(SERVER, ?MODULE).
|
||||
start_link() ->
|
||||
supervisor:start_link({local, ?SERVER}, ?MODULE, []).
|
||||
|
||||
init([]) ->
|
||||
% GSUP side
|
||||
HlrIp = application:get_env(osmo_dia2gsup, hlr_ip, "127.0.0.1"),
|
||||
HlrPort = application:get_env(osmo_dia2gsup, hlr_port, 4222),
|
||||
Args = [{local, gsup_client}, gsup_client, [HlrIp, HlrPort, []], [{debug, [trace]}]],
|
||||
GsupChild = {gsup_client, {gen_server, start_link, Args}, permanent, 2000, worker, [gsup_client]},
|
||||
% DIAMETER side
|
||||
DiaServer = {osmo_dia2gsup,{osmo_dia2gsup,start_link,[]},
|
||||
permanent,
|
||||
5000,
|
||||
worker,
|
||||
[server_cb]},
|
||||
{ok, { {one_for_one, 5, 10}, [DiaServer, GsupChild]} }.
|
|
@ -1,332 +0,0 @@
|
|||
-module(server_cb).
|
||||
|
||||
|
||||
-include_lib("diameter/include/diameter.hrl").
|
||||
-include_lib("diameter/include/diameter_gen_base_rfc6733.hrl").
|
||||
-include_lib("diameter_3gpp_ts29_272.hrl").
|
||||
-include_lib("osmo_gsup/include/gsup_protocol.hrl").
|
||||
|
||||
|
||||
%% diameter callbacks
|
||||
-export([peer_up/3, peer_down/3, pick_peer/4, prepare_request/3, prepare_retransmit/3,
|
||||
handle_answer/4, handle_error/4, handle_request/3]).
|
||||
|
||||
-define(UNEXPECTED, erlang:error({unexpected, ?MODULE, ?LINE})).
|
||||
|
||||
peer_up(_SvcName, {PeerRef, Caps}, State) ->
|
||||
lager:info("Peer up ~p - ~p~n", [PeerRef, lager:pr(Caps, ?MODULE)]),
|
||||
State.
|
||||
|
||||
peer_down(_SvcName, {PeerRef, Caps}, State) ->
|
||||
lager:info("Peer down ~p - ~p~n", [PeerRef, lager:pr(Caps, ?MODULE)]),
|
||||
State.
|
||||
|
||||
pick_peer(_, _, _SvcName, _State) ->
|
||||
?UNEXPECTED.
|
||||
|
||||
prepare_request(_, _SvcName, _Peer) ->
|
||||
?UNEXPECTED.
|
||||
|
||||
prepare_retransmit(_Packet, _SvcName, _Peer) ->
|
||||
?UNEXPECTED.
|
||||
|
||||
handle_answer(_Packet, _Request, _SvcName, _Peer) ->
|
||||
?UNEXPECTED.
|
||||
|
||||
handle_error(_Reason, _Request, _SvcName, _Peer) ->
|
||||
lager:error("Request error: ~p~n", [_Reason]),
|
||||
?UNEXPECTED.
|
||||
|
||||
% generate Diameter E-UTRAN / UTRAN / GERAN Vectors from GSUP tuple input
|
||||
-spec gsup_tuple2dia_eutran('GSUPAuthTuple'(), binary(), integer()) -> #'E-UTRAN-Vector'{}.
|
||||
gsup_tuple2dia_eutran(#{autn:=Autn, ck:=Ck, ik:=Ik, rand:=Rand, res:=Res}, Vplmn, Idx) ->
|
||||
#'E-UTRAN-Vector'{'Item-Number'=Idx, 'RAND'=Rand, 'XRES'=Res , 'AUTN'=Autn,
|
||||
'KASME'=compute_kasme(Ck, Ik, Vplmn, Autn)}.
|
||||
|
||||
-spec gsup_tuple2dia_utran('GSUPAuthTuple'()) -> #'UTRAN-Vector'{}.
|
||||
gsup_tuple2dia_utran(#{autn:=Autn, ck:=Ck, ik:=Ik, rand:=Rand, res:=Res}) ->
|
||||
#'UTRAN-Vector'{'RAND'=Rand, 'XRES'=Res, 'AUTN'=Autn, 'Confidentiality-Key'=Ck, 'Integrity-Key'=Ik}.
|
||||
|
||||
-spec gsup_tuple2dia_geran('GSUPAuthTuple'()) -> #'GERAN-Vector'{}.
|
||||
gsup_tuple2dia_geran(#{rand:=Rand, sres:=Sres, kc:=Kc}) ->
|
||||
#'GERAN-Vector'{'RAND'=Rand, 'SRES'=Sres, 'Kc'=Kc}.
|
||||
|
||||
-spec gsup_tuples2dia_eutran(['GSUPAuthTuple'()], binary()) -> [#'E-UTRAN-Vector'{}].
|
||||
gsup_tuples2dia_eutran(List, Vplmn) -> gsup_tuples2dia_eutran(List, Vplmn, [], 1).
|
||||
gsup_tuples2dia_eutran([], _Vplmn, Out, _Idx) -> Out;
|
||||
gsup_tuples2dia_eutran([Head|Tail], Vplmn, Out, Ctr) ->
|
||||
Dia = gsup_tuple2dia_eutran(Head, Vplmn, Ctr),
|
||||
gsup_tuples2dia_eutran(Tail, Vplmn, [Dia|Out], Ctr+1).
|
||||
|
||||
-type int_or_false() :: false | integer().
|
||||
-spec gsup_tuples2dia(['GSUPAuthTuple'()], binary(), int_or_false(), int_or_false(), int_or_false()) -> #'Authentication-Info'{}.
|
||||
gsup_tuples2dia(Tuples, Vplmn, NumEutran, NumUtran, NumGeran) ->
|
||||
case NumEutran of
|
||||
false -> EutranVecs = [];
|
||||
0 -> EutranVecs = [];
|
||||
_ -> EutranVecs = gsup_tuples2dia_eutran(lists:sublist(Tuples,NumEutran), Vplmn)
|
||||
end,
|
||||
case NumUtran of
|
||||
false -> UtranVecs = [];
|
||||
0 -> UtranVecs = [];
|
||||
_ -> UtranVecs = lists:map(fun gsup_tuple2dia_utran/1, lists:sublist(Tuples,NumUtran))
|
||||
end,
|
||||
case NumGeran of
|
||||
false -> GeranVecs = [];
|
||||
0 -> GeranVecs = [];
|
||||
_ -> GeranVecs = lists:map(fun gsup_tuple2dia_geran/1, lists:sublist(Tuples,NumGeran))
|
||||
end,
|
||||
#'Authentication-Info'{'E-UTRAN-Vector'=EutranVecs, 'UTRAN-Vector'=UtranVecs,
|
||||
'GERAN-Vector'=GeranVecs}.
|
||||
|
||||
|
||||
-spec compute_kasme(<<_:16>>, <<_:16>>, <<_:3>>, <<_:16>>) -> <<_:32>>.
|
||||
compute_kasme(Ck, Ik, VplmnId, Autn) ->
|
||||
Autn6 = binary_part(Autn, 0, 6),
|
||||
K = <<Ck:16/binary, Ik:16/binary>>,
|
||||
S = <<16, VplmnId:3/binary, 0, 3, Autn6:6/binary, 0, 6>>,
|
||||
Release = erlang:system_info(otp_release),
|
||||
if
|
||||
Release >= "24" ->
|
||||
crypto:macN(hmac, sha256, K, S, 32);
|
||||
true ->
|
||||
crypto:hmac(sha256, K, S, 32)
|
||||
end.
|
||||
|
||||
-spec req_num_of_vec([tuple()]) -> int_or_false().
|
||||
req_num_of_vec([#'Requested-EUTRAN-Authentication-Info'{'Number-Of-Requested-Vectors'=[]}]) -> false;
|
||||
req_num_of_vec([#'Requested-EUTRAN-Authentication-Info'{'Number-Of-Requested-Vectors'=[Num]}]) -> Num;
|
||||
req_num_of_vec([#'Requested-UTRAN-GERAN-Authentication-Info'{'Number-Of-Requested-Vectors'=[]}]) -> false;
|
||||
req_num_of_vec([#'Requested-UTRAN-GERAN-Authentication-Info'{'Number-Of-Requested-Vectors'=[Num]}]) -> Num;
|
||||
req_num_of_vec(_) -> false.
|
||||
|
||||
|
||||
-type binary_or_false() :: false | binary().
|
||||
-spec req_resynchronization_info([tuple()]) -> binary_or_false().
|
||||
req_resynchronization_info([#'Requested-EUTRAN-Authentication-Info'{'Re-Synchronization-Info'=[]}]) ->
|
||||
false;
|
||||
req_resynchronization_info([#'Requested-EUTRAN-Authentication-Info'{'Re-Synchronization-Info'=[Info]}]) ->
|
||||
list_to_binary(Info);
|
||||
|
||||
req_resynchronization_info([#'Requested-UTRAN-GERAN-Authentication-Info'{'Re-Synchronization-Info'=[]}]) ->
|
||||
false;
|
||||
req_resynchronization_info([#'Requested-UTRAN-GERAN-Authentication-Info'{'Re-Synchronization-Info'=[Info]}]) ->
|
||||
list_to_binary(Info);
|
||||
|
||||
req_resynchronization_info(_) ->
|
||||
false.
|
||||
|
||||
-define(PDP_TYPE_DEFAULT, <<0,0,0,16#21>>). % IPv4
|
||||
-define(PDP_QOS_DEFAULT, <<0,0,0,0,0,0,0,0,0,0,0,0,0,0>>). % fixme
|
||||
|
||||
-spec gsup_pdp2dia('GSUPPdpInfo'()) -> #'PDP-Context'{}.
|
||||
gsup_pdp2dia(GsupPdpInfo) ->
|
||||
#'PDP-Context'{'PDP-Type' = maps:get(pdp_type, GsupPdpInfo, ?PDP_TYPE_DEFAULT),
|
||||
'Context-Identifier' = maps:get(pdp_context_id, GsupPdpInfo),
|
||||
'Service-Selection' = maps:get(access_point_name, GsupPdpInfo),
|
||||
'QoS-Subscribed' = maps:get(quality_of_service, GsupPdpInfo, ?PDP_QOS_DEFAULT)
|
||||
}.
|
||||
|
||||
-define(PDN_TYPE_DEFAULT, 0). % IPv4
|
||||
-define(EPS_QOS_DEFAULT,
|
||||
#'EPS-Subscribed-QoS-Profile'{'QoS-Class-Identifier'=9,
|
||||
'Allocation-Retention-Priority'=
|
||||
#'Allocation-Retention-Priority'{'Priority-Level'=8,
|
||||
'Pre-emption-Capability'=1,
|
||||
'Pre-emption-Vulnerability'=1}
|
||||
}).
|
||||
|
||||
-define(APN_WILDCARD,
|
||||
#'APN-Configuration'{'Context-Identifier' = maps:get(pdp_context_id, GsupPdpInfo),
|
||||
'PDN-Type' = ?PDN_TYPE_DEFAULT),
|
||||
% The EPS-Subscribed-QoS-Profile AVP and the AMBR AVP shall be present in the
|
||||
% APN-Configuration AVP when the APN-Configuration AVP is sent in the
|
||||
% APN-Configuration-Profile AVP and when the APN-Configuration-Profile AVP is
|
||||
% sent within a ULA (as part of the Subscription-Data AVP).
|
||||
'EPS-Subscribed-QoS-Profile' = ?EPS_QOS_DEFAULT,
|
||||
'AMBR' = #'AMBR'{'Max-Requested-Bandwidth-UL' = 100000000,
|
||||
'Max-Requested-Bandwidth-DL' = 100000000},
|
||||
% The default APN Configuration shall not contain the Wildcard APN (see 3GPP TS
|
||||
% 23.003 [3], clause 9.2); the default APN shall always contain an explicit APN
|
||||
'Service-Selection' = "*"
|
||||
})
|
||||
|
||||
-spec gsup_pdp2dia_apn('GSUPPdpInfo'()) -> #'APN-Configuration'{}.
|
||||
gsup_pdp2dia_apn(GsupPdpInfo) ->
|
||||
#'APN-Configuration'{'Context-Identifier' = maps:get(pdp_context_id, GsupPdpInfo),
|
||||
'PDN-Type' = maps:get(pdp_type, GsupPdpInfo, ?PDN_TYPE_DEFAULT),
|
||||
% The EPS-Subscribed-QoS-Profile AVP and the AMBR AVP shall be present in the
|
||||
% APN-Configuration AVP when the APN-Configuration AVP is sent in the
|
||||
% APN-Configuration-Profile AVP and when the APN-Configuration-Profile AVP is
|
||||
% sent within a ULA (as part of the Subscription-Data AVP).
|
||||
'EPS-Subscribed-QoS-Profile' = ?EPS_QOS_DEFAULT,
|
||||
'AMBR' = #'AMBR'{'Max-Requested-Bandwidth-UL' = 100000000,
|
||||
'Max-Requested-Bandwidth-DL' = 100000000},
|
||||
% The default APN Configuration shall not contain the Wildcard APN (see 3GPP TS
|
||||
% 23.003 [3], clause 9.2); the default APN shall always contain an explicit APN
|
||||
'Service-Selection' = "internet"%maps:get(access_point_name, GsupPdpInfo)
|
||||
}.
|
||||
|
||||
% transient (only in Experimental-Result-Code)
|
||||
-define(DIAMETER_AUTHENTICATION_DATA_UNAVAILABLE, 4181).
|
||||
-define(DIAMETER_ERROR_CAMEL_SUBSCRIPTION_PRESENT, 4182).
|
||||
% permanent (only in Experimental-Result-Code)
|
||||
-define(DIAMETER_ERROR_USER_UNKNOWN, 5001).
|
||||
-define(DIAMETER_ERROR_ROAMING_NOT_ALLOWED, 5004).
|
||||
-define(DIAMETER_ERROR_UNKNOWN_EPS_SUBSCRIPTION, 5420).
|
||||
-define(DIAMETER_ERROR_RAT_NOT_ALLOWED, 5421).
|
||||
-define(DIAMETER_ERROR_EQUIPMENT_UNKNOWN, 5422).
|
||||
-define(DIAMETER_ERROR_UNKOWN_SERVING_NODE, 5423).
|
||||
|
||||
% 10.5.5.14
|
||||
-define(GMM_CAUSE_IMSI_UNKNOWN, 16#02).
|
||||
-define(GMM_CAUSE_PLMN_NOTALLOWED, 16#0b).
|
||||
-define(GMM_CAUSE_GPRS_NOTALLOWED, 16#07).
|
||||
-define(GMM_CAUSE_INV_MAND_INFO, 16#60).
|
||||
-define(GMM_CAUSE_NET_FAIL, 16#11).
|
||||
% TODO: more values
|
||||
|
||||
-define(EXP_RES(Foo), #'Experimental-Result'{'Vendor-Id'=fixme, 'Experimental-Result-Code'=Foo}).
|
||||
|
||||
-type empty_or_intl() :: [] | [integer()].
|
||||
-spec gsup_cause2dia(integer()) -> {empty_or_intl(), empty_or_intl()}.
|
||||
gsup_cause2dia(?GMM_CAUSE_IMSI_UNKNOWN) -> {[], [?EXP_RES(?DIAMETER_ERROR_USER_UNKNOWN)]};
|
||||
gsup_cause2dia(?GMM_CAUSE_PLMN_NOTALLOWED) -> {[], [?DIAMETER_ERROR_ROAMING_NOT_ALLOWED]};
|
||||
gsup_cause2dia(?GMM_CAUSE_GPRS_NOTALLOWED) -> {[], [?DIAMETER_ERROR_RAT_NOT_ALLOWED]};
|
||||
%gsup_cause2dia(?GMM_CAUSE_INV_MAND_INFO) ->
|
||||
%gsup_cause2dia(?GMM_CAUSE_NET_FAIL) ->
|
||||
% TODO: more values
|
||||
gsup_cause2dia(_) -> {fixme, []}.
|
||||
|
||||
% get the value for a tiven key in Map1. If not found, try same key in Map2. If not found, return Default
|
||||
-spec twomap_get(atom(), map(), map(), any()) -> any().
|
||||
twomap_get(Key, Map1, Map2, Default) ->
|
||||
maps:get(Key, Map1, maps:get(Key, Map2, Default)).
|
||||
|
||||
handle_request(#diameter_packet{msg = Req, errors = []}, _SvcName, {_, Caps}) when is_record(Req, 'AIR') ->
|
||||
lager:info("AIR: ~p~n", [Req]),
|
||||
% extract relevant fields from DIAMETER AIR
|
||||
#diameter_caps{origin_host = {OH,_}, origin_realm = {OR,_}} = Caps,
|
||||
#'AIR'{'Session-Id' = SessionId,
|
||||
'User-Name' = UserName,
|
||||
'Visited-PLMN-Id' = VplmnId,
|
||||
'Requested-EUTRAN-Authentication-Info' = ReqEU,
|
||||
'Requested-UTRAN-GERAN-Authentication-Info' = ReqUG} = Req,
|
||||
VplmnIdBin = list_to_binary(VplmnId),
|
||||
NumEutran = req_num_of_vec(ReqEU),
|
||||
NumUgran = req_num_of_vec(ReqUG),
|
||||
lager:info("Num EUTRAN=~p, UTRAN=~p~n", [NumEutran, NumUgran]),
|
||||
% construct GSUP request to HLR and transceive it
|
||||
GsupTx1 = #{message_type => send_auth_info_req, imsi => list_to_binary(UserName),
|
||||
supported_rat_types => [rat_eutran_sgs], current_rat_type => rat_eutran_sgs},
|
||||
ResyncInfo = req_resynchronization_info(ReqEU),
|
||||
case ResyncInfo of
|
||||
false ->
|
||||
GsupTx2 = #{};
|
||||
ValidResyncInfo ->
|
||||
lager:info("ResyncInfo is valid ~p", [ResyncInfo]),
|
||||
GsupTx2 = #{rand => binary:part(ValidResyncInfo, 0, 16),
|
||||
auts => binary:part(ValidResyncInfo, 16, 14)}
|
||||
end,
|
||||
GsupTx = maps:merge(GsupTx1, GsupTx2),
|
||||
GsupRx = gen_server:call(gsup_client, {transceive_gsup, GsupTx, send_auth_info_res, send_auth_info_err}),
|
||||
lager:info("GsupRx: ~p~n", [GsupRx]),
|
||||
% construct DIAMETER AIA response
|
||||
case GsupRx of
|
||||
#{message_type:=send_auth_info_res, auth_tuples:=GsupAuthTuples} ->
|
||||
AuthInfo = gsup_tuples2dia(GsupAuthTuples, VplmnIdBin, NumEutran, NumUgran, NumUgran),
|
||||
Resp = #'AIA'{'Session-Id'=SessionId, 'Origin-Host'=OH, 'Origin-Realm'=OR,
|
||||
'Result-Code'=2001, 'Auth-Session-State'=1,
|
||||
'Authentication-Info'=AuthInfo};
|
||||
#{message_type := send_auth_info_err} ->
|
||||
Resp = #'AIA'{'Session-Id'=SessionId, 'Origin-Host'=OH, 'Origin-Realm'=OR,
|
||||
'Result-Code'=?DIAMETER_ERROR_USER_UNKNOWN,
|
||||
'Auth-Session-State'=1};
|
||||
timeout ->
|
||||
Resp = #'AIA'{'Session-Id'=SessionId, 'Origin-Host'=OH, 'Origin-Realm'=OR,
|
||||
'Result-Code'=4181, 'Auth-Session-State'=1}
|
||||
end,
|
||||
lager:info("Resp: ~p~n", [Resp]),
|
||||
{reply, Resp};
|
||||
|
||||
handle_request(#diameter_packet{msg = Req, errors = []}, _SvcName, {_, Caps}) when is_record(Req, 'ULR') ->
|
||||
% extract relevant fields from DIAMETER ULR
|
||||
#diameter_caps{origin_host = {OH,_}, origin_realm = {OR,_}} = Caps,
|
||||
#'ULR'{'Session-Id' = SessionId,
|
||||
'RAT-Type' = RatType,
|
||||
'ULR-Flags' = UlrFlags,
|
||||
'User-Name' = UserName} = Req,
|
||||
|
||||
% construct GSUP UpdateLocation request to HLR and transceive it; expect InsertSubscrDataReq
|
||||
GsupTxUlReq = #{message_type => location_upd_req, imsi => list_to_binary(UserName),
|
||||
cn_domain => 1},
|
||||
GsupRxIsdReq = gen_server:call(gsup_client,
|
||||
{transceive_gsup, GsupTxUlReq, insert_sub_data_req, location_upd_err}),
|
||||
lager:info("GsupRxIsdReq: ~p~n", [GsupRxIsdReq]),
|
||||
case GsupRxIsdReq of
|
||||
#{message_type:=location_upd_err, cause:=Cause} ->
|
||||
{Res, ExpRes} = gsup_cause2dia(Cause),
|
||||
Resp = #'ULA'{'Session-Id'= SessionId, 'Auth-Session-State'=1,
|
||||
'Origin-Host'=OH, 'Origin-Realm'=OR,
|
||||
'Result-Code'=Res, 'Experimental-Result'=ExpRes};
|
||||
#{message_type:=insert_sub_data_req} ->
|
||||
% construct GSUP InsertSubscrData response to HLR and transceive it; expect
|
||||
% UpdateLocationRes
|
||||
GsupTxIsdRes = #{message_type => insert_sub_data_res,
|
||||
imsi => list_to_binary(UserName)},
|
||||
GsupRxUlRes = gen_server:call(gsup_client,
|
||||
{transceive_gsup, GsupTxIsdRes, location_upd_res, location_upd_err}),
|
||||
lager:info("GsupRxUlRes: ~p~n", [GsupRxUlRes]),
|
||||
|
||||
case GsupRxUlRes of
|
||||
#{message_type:=location_upd_res} ->
|
||||
Msisdn = twomap_get(msisdn, GsupRxIsdReq, GsupRxUlRes, []),
|
||||
Compl = twomap_get(pdp_info_complete, GsupRxIsdReq, GsupRxUlRes, 0),
|
||||
|
||||
% build the GPRS Subscription Data
|
||||
PdpInfoList = twomap_get(pdp_info_list, GsupRxIsdReq, GsupRxUlRes, []),
|
||||
PdpContexts = lists:map(fun gsup_pdp2dia/1, PdpInfoList),
|
||||
GSubD = #'GPRS-Subscription-Data'{'Complete-Data-List-Included-Indicator'=Compl,
|
||||
'PDP-Context'=PdpContexts},
|
||||
|
||||
% build the APN-Configuration-Profile
|
||||
ApnCfgList = lists:map(fun gsup_pdp2dia_apn/1, PdpInfoList),
|
||||
ApnCfgList = lists:append(ApnCfgList, [?APN_WILDCARD]),
|
||||
FirstApn = lists:nth(1, ApnCfgList),
|
||||
DefaultCtxId = FirstApn#'APN-Configuration'.'Context-Identifier',
|
||||
ApnCfgProf = #'APN-Configuration-Profile'{'Context-Identifier' = DefaultCtxId,
|
||||
'All-APN-Configurations-Included-Indicator'=Compl,
|
||||
'APN-Configuration' = ApnCfgList},
|
||||
|
||||
% put together the Subscription-Data and finally the ULA response
|
||||
SubscrData = #'Subscription-Data'{'MSISDN' = Msisdn,
|
||||
|
||||
'Network-Access-Mode' = 0, % PACKET_AND_CIRCUIT
|
||||
'GPRS-Subscription-Data' = GSubD,
|
||||
% Subscriber-Status must be present in ULA
|
||||
'Subscriber-Status' = 0,
|
||||
% AMBR must be present if this is an ULA; let's permit 100MBps UL + DL
|
||||
'AMBR' = #'AMBR'{'Max-Requested-Bandwidth-UL' = 100000000,
|
||||
'Max-Requested-Bandwidth-DL' = 100000000},
|
||||
'APN-Configuration-Profile' = ApnCfgProf},
|
||||
Resp = #'ULA'{'Session-Id' = SessionId, 'Auth-Session-State' = 1,
|
||||
'Origin-Host' = OH, 'Origin-Realm' = OR,
|
||||
'Result-Code' = 2001,
|
||||
'Subscription-Data' = SubscrData, 'ULA-Flags' = 0};
|
||||
#{message_type:=location_upd_err, cause:=Cause} ->
|
||||
{Res, ExpRes} = gsup_cause2dia(Cause),
|
||||
Resp = #'ULA'{'Session-Id'= SessionId, 'Auth-Session-State'=1,
|
||||
'Origin-Host'=OH, 'Origin-Realm'=OR,
|
||||
'Result-Code'=Res, 'Experimental-Result'=ExpRes};
|
||||
_ ->
|
||||
Resp = #'ULA'{'Session-Id'= SessionId, 'Auth-Session-State'=1,
|
||||
'Origin-Host'=OH, 'Origin-Realm'=OR,
|
||||
'Result-Code'=fixme}
|
||||
end
|
||||
end,
|
||||
lager:info("ULR Resp: ~p~n", [Resp]),
|
||||
{reply, Resp};
|
||||
|
||||
handle_request(Packet, _SvcName, {_,_}) ->
|
||||
lager:error("Unsuppoerted message: ~p~n", [Packet]),
|
||||
discard.
|
|
@ -0,0 +1,62 @@
|
|||
%%
|
||||
%% The diameter application callback module configured by client.erl.
|
||||
%%
|
||||
-module(swx_client_cb).
|
||||
|
||||
-include_lib("diameter/include/diameter.hrl").
|
||||
-include_lib("diameter_3gpp_ts29_273_swx.hrl").
|
||||
|
||||
%% diameter callbacks
|
||||
-export([peer_up/3, peer_down/3, pick_peer/4, prepare_request/3, prepare_retransmit/3,
|
||||
handle_answer/4, handle_error/4, handle_request/3]).
|
||||
|
||||
%% peer_up/3
|
||||
peer_up(_SvcName, _Peer, State) ->
|
||||
State.
|
||||
|
||||
%% peer_down/3
|
||||
peer_down(_SvcName, _Peer, State) ->
|
||||
State.
|
||||
|
||||
%% pick_peer/4
|
||||
pick_peer([Peer | _], _, _SvcName, _State) ->
|
||||
{ok, Peer}.
|
||||
|
||||
%% prepare_request/3
|
||||
prepare_request(#diameter_packet{msg = [ T | Avps]}, _, {_, Caps}) ->
|
||||
#diameter_caps{origin_host = {OH, DH}, origin_realm = {OR, DR}} = Caps,
|
||||
{send,
|
||||
[T,
|
||||
{'Origin-Host', OH},
|
||||
{'Origin-Realm', OR},
|
||||
{'Destination-Host', [DH]},
|
||||
{'Destination-Realm', DR}
|
||||
| Avps]}.
|
||||
|
||||
%% prepare_retransmit/3
|
||||
prepare_retransmit(Packet, SvcName, Peer) ->
|
||||
prepare_request(Packet, SvcName, Peer).
|
||||
|
||||
%% handle_answer/4
|
||||
|
||||
%% Since client.erl has detached the call when using the list
|
||||
%% encoding and not otherwise, output to the terminal in the
|
||||
%% the former case, return in the latter.
|
||||
|
||||
handle_answer(#diameter_packet{msg = Msg}, Request, _SvcName, _Peer)
|
||||
when is_list(Request) ->
|
||||
lager:info("CCA: ~p~n", [Msg]),
|
||||
{ok, Msg};
|
||||
handle_answer(#diameter_packet{msg = Msg}, _Request, _SvcName, _Peer) ->
|
||||
{ok, Msg}.
|
||||
|
||||
%% handle_error/4
|
||||
handle_error(Reason, Request, _SvcName, _Peer) when is_list(Request) ->
|
||||
lager:error("error: ~p~n", [Reason]),
|
||||
{error, Reason};
|
||||
handle_error(Reason, _Request, _SvcName, _Peer) ->
|
||||
{error, Reason}.
|
||||
|
||||
%% handle_request/3
|
||||
handle_request(_Packet, _SvcName, _Peer) ->
|
||||
erlang:error({unexpected, ?MODULE, ?LINE}).
|
Loading…
Reference in New Issue