diff options
Diffstat (limited to 'lib/diameter')
-rw-r--r-- | lib/diameter/src/base/diameter_dist.erl | 174 | ||||
-rw-r--r-- | lib/diameter/test/diameter_pool_SUITE.erl | 3 |
2 files changed, 136 insertions, 41 deletions
diff --git a/lib/diameter/src/base/diameter_dist.erl b/lib/diameter/src/base/diameter_dist.erl index cef9522c9d..ed2859e914 100644 --- a/lib/diameter/src/base/diameter_dist.erl +++ b/lib/diameter/src/base/diameter_dist.erl @@ -47,6 +47,8 @@ code_change/3, terminate/2]). +-type request() :: tuple(). %% callback argument from diameter_traffic + -define(SERVER, ?MODULE). %% server monitoring node connections -define(TABLE, ?MODULE). %% node() binary -> node() atom @@ -61,6 +63,9 @@ %% {spawn_opt, Opts} %% {spawn_opt, {diameter_dist, spawn_local, [Opts]}} +-spec spawn_local(ReqT :: request(), Opts :: list()) + -> pid(). + spawn_local(ReqT, Opts) -> spawn_opt(diameter_traffic, request, [ReqT], Opts). @@ -74,12 +79,48 @@ spawn_local(ReqT) -> %% Callback that routes requests containing Session-Id AVPs as %% returned by diameter:session_id/0 back to the node on which the %% function was called. This is only appropriate when sessions are -%% initiated by the own (typically client) node, and ids have been -%% returned from diameter:session_id/0. +%% only initiated by the own (typically client) node, and ids have +%% been returned from diameter:session_id/0. +%% +%% This can be used with #{search => 0} to route on something other +%% than Session-Id since default can be an MFA returning a node() +%% (applied to the incoming diameter_packet record) and dispatch can +%% be an MFA returning a pid() (applied to Node and the request MFA), +%% but this is no simpler than just implementing an own spawn_opt +%% callback. (Except with the default dispatch possibly.) + +-spec route_session(ReqT :: request(), Opts) + -> discard + | pid() + when Opts :: pos_integer() %% aka #{search => N} + | list() %% aka #{dispatch => Opts} + | #{search => non_neg_integer(), %% limit number of examined AVPs + default => discard | mfa(), %% return node() | false + dispatch => list() | mfa()}. %% spawn options or return pid() route_session(ReqT, Opts) -> - #diameter_packet{bin = Bin} = element(1, ReqT), - Node = node_of_session_id(Bin), + #diameter_packet{bin = Bin} = Pkt = element(1, ReqT), + Sid = session_id(avps(Bin), search(Opts)), + Node = default(node_of_session_id(Sid), Sid, Opts, Pkt), + dispatch(Node, ReqT, dispatch(Opts)). + +%% avps/1 + +avps(<<_:20/binary, Bin/binary>>) -> + Bin; + +avps(_) -> + false. + +%% dispatch/3 + +dispatch(false, _, _) -> + discard; + +dispatch(Node, ReqT, {M,F,A}) -> + apply(M, F, [Node, diameter_traffic, request, [ReqT] | A]); + +dispatch(Node, ReqT, Opts) -> spawn_opt(Node, diameter_traffic, request, [ReqT], Opts). %% route_session/1 @@ -90,27 +131,34 @@ route_session(ReqT) -> %% node_of_session_id/1 %% %% Return the node name encoded as optional value in a Session-Id, -%% assuming the id has been created with diameter:session_id/0. -%% -%% node() is returned if a node name can't be extracted for any -%% reason. +%% assuming the id has been created with diameter:session_id/0. Lookup +%% the node name to ensure we don't convert arbitrary binaries to +%% atom. -node_of_session_id(<<_Head:20/binary, Avps/binary>>) -> - sid_node(Avps); +node_of_session_id([_, _, _, Bin]) -> + case ets:lookup(?TABLE, Bin) of + [{_, Node}] -> + Node; + [] -> + false + end; node_of_session_id(_) -> - node(). + false. + +%% session_id/2 -%% sid_node/1 +session_id(_, 0) -> %% give up + false; %% Session-Id = Command Code 263, V-bit = 0. -sid_node(<<263:32, 0:1, _:7, Len:24, _/binary>> = Bin) -> +session_id(<<263:32, 0:1, _:7, Len:24, _/binary>> = Bin, _) -> case Bin of <<Avp:Len/binary>> -> <<_:8/binary, Sid/binary>> = Avp, - sid_node(Sid, pattern(), 2); %% look for the optional value + split(Sid); _ -> - node() + false end; %% Jump to the next AVP. This is potentially costly for a message with @@ -118,38 +166,41 @@ sid_node(<<263:32, 0:1, _:7, Len:24, _/binary>> = Bin) -> %% 8.8 or RFC 6733 says that Session-Id SHOULD (but not MUST) appear %% immediately following the Diameter Header, so there is no %% guarantee. -sid_node(<<_:40, Len:24, _/binary>> = Bin) -> +session_id(<<_:40, Len:24, _/binary>> = Bin, N) -> Pad = (4 - (Len rem 4)) rem 4, case Bin of <<_:Len/binary, _:Pad/binary, Rest/binary>> -> - sid_node(Rest); + session_id(Rest, if N == infinity -> N; true -> N-1 end); _ -> - node() - end. + false + end; -%% sid_node/2 +session_id(_, _) -> + false. -%% Lookup the node name to ensure we don't convert arbitrary binaries -%% to atom. -sid_node(Bin, _, 0) -> - case ets:lookup(?TABLE, Bin) of - [{_, Node}] -> - Node; - [] -> - node() - end; +%% split/1 +%% +%% Split a Session-Id at no more than three semicolons: the optional +%% value (if any) follows the third. binary:split/2 does better than +%% matching character by character, especially when the pattern is +%% compiled. -%% The optional value (if any) of a Session-Id follows the third -%% semicolon. Searching with binary:match/2 does better than matching, -%% especially when the pattern is compiled. -sid_node(Bin, CP, N) -> - case binary:match(Bin, CP) of - {Offset, 1} -> - <<_:Offset/binary, _, Rest/binary>> = Bin, - sid_node(Rest, CP, N-1); - nomatch -> - node() - end. +split(Bin) -> + split(3, Bin, pattern()). + +%% split/3 + +split(0, Bin, _) -> + [Bin]; + +split(N, Bin, Pattern) -> + [H|T] = binary:split(Bin, Pattern), + [H | case T of + [] -> + T; + [Rest] -> + split(N-1, Rest, Pattern) + end]. %% pattern/0 %% @@ -166,6 +217,49 @@ pattern() -> CP end. +%% dispatch/1 + +dispatch(#{} = Opts) -> + maps:get(dispatch, Opts, []); + +dispatch(Opts) + when is_list(Opts) -> + Opts; + +dispatch(_) -> + []. + +%% search/1 +%% +%% Bound number of AVPs examined when looking for Session-Id. + +search(#{search := N}) + when is_integer(N), 0 =< N -> + N; + +search(N) + when is_integer(N), 0 =< N -> + N; + +search(_) -> + infinity. + +%% default/3 +%% +%% Choose a node when Session-Id lookup has failed. + +default(false = No, _, #{default := discard}, _) -> + No; + +default(false, Sid, #{default := {M,F,A}}, Pkt) -> + apply(M, F, [Sid, Pkt | A]); %% false | node() + +default(false, _, _, _) -> + node(); + +default(Node, _, _, _) -> + Node. + %% =========================================================================== start_link() -> diff --git a/lib/diameter/test/diameter_pool_SUITE.erl b/lib/diameter/test/diameter_pool_SUITE.erl index 97c16940ff..a36a4fa17a 100644 --- a/lib/diameter/test/diameter_pool_SUITE.erl +++ b/lib/diameter/test/diameter_pool_SUITE.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2015-2017. All Rights Reserved. +%% Copyright Ericsson AB 2015-2019. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -51,6 +51,7 @@ {'Auth-Application-Id', [0]}, %% common {'Acct-Application-Id', [3]}, %% accounting {restrict_connections, false}, + {spawn_opt, {diameter_dist, route_session, []}}, {application, [{alias, common}, {dictionary, diameter_gen_base_rfc6733}, {module, diameter_callback}]}, |