aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--lib/diameter/doc/src/diameter.xml36
-rw-r--r--lib/diameter/src/base/diameter.erl3
-rw-r--r--lib/diameter/src/base/diameter_peer_fsm.erl90
-rw-r--r--lib/diameter/src/base/diameter_traffic.erl32
4 files changed, 114 insertions, 47 deletions
diff --git a/lib/diameter/doc/src/diameter.xml b/lib/diameter/doc/src/diameter.xml
index 7e50f338d3..ba9225da8b 100644
--- a/lib/diameter/doc/src/diameter.xml
+++ b/lib/diameter/doc/src/diameter.xml
@@ -975,6 +975,42 @@ configured them.</p>
Defaults to a single callback returning <c>dpr</c>.</p>
</item>
+<marker id="length_errors"/>
+<tag><c>{length_errors, exit|handle|discard}</c></tag>
+<item>
+<p>
+Specifies how to deal with errors in the Message Length field of the
+Diameter Header in an incoming message.
+An error in this context is that the length is not at least 20 bytes
+(the length of a Header), is not a multiple of 4 (a valid length) or
+is not the length of the message in question, as received over the
+transport interface documented in &man_transport;.</p>
+
+<p>
+If <c>exit</c> then a warning report is emitted and the parent of the
+transport process in question exits, which causes the transport
+process itself to exit as described in &man_transport;.
+If <c>handle</c> then the message is processed as usual, a resulting
+&app_handle_request; or &app_handle_answer; callback (if one takes
+place) indicating the <c>5015</c> error (DIAMETER_INVALID_MESSAGE_LENGTH).
+If <c>discard</c> then the message in question is silently discarded.</p>
+
+<p>
+Defaults to <c>exit</c>.</p>
+
+<note>
+<p>
+The default value reflects the fact that a transport module for a
+stream-oriented transport like TCP may not be able to recover from a
+message length error since such a transport must use the Message
+Length header to divide the incoming byte stream into individual
+Diameter messages.
+An invalid length leaves it with no reliable way to rediscover message
+boundaries, which may result in the failure of subsequent messages.
+See &man_tcp; for the behaviour of that module.</p>
+</note>
+</item>
+
<marker id="reconnect_timer"/>
<tag><c>{reconnect_timer, Tc}</c></tag>
<item>
diff --git a/lib/diameter/src/base/diameter.erl b/lib/diameter/src/base/diameter.erl
index 6be544e950..f563d244f6 100644
--- a/lib/diameter/src/base/diameter.erl
+++ b/lib/diameter/src/base/diameter.erl
@@ -332,8 +332,9 @@ call(SvcName, App, Message) ->
| {capabilities_cb, evaluable()}
| {capx_timeout, 'Unsigned32'()}
| {disconnect_cb, evaluable()}
- | {watchdog_timer, 'Unsigned32'() | {module(), atom(), list()}}
+ | {length_errors, exit | handle | discard}
| {reconnect_timer, 'Unsigned32'()}
+ | {watchdog_timer, 'Unsigned32'() | {module(), atom(), list()}}
| {private, any()}.
%% Predicate passed to remove_transport/2
diff --git a/lib/diameter/src/base/diameter_peer_fsm.erl b/lib/diameter/src/base/diameter_peer_fsm.erl
index ad26f230ef..66342f7b62 100644
--- a/lib/diameter/src/base/diameter_peer_fsm.erl
+++ b/lib/diameter/src/base/diameter_peer_fsm.erl
@@ -18,10 +18,10 @@
%%
%%
-%% This module implements (as a process) the RFC 3588 Peer State
+%% This module implements (as a process) the RFC 3588/6733 Peer State
%% Machine modulo the necessity of adapting the peer election to the
-%% fact that we don't know the identity of a peer until we've
-%% received a CER/CEA from it.
+%% fact that we don't know the identity of a peer until we've received
+%% a CER/CEA from it.
%%
-module(diameter_peer_fsm).
@@ -107,8 +107,9 @@
transport :: pid(), %% transport process
dictionary :: module(), %% common dictionary
service :: #diameter_service{},
- dpr = false :: false | {uint32(), uint32()}}).
+ dpr = false :: false | {uint32(), uint32()},
%% | hop by hop and end to end identifiers
+ length_errors :: exit | handle | discard}).
%% There are non-3588 states possible as a consequence of 5.6.1 of the
%% standard and the corresponding problem for incoming CEA's: we don't
@@ -191,15 +192,22 @@ i({Ack, WPid, {M, Ref} = T, Opts, {Mask,
putr(?REF_KEY, Ref),
putr(?SEQUENCE_KEY, Mask),
putr(?RESTRICT_KEY, Nodes),
- {TPid, Addrs} = start_transport(T, Rest, Svc),
+
Tmo = proplists:get_value(capx_timeout, Opts, ?EVENT_TIMEOUT),
?IS_TIMEOUT(Tmo) orelse ?ERROR({invalid, {capx_timeout, Tmo}}),
+ OnLengthErr = proplists:get_value(length_errors, Opts, exit),
+ lists:member(OnLengthErr, [exit, handle, discard])
+ orelse ?ERROR({invalid, {length_errors, OnLengthErr}}),
+
+ {TPid, Addrs} = start_transport(T, Rest, Svc),
+
#state{state = {'Wait-Conn-Ack', Tmo},
parent = WPid,
transport = TPid,
dictionary = Dict0,
mode = M,
- service = svc(Svc, Addrs)}.
+ service = svc(Svc, Addrs),
+ length_errors = OnLengthErr}.
%% The transport returns its local ip addresses so that different
%% transports on the same service can use different local addresses.
%% The local addresses are put into Host-IP-Address avps here when
@@ -512,21 +520,6 @@ encode(Rec, Dict) ->
%% recv/2
-%% RFC 3588 has result code 5015 for an invalid length but if a
-%% transport is detecting message boundaries using the length header
-%% then a length error will likely lead to further errors.
-
-recv(#diameter_packet{header = #diameter_header{length = Len}
- = Hdr,
- bin = Bin},
- S)
- when Len < 20;
- (0 /= Len rem 4 orelse bit_size(Bin) /= 8*Len) ->
- discard(invalid_message_length, recv, [size(Bin),
- bit_size(Bin) rem 8,
- Hdr,
- S]);
-
recv(#diameter_packet{header = #diameter_header{} = Hdr}
= Pkt,
#state{parent = Pid,
@@ -541,29 +534,52 @@ recv(#diameter_packet{header = undefined,
bin = Bin}
= Pkt,
S) ->
- recv(Pkt#diameter_packet{header = diameter_codec:decode_header(Bin)}, S);
+ recv(diameter_codec:decode_header(Bin), Pkt, S);
-recv(Bin, S)
- when is_binary(Bin) ->
- recv(#diameter_packet{bin = Bin}, S);
+recv(Bin, S) ->
+ recv(#diameter_packet{bin = Bin}, S).
-recv(#diameter_packet{header = false} = Pkt, S) ->
- discard(truncated_header, recv, [Pkt, S]).
+%% recv/3
-msg_id({_,_,_} = T, _) ->
- T;
-msg_id(_, Hdr) ->
- diameter_codec:msg_id(Hdr).
+recv(#diameter_header{length = Len}
+ = H,
+ #diameter_packet{bin = Bin}
+ = Pkt,
+ #state{length_errors = E}
+ = S)
+ when E == handle;
+ 0 == Len rem 4, bit_size(Bin) == 8*Len ->
+ recv(Pkt#diameter_packet{header = H}, S);
+
+recv(#diameter_header{}
+ = H,
+ #diameter_packet{bin = Bin},
+ #state{length_errors = E}
+ = S) ->
+ invalid(E,
+ invalid_message_length,
+ recv,
+ [size(Bin), bit_size(Bin) rem 8, H, S]);
-%% Treat invalid length as a transport error and die. Especially in
-%% the TCP case, in which there's no telling where the next message
-%% begins in the incoming byte stream, keeping a crippled connection
-%% alive may just make things worse.
+recv(false, Pkt, #state{length_errors = E} = S) ->
+ invalid(E, truncated_header, recv, [Pkt, S]).
-discard(Reason, F, A) ->
+%% Note that counters here only count discarded messages.
+invalid(E, Reason, F, A) ->
diameter_stats:incr(Reason),
+ abort(E, Reason, F, A).
+
+abort(exit, Reason, F, A) ->
diameter_lib:warning_report(Reason, {?MODULE, F, A}),
- throw({?MODULE, abort, Reason}).
+ throw({?MODULE, abort, Reason});
+
+abort(_, _, _, _) ->
+ ok.
+
+msg_id({_,_,_} = T, _) ->
+ T;
+msg_id(_, Hdr) ->
+ {_,_,_} = diameter_codec:msg_id(Hdr).
%% rcv/3
diff --git a/lib/diameter/src/base/diameter_traffic.erl b/lib/diameter/src/base/diameter_traffic.erl
index 2f486861a2..0de3825943 100644
--- a/lib/diameter/src/base/diameter_traffic.erl
+++ b/lib/diameter/src/base/diameter_traffic.erl
@@ -309,21 +309,35 @@ request_cb(App,
%% examine/1
%%
-%% Look for errors in a decoded message. Length errors result in
-%% decode failure in diameter_codec.
+%% Look for errors in a decoded message. It's odd/unfortunate that
+%% 501[15] aren't protocol errors.
-examine(#diameter_packet{header = #diameter_header{version
- = ?DIAMETER_VERSION}}
- = Pkt) ->
- Pkt;
+%% DIAMETER_INVALID_MESSAGE_LENGTH 5015
+%%
+%% This error is returned when a request is received with an invalid
+%% message length.
+
+examine(#diameter_packet{header = #diameter_header{length = Len},
+ bin = Bin,
+ errors = Es}
+ = Pkt)
+ when Len < 20;
+ 0 /= Len rem 4;
+ 8*Len /= bit_size(Bin) ->
+ Pkt#diameter_packet{errors = [5015 | Es]};
%% DIAMETER_UNSUPPORTED_VERSION 5011
%% This error is returned when a request was received, whose version
%% number is unsupported.
-examine(#diameter_packet{errors = Es} = Pkt) ->
- Pkt#diameter_packet{errors = [5011 | Es]}.
-%% It's odd/unfortunate that this isn't a protocol error.
+examine(#diameter_packet{header = #diameter_header{version = V},
+ errors = Es}
+ = Pkt)
+ when V /= ?DIAMETER_VERSION ->
+ Pkt#diameter_packet{errors = [5011 | Es]};
+
+examine(Pkt) ->
+ Pkt.
%% request_cb/8