diff --git a/src/osmo_dia2gsup.app.src b/src/epdg.app.src similarity index 54% rename from src/osmo_dia2gsup.app.src rename to src/epdg.app.src index 941573a..4a11ef8 100644 --- a/src/osmo_dia2gsup.app.src +++ b/src/epdg.app.src @@ -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, []} ]}. diff --git a/src/osmo_dia2gsup_app.erl b/src/epdg_app.erl similarity index 64% rename from src/osmo_dia2gsup_app.erl rename to src/epdg_app.erl index c05cd3c..8bf2279 100644 --- a/src/osmo_dia2gsup_app.erl +++ b/src/epdg_app.erl @@ -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. diff --git a/src/epdg_diameter_swx.erl b/src/epdg_diameter_swx.erl new file mode 100644 index 0000000..9180d4e --- /dev/null +++ b/src/epdg_diameter_swx.erl @@ -0,0 +1,184 @@ +% SWx: AAA side +% +% TS 29.273 +% +% (C) 2023 by sysmocom - s.m.f.c. GmbH +% Author: Alexander Couzens +% +% 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 . +% +% 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 '). + +-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. + + diff --git a/src/epdg_sup.erl b/src/epdg_sup.erl new file mode 100644 index 0000000..05ae8a4 --- /dev/null +++ b/src/epdg_sup.erl @@ -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]} }. diff --git a/src/osmo_dia2gsup.erl b/src/osmo_dia2gsup.erl deleted file mode 100644 index a4edd71..0000000 --- a/src/osmo_dia2gsup.erl +++ /dev/null @@ -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. diff --git a/src/osmo_dia2gsup_sup.erl b/src/osmo_dia2gsup_sup.erl deleted file mode 100644 index 17b5a14..0000000 --- a/src/osmo_dia2gsup_sup.erl +++ /dev/null @@ -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]} }. diff --git a/src/server_cb.erl b/src/server_cb.erl deleted file mode 100644 index 4db57ef..0000000 --- a/src/server_cb.erl +++ /dev/null @@ -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 = <>, - 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. diff --git a/src/swx_client_cb.erl b/src/swx_client_cb.erl new file mode 100644 index 0000000..a792d70 --- /dev/null +++ b/src/swx_client_cb.erl @@ -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}).