From 3c15ff32e89e401b4dde2b8acc9699be2614b996 Mon Sep 17 00:00:00 2001 From: Anders Svensson Date: Wed, 18 May 2011 18:29:12 +0200 Subject: Initial commit of the diameter application. The application provides an implementation of the Diameter protocol as defined in RFC 3588. --- lib/diameter/examples/.gitignore | 3 + lib/diameter/examples/GNUmakefile | 35 +++++++++ lib/diameter/examples/client.erl | 125 ++++++++++++++++++++++++++++++ lib/diameter/examples/client_cb.erl | 103 +++++++++++++++++++++++++ lib/diameter/examples/peer.erl | 139 ++++++++++++++++++++++++++++++++++ lib/diameter/examples/redirect.erl | 70 +++++++++++++++++ lib/diameter/examples/redirect_cb.erl | 63 +++++++++++++++ lib/diameter/examples/relay.erl | 92 ++++++++++++++++++++++ lib/diameter/examples/relay_cb.erl | 69 +++++++++++++++++ lib/diameter/examples/sctp.erl | 113 +++++++++++++++++++++++++++ lib/diameter/examples/server.erl | 88 +++++++++++++++++++++ lib/diameter/examples/server_cb.erl | 118 +++++++++++++++++++++++++++++ 12 files changed, 1018 insertions(+) create mode 100644 lib/diameter/examples/.gitignore create mode 100644 lib/diameter/examples/GNUmakefile create mode 100644 lib/diameter/examples/client.erl create mode 100644 lib/diameter/examples/client_cb.erl create mode 100644 lib/diameter/examples/peer.erl create mode 100644 lib/diameter/examples/redirect.erl create mode 100644 lib/diameter/examples/redirect_cb.erl create mode 100644 lib/diameter/examples/relay.erl create mode 100644 lib/diameter/examples/relay_cb.erl create mode 100644 lib/diameter/examples/sctp.erl create mode 100644 lib/diameter/examples/server.erl create mode 100644 lib/diameter/examples/server_cb.erl (limited to 'lib/diameter/examples') diff --git a/lib/diameter/examples/.gitignore b/lib/diameter/examples/.gitignore new file mode 100644 index 0000000000..d7995d4e6b --- /dev/null +++ b/lib/diameter/examples/.gitignore @@ -0,0 +1,3 @@ + +*.beam + diff --git a/lib/diameter/examples/GNUmakefile b/lib/diameter/examples/GNUmakefile new file mode 100644 index 0000000000..4c3f87939b --- /dev/null +++ b/lib/diameter/examples/GNUmakefile @@ -0,0 +1,35 @@ +# +# %CopyrightBegin% +# +# Copyright Ericsson AB 2010-2011. All Rights Reserved. +# +# The contents of this file are subject to the Erlang Public License, +# Version 1.1, (the "License"); you may not use this file except in +# compliance with the License. You should have received a copy of the +# Erlang Public License along with this software. If not, it can be +# retrieved online at http://www.erlang.org/. +# +# Software distributed under the License is distributed on an "AS IS" +# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +# the License for the specific language governing rights and limitations +# under the License. +# +# %CopyrightEnd% +# + +EXAMPLES = client server relay # redirect proxy + +CALLBACKS = $(EXAMPLES:%=%_cb) +MODULES = peer $(EXAMPLES) $(EXAMPLES:%=%_cb) + +BEAM = $(MODULES:%=%.beam) + +%.beam: %.erl + erlc -W $< + +all: $(BEAM) + +clean: + rm -f $(BEAM) + +.PHONY: all clean diff --git a/lib/diameter/examples/client.erl b/lib/diameter/examples/client.erl new file mode 100644 index 0000000000..36a77dd524 --- /dev/null +++ b/lib/diameter/examples/client.erl @@ -0,0 +1,125 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2010-2011. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +%% An example Diameter client that can sends base protocol RAR +%% requests to a connected peer. +%% +%% The simplest usage is as follows this to connect to a server +%% listening on the default port on the local host, assuming diameter +%% is already started (eg. diameter:start()). +%% +%% client:start(). +%% client:connect(tcp). +%% client:call(). +%% +%% The first call starts the a service with the default name of +%% ?MODULE, the second defines a connecting transport that results in +%% a connection to the peer (if it's listening), the third sends it a +%% RAR and returns the answer. +%% + +-module(client). + +-include_lib("diameter/include/diameter.hrl"). +-include_lib("diameter/src/app/diameter_gen_base_rfc3588.hrl"). + +-export([start/1, %% start a service + connect/2, %% add a connecting transport + call/1, %% send using the record encoding + cast/1, %% send using the list encoding and detached + stop/1]). %% stop a service +%% A real application would typically choose an encoding and whether +%% they want the call to return the answer or not. Sending with +%% both the record and list encoding here, one detached and one not, +%% is just for demonstration purposes. + +%% Convenience functions using the default service name, ?SVC_NAME. +-export([start/0, + connect/1, + stop/0, + call/0, + cast/0]). + +-define(SVC_NAME, ?MODULE). +-define(APP_ALIAS, ?MODULE). +-define(CALLBACK_MOD, client_cb). + +-define(L, atom_to_list). + +%% 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', ?L(Name) ++ ".example.com"}, + {'Origin-Realm', "example.com"}, + {'Vendor-Id', 0}, + {'Product-Name', "Client"}, + {'Auth-Application-Id', [?DIAMETER_APP_ID_COMMON]}, + {application, [{alias, ?APP_ALIAS}, + {dictionary, ?DIAMETER_DICT_COMMON}, + {module, ?CALLBACK_MOD}]}]). + +%% start/1 + +start(Name) + when is_atom(Name) -> + peer:start(Name, ?SERVICE(Name)). + +start() -> + start(?SVC_NAME). + +%% connect/2 + +connect(Name, T) -> + peer:connect(Name, T). + +connect(T) -> + connect(?SVC_NAME, T). + +%% call/1 + +call(Name) -> + SId = diameter:session_id(?L(Name)), + RAR = #diameter_base_RAR{'Session-Id' = SId, + 'Auth-Application-Id' = 0, + 'Re-Auth-Request-Type' = 0}, + diameter:call(Name, ?APP_ALIAS, RAR, []). + +call() -> + call(?SVC_NAME). + +%% cast/1 + +cast(Name) -> + SId = diameter:session_id(?L(Name)), + RAR = ['RAR', {'Session-Id', SId}, + {'Auth-Application-Id', 0}, + {'Re-Auth-Request-Type', 1}], + diameter:call(Name, ?APP_ALIAS, RAR, [detach]). + +cast() -> + cast(?SVC_NAME). + +%% stop/1 + +stop(Name) -> + peer:stop(Name). + +stop() -> + stop(?SVC_NAME). diff --git a/lib/diameter/examples/client_cb.erl b/lib/diameter/examples/client_cb.erl new file mode 100644 index 0000000000..524a8f94a1 --- /dev/null +++ b/lib/diameter/examples/client_cb.erl @@ -0,0 +1,103 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2010-2011. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +-module(client_cb). + +-include_lib("diameter/include/diameter.hrl"). +-include_lib("diameter/src/app/diameter_gen_base_rfc3588.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 = ['RAR' = 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_request(#diameter_packet{msg = Rec}, _, {_, Caps}) -> + #diameter_caps{origin_host = {OH, DH}, + origin_realm = {OR, DR}} + = Caps, + + {send, Rec#diameter_base_RAR{'Origin-Host' = OH, + 'Origin-Realm' = OR, + 'Destination-Host' = DH, + 'Destination-Realm' = DR}}. + +%% 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) -> + io:format("answer: ~p~n", [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) -> + io:format("error: ~p~n", [Reason]); + +handle_error(Reason, _Request, _SvcName, _Peer) -> + {error, Reason}. + +%% handle_request/3 + +handle_request(_Packet, _SvcName, _Peer) -> + erlang:error({unexpected, ?MODULE, ?LINE}). diff --git a/lib/diameter/examples/peer.erl b/lib/diameter/examples/peer.erl new file mode 100644 index 0000000000..89203e15c3 --- /dev/null +++ b/lib/diameter/examples/peer.erl @@ -0,0 +1,139 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2010-2011. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +%% A library module that factors out commonality in the example +%% Diameter peers. +%% + +-module(peer). + +-include_lib("diameter/include/diameter.hrl"). +-include_lib("diameter/src/app/diameter_gen_base_rfc3588.hrl"). + +-export([start/2, + listen/2, + connect/2, + stop/1]). + +-type service_name() + :: term(). + +-type protocol() + :: tcp | sctp. + +-type ip_address() + :: default + | inet:ip_address(). + +-type server_config() + :: protocol() + | {protocol(), ip_address(), non_neg_integer()}. + +-type client_config() + :: protocol() + | {protocol(), ip_address(), non_neg_integer()} + | {protocol(), ip_address(), ip_address(), non_neg_integer()}. + +-define(DEFAULT_ADDR, {127,0,0,1}). +-define(DEFAULT_PORT, 3868). + +%% --------------------------------------------------------------------------- +%% Interface functions +%% --------------------------------------------------------------------------- + +%% start/2 + +-spec start(service_name(), list()) + -> ok + | {error, term()}. + +start(Name, Opts) + when is_atom(Name), is_list(Opts) -> + diameter:start_service(Name, Opts). + +%% connect/2 + +-spec connect(service_name(), client_config()) + -> {ok, reference()} + | {error, term()}. + +connect(Name, T) -> + diameter:add_transport(Name, {connect, [{reconnect_timer, 5000} + | client(T)]}). + +%% listen/2 + +-spec listen(service_name(), server_config()) + -> {ok, reference()} + | {error, term()}. + +listen(Name, T) -> + diameter:add_transport(Name, {listen, server(T)}). + +%% stop/1 + +-spec stop(service_name()) + -> ok + | {error, term()}. + +stop(Name) -> + diameter:stop_service(Name). + +%% --------------------------------------------------------------------------- +%% Internal functions +%% --------------------------------------------------------------------------- + +%% server/1 +%% +%% Return config for a listening transport. + +server({T, Addr, Port}) -> + [{transport_module, tmod(T)}, + {transport_config, [{reuseaddr, true}, + {ip, addr(Addr)}, + {port, Port}]}]; + +server(T) -> + server({T, ?DEFAULT_ADDR, ?DEFAULT_PORT}). + +%% client/1 +%% +%% Return config for a connecting transport. + +client({T, LA, RA, RP}) -> + [{transport_module, tmod(T)}, + {transport_config, [{ip, addr(LA)}, + {raddr, addr(RA)}, + {rport, RP}, + {reuseaddr, true}]}]; + +client({T, LA, RP}) -> + client({T, LA, LA, RP}); + +client(T) -> + client({T, ?DEFAULT_ADDR, ?DEFAULT_ADDR, ?DEFAULT_PORT}). + +tmod(tcp) -> diameter_tcp; +tmod(sctp) -> diameter_sctp. + +addr(default) -> + ?DEFAULT_ADDR; +addr(A) -> + A. diff --git a/lib/diameter/examples/redirect.erl b/lib/diameter/examples/redirect.erl new file mode 100644 index 0000000000..b54701243f --- /dev/null +++ b/lib/diameter/examples/redirect.erl @@ -0,0 +1,70 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2010-2011. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +-module(redirect). + +-include_lib("diameter/include/diameter.hrl"). +-include_lib("diameter/src/app/diameter_gen_base_rfc3588.hrl"). + +-export([start/1, + listen/2, + stop/1]). + +-export([start/0, + listen/1, + stop/0]). + +-define(APP_ALIAS, ?MODULE). +-define(SVC_NAME, ?MODULE). +-define(CALLBACK_MOD, redirect_mod). + +%% The service configuration. +-define(SERVICE(Name), [{'Origin-Host', atom_to_list(Name) ++ ".example.com"}, + {'Origin-Realm', "example.com"}, + {'Vendor-Id', 193}, + {'Product-Name', "RedirectAgent"}, + {'Auth-Application-Id', [?DIAMETER_APP_ID_RELAY]}, + {application, [{alias, ?APP_ALIAS}, + {dictionary, ?DIAMETER_DICT_RELAY}, + {module, ?CALLBACK_MOD}]}]). + +%% start/1 + +start(Name) + when is_atom(Name) -> + peer:start(Name, ?SERVICE(Name)). + +start() -> + start(?SVC_NAME). + +%% listen/2 + +listen(Name, T) -> + peer:listen(Name, T). + +listen(T) -> + listen(?SVC_NAME, T). + +%% stop/1 + +stop(Name) -> + peer:stop(Name). + +stop() -> + stop(?SVC_NAME). diff --git a/lib/diameter/examples/redirect_cb.erl b/lib/diameter/examples/redirect_cb.erl new file mode 100644 index 0000000000..ea7ad38749 --- /dev/null +++ b/lib/diameter/examples/redirect_cb.erl @@ -0,0 +1,63 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2010-2011. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +-module(redirect_cb). + +-include_lib("diameter/include/diameter.hrl"). +-include_lib("diameter/src/app/diameter_gen_base_rfc3588.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, _}, State) -> + io:format("up: ~p~n", [PeerRef]), + State. + +peer_down(_SvcName, {PeerRef, _}, State) -> + io:format("down: ~p~n", [PeerRef]), + 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) -> + ?UNEXPECTED. + +handle_request(#diameter_packet{msg = _, errors = []}, _SvcName, {_, Caps}) -> + #diameter_caps{} + = Caps, + discard. %% TODO diff --git a/lib/diameter/examples/relay.erl b/lib/diameter/examples/relay.erl new file mode 100644 index 0000000000..deecb1cfc0 --- /dev/null +++ b/lib/diameter/examples/relay.erl @@ -0,0 +1,92 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2010-2011. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +%% An example Diameter relay agent. +%% +%% Usage to connect to a server listening on the default port over TCP +%% and to listen on the default port over SCTP is as follows, assuming +%% diameter is already started (eg. diameter:start()). +%% +%% Eg. relay:start(). +%% relay:connect(tcp). +%% relay:listen(sctp). +%% + +-module(relay). + +-include_lib("diameter/include/diameter.hrl"). +-include_lib("diameter/src/app/diameter_gen_base_rfc3588.hrl"). + +-export([start/1, + listen/2, + connect/2, + stop/1]). + +-export([start/0, + listen/1, + connect/1, + stop/0]). + +-define(APP_ALIAS, ?MODULE). +-define(SVC_NAME, ?MODULE). +-define(CALLBACK_MOD, relay_cb). + +%% The service configuration. +-define(SERVICE(Name), [{'Origin-Host', atom_to_list(Name) ++ ".example.com"}, + {'Origin-Realm', "example.com"}, + {'Vendor-Id', 193}, + {'Product-Name', "RelayAgent"}, + {'Auth-Application-Id', [?DIAMETER_APP_ID_RELAY]}, + {application, [{alias, ?MODULE}, + {dictionary, ?DIAMETER_DICT_RELAY}, + {module, ?CALLBACK_MOD}]}]). + +%% start/1 + +start(Name) + when is_atom(Name) -> + peer:start(Name, ?SERVICE(Name)). + +start() -> + start(?SVC_NAME). + +%% listen/2 + +listen(Name, T) -> + peer:listen(Name, T). + +listen(T) -> + listen(?SVC_NAME, T). + +%% connect/2 + +connect(Name, T) -> + peer:connect(Name, T). + +connect(T) -> + connect(?SVC_NAME, T). + +%% stop/1 + +stop(Name) -> + peer:stop(Name). + +stop() -> + stop(?SVC_NAME). diff --git a/lib/diameter/examples/relay_cb.erl b/lib/diameter/examples/relay_cb.erl new file mode 100644 index 0000000000..9ed6517d5c --- /dev/null +++ b/lib/diameter/examples/relay_cb.erl @@ -0,0 +1,69 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2010-2011. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +-module(relay_cb). + +-include_lib("diameter/include/diameter.hrl"). +-include_lib("diameter/src/app/diameter_gen_base_rfc3588.hrl"). + +%% diameter callbacks +-export([peer_up/3, + peer_down/3, + pick_peer/5, + prepare_request/4, + prepare_retransmit/4, + handle_answer/5, + handle_error/5, + handle_request/3]). + +peer_up(_SvcName, {PeerRef, _}, State) -> + io:format("up: ~p~n", [PeerRef]), + State. + +peer_down(_SvcName, {PeerRef, _}, State) -> + io:format("down: ~p~n", [PeerRef]), + State. + +%% Returning 'relay' from handle_request causes diameter to resend the +%% incoming request, which leads to pick_peer and prepare_request +%% callbacks as if sending explicitly. The 'extra' argument is +%% appended to the argument list for callbacks following from +%% resending of the request. + +handle_request(_Pkt, _SvcName, _Peer) -> + {relay, [{timeout, 1000}, {extra, [relayed]}]}. + +%% diameter will filter the sender in the Peers list. +pick_peer([Peer | _], _, _SvcName, _State, relayed) -> + {ok, Peer}. + +prepare_request(Pkt, _SvcName, _Peer, relayed) -> + {send, Pkt}. + +prepare_retransmit(Pkt, _SvcName, _Peer, relayed) -> + {send, Pkt}. + +%% diameter expects handle_answer to return the diameter_packet record +%% containing the answer when called for a relayed request. + +handle_answer(Pkt, _Request, _SvcName, _Peer, relayed) -> + Pkt. + +handle_error(Reason, _Request, _SvcName, _Peer, relayed) -> + {error, Reason}. diff --git a/lib/diameter/examples/sctp.erl b/lib/diameter/examples/sctp.erl new file mode 100644 index 0000000000..2e0e9d8b0b --- /dev/null +++ b/lib/diameter/examples/sctp.erl @@ -0,0 +1,113 @@ + +-module(sctp). + +%% +%% A small example demonstrating the establishment of an SCTP +%% association with gen_sctp. +%% + +-export([assoc/0, + dbg/0]). + +-include_lib("kernel/include/inet_sctp.hrl"). + +-define(ADDR, {127,0,0,1}). +-define(SERVER_PORT, 3868). +-define(CONNECT_TIMEOUT, 2000). +-define(REQUEST, <<0:64>>). +-define(REPLY, <<1:64>>). + +-record(server, {client, + socket, + assoc}). + +-record(client, {socket, + assoc}). + +%% assoc/0 +%% +%% Return on a successfully established association, raise an +%% exception otherwise. + +assoc() -> + {_, MRef} = spawn_monitor(fun server/0), + receive {'DOWN', MRef, process, _, Info} -> Info end. + +%% dbg/0 + +dbg() -> + dbg:tracer(), + dbg:p(all,c), + dbg:tpl(?MODULE, [{'_',[],[{exception_trace}]}]), + dbg:tp(gen_sctp, [{'_',[],[{exception_trace}]}]), + ok. + +%% server/0 + +server() -> + {ok, Sock} = gen_sctp:open([binary, + {reuseaddr, true}, + {active, true}, + {ip, ?ADDR}, + {port, ?SERVER_PORT}]), + ok = gen_sctp:listen(Sock, true), + {_Pid, MRef} = spawn_monitor(fun client/0), + s(#server{client = MRef, socket = Sock}), + gen_sctp:close(Sock). + +%% s/1 + +s(#server{} = S) -> + s(s(receive T -> T end, S)); +s(T) -> + T. + +%% s/2 + +s({sctp, Sock, _RA, _RP, {[], #sctp_assoc_change{state = comm_up, + assoc_id = Id}}}, + #server{socket = Sock, assoc = undefined} + = S) -> + S#server{assoc = Id}; + +s({sctp, Sock, _RA, _RP, {[#sctp_sndrcvinfo{assoc_id = AId, + stream = SId}], + ?REQUEST}}, + #server{socket = Sock, assoc = AId} + = S) -> + ok = gen_sctp:send(Sock, AId, SId, ?REPLY), + S; + +s({'DOWN', MRef, process, _, normal} = T, #server{client = MRef}) -> + T. + +%% client/0 + +client() -> + {ok, Sock} = gen_sctp:open([binary, + {reuseaddr, true}, + {active, true}, + {ip, ?ADDR}, + {port, 0}]), + ok = gen_sctp:connect_init(Sock, ?ADDR, ?SERVER_PORT, []), + c(#client{socket = Sock}), + gen_sctp:close(Sock). + + +%% c/1 + +c(#client{} = S) -> + c(c(receive T -> T end, S)); +c(T) -> + T. + +c({sctp, Sock, _RA, _RP, {[], #sctp_assoc_change{state = comm_up, + assoc_id = Id}}}, + #client{socket = Sock, assoc = undefined} + = S) -> + ok = gen_sctp:send(Sock, Id, 0, ?REQUEST), + S#client{assoc = Id}; + +c({sctp, Sock, _RA, _RP, {[#sctp_sndrcvinfo{assoc_id = AId}], ?REPLY}}, + #client{socket = Sock, assoc = AId}) -> + ok. diff --git a/lib/diameter/examples/server.erl b/lib/diameter/examples/server.erl new file mode 100644 index 0000000000..ebb408e501 --- /dev/null +++ b/lib/diameter/examples/server.erl @@ -0,0 +1,88 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2010-2011. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +%% An example Diameter server that can respond to the base protocol +%% RAR sent by the client example. +%% +%% The simplest example to start a server listening on the loopback +%% address (which will serve the example usage given in client.erl) is +%% like this assuming diameter is already started (eg. diameter:start()): +%% +%% server:start(). +%% server:listen(tcp). +%% +%% The first call starts a service, the second adds a transport listening +%% on the default port. +%% + +-module(server). + +-include_lib("diameter/include/diameter.hrl"). +-include_lib("diameter/src/app/diameter_gen_base_rfc3588.hrl"). + +-export([start/1, %% start a service + listen/2, %% add a listening transport + stop/1]). %% stop a service + +%% Convenience functions using the default service name, ?SVC_NAME. +-export([start/0, + listen/1, + stop/0]). + +-define(SVC_NAME, ?MODULE). +-define(APP_ALIAS, ?MODULE). +-define(CALLBACK_MOD, server_cb). + +%% The service configuration. In a server supporting multiple Diameter +%% applications each application may have its own, although they could all +%% be configured with a common callback module. +-define(SERVICE(Name), [{'Origin-Host', atom_to_list(Name) ++ ".example.com"}, + {'Origin-Realm', "example.com"}, + {'Vendor-Id', 193}, + {'Product-Name', "Server"}, + {'Auth-Application-Id', [?DIAMETER_APP_ID_COMMON]}, + {application, [{alias, ?APP_ALIAS}, + {dictionary, ?DIAMETER_DICT_COMMON}, + {module, ?CALLBACK_MOD}]}]). + +%% start/1 + +start(Name) + when is_atom(Name) -> + peer:start(Name, ?SERVICE(Name)). + +start() -> + start(?SVC_NAME). + +%% listen/2 + +listen(Name, T) -> + peer:listen(Name, T). + +listen(T) -> + listen(?SVC_NAME, T). + +%% stop/1 + +stop(Name) -> + peer:stop(Name). + +stop() -> + stop(?SVC_NAME). diff --git a/lib/diameter/examples/server_cb.erl b/lib/diameter/examples/server_cb.erl new file mode 100644 index 0000000000..b8705aedfc --- /dev/null +++ b/lib/diameter/examples/server_cb.erl @@ -0,0 +1,118 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2010-2011. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +%% The diameter application callback module configured by server.erl. +%% + +-module(server_cb). + +-include_lib("diameter/include/diameter.hrl"). +-include_lib("diameter/src/app/diameter_gen_base_rfc3588.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, _}, State) -> + io:format("up: ~p~n", [PeerRef]), + State. + +peer_down(_SvcName, {PeerRef, _}, State) -> + io:format("down: ~p~n", [PeerRef]), + 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) -> + ?UNEXPECTED. + +%% A request whose decode was successful ... +handle_request(#diameter_packet{msg = Req, errors = []}, _SvcName, {_, Caps}) + when is_record(Req, diameter_base_RAR) -> + #diameter_caps{origin_host = {OH,_}, + origin_realm = {OR,_}} + = Caps, + #diameter_base_RAR{'Session-Id' = Id, + 'Re-Auth-Request-Type' = RT} + = Req, + + {reply, answer(RT, Id, OH, OR)}; + +%% ... or one that wasn't. 3xxx errors are answered by diameter itself +%% but these are non-3xxx errors for which we must contruct a reply. +%% Returning a packet with the non-[] errors field will cause +%% diameter to add the appropriate result code and Failed-AVPs avps. +%% We just have to return the relevant answer record with any required +%% avps. +handle_request(#diameter_packet{msg = Req} = Pkt, _SvcName, {_, Caps}) + when is_record(Req, diameter_base_RAR) -> + #diameter_caps{origin_host = {OH,_}, + origin_realm = {OR,_}} + = Caps, + #diameter_base_RAR{'Session-Id' = Id} + = Req, + + Ans = #diameter_base_RAA{'Origin-Host' = OH, + 'Origin-Realm' = OR, + 'Session-Id' = Id}, + + {reply, Pkt#diameter_packet{msg = Ans}}; + +%% Should really reply to other base messages that we don't support +%% but simply discard them instead. +handle_request(#diameter_packet{}, _SvcName, {_,_}) -> + discard. + +%% --------------------------------------------------------------------------- + +%% Answer using the record or list encoding depending on +%% Re-Auth-Request-Type. This is just as an example. You would +%% typically just choose one, and this has nothing to do with the how +%% client.erl sends. + +answer(0, Id, OH, OR) -> + #diameter_base_RAA{'Result-Code' = 2001, %% DIAMETER_SUCCESS + 'Origin-Host' = OH, + 'Origin-Realm' = OR, + 'Session-Id' = Id}; + +answer(_, Id, OH, OR) -> + ['RAA', {'Result-Code', 5012}, %% DIAMETER_UNABLE_TO_COMPLY + {'Origin-Host', OH}, + {'Origin-Realm', OR}, + {'Session-Id', Id}]. -- cgit v1.2.3