diff options
Diffstat (limited to 'lib/diameter/include')
-rw-r--r-- | lib/diameter/include/diameter.hrl | 11 | ||||
-rw-r--r-- | lib/diameter/include/diameter_gen.hrl | 212 |
2 files changed, 189 insertions, 34 deletions
diff --git a/lib/diameter/include/diameter.hrl b/lib/diameter/include/diameter.hrl index 79c4dce541..c2c271a9a3 100644 --- a/lib/diameter/include/diameter.hrl +++ b/lib/diameter/include/diameter.hrl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2010-2013. All Rights Reserved. +%% Copyright Ericsson AB 2010-2014. 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 @@ -58,8 +58,8 @@ -record(diameter_header, {version, %% 8-bit unsigned length, %% 24-bit unsigned - cmd_code, %% 8-bit unsigned - application_id, %% 24-bit unsigned + cmd_code, %% 24-bit unsigned + application_id, %% 32-bit unsigned hop_by_hop_id, %% 32-bit unsigned end_to_end_id, %% 32-bit unsigned is_request, %% boolean() R flag @@ -126,7 +126,7 @@ default, extra = []}). -%% The diameter service and diameter_apps records are only passed +%% The diameter service and diameter_app records are only passed %% through the transport interface when starting a transport process, %% although typically a transport implementation will (and probably %% should) only be interested host_ip_address. @@ -143,6 +143,7 @@ init_state, %% option 'state', initial callback state id, %% 32-bit unsigned application identifier = Dict:id() mutable = false, %% boolean(), do traffic callbacks modify state? - options = [{answer_errors, report}, %% | callback | discard + options = [{answer_errors, discard}, %% | callback | report {request_errors, answer_3xxx}]}). %% | callback | answer + -endif. %% -ifdef(diameter_hrl). diff --git a/lib/diameter/include/diameter_gen.hrl b/lib/diameter/include/diameter_gen.hrl index 55aae3a243..7e91ce375f 100644 --- a/lib/diameter/include/diameter_gen.hrl +++ b/lib/diameter/include/diameter_gen.hrl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2010-2013. All Rights Reserved. +%% Copyright Ericsson AB 2010-2014. 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 @@ -25,6 +25,15 @@ -define(THROW(T), throw({?MODULE, T})). +%% Key to a value in the process dictionary that determines whether or +%% not an unrecognized AVP setting the M-bit should be regarded as an +%% error or not. See is_strict/0. +-define(STRICT_KEY, strict). + +%% Key that says whether or not we should do a best-effort decode +%% within Failed-AVP. +-define(FAILED_KEY, failed). + -type parent_name() :: atom(). %% parent = Message or AVP -type parent_record() :: tuple(). %% -type avp_name() :: atom(). @@ -35,6 +44,18 @@ -type grouped_avp() :: nonempty_improper_list(#diameter_avp{}, [avp()]). -type avp() :: non_grouped_avp() | grouped_avp(). +%% Use a (hopefully) unique key when manipulating the process +%% dictionary. + +putr(K,V) -> + put({?MODULE, K}, V). + +getr(K) -> + get({?MODULE, K}). + +eraser(K) -> + erase({?MODULE, K}). + %% --------------------------------------------------------------------------- %% # encode_avps/2 %% --------------------------------------------------------------------------- @@ -212,46 +233,151 @@ decode(Name, #diameter_avp{code = Code, vendor_id = Vid} = Avp, Acc) -> %% decode/4 +%% AVP is defined in the dictionary ... decode(Name, {AvpName, Type}, Avp, Acc) -> d(Name, Avp#diameter_avp{name = AvpName, type = Type}, Acc); +%% ... or not. decode(Name, 'AVP', Avp, Acc) -> decode_AVP(Name, Avp, Acc). -%% d/3 +%% 6733, 4.4: +%% +%% Receivers of a Grouped AVP that does not have the 'M' (mandatory) +%% bit set and one or more of the encapsulated AVPs within the group +%% has the 'M' (mandatory) bit set MAY simply be ignored if the +%% Grouped AVP itself is unrecognized. The rule applies even if the +%% encapsulated AVP with its 'M' (mandatory) bit set is further +%% encapsulated within other sub-groups, i.e., other Grouped AVPs +%% embedded within the Grouped AVP. +%% +%% The first sentence is slightly mangled, but take it to mean this: +%% +%% An unrecognized AVP of type Grouped that does not set the 'M' bit +%% MAY be ignored even if one of its encapsulated AVPs sets the 'M' +%% bit. +%% +%% The text above is a change from RFC 3588, which instead says this: +%% +%% Further, if any of the AVPs encapsulated within a Grouped AVP has +%% the 'M' (mandatory) bit set, the Grouped AVP itself MUST also +%% include the 'M' bit set. +%% +%% Both of these texts have problems. If the AVP is unknown then its +%% type is unknown since the type isn't sent over the wire, so the +%% 6733 text becomes a non-statement: don't know that the AVP not +%% setting the M-bit is of type Grouped, therefore can't know that its +%% data consists of encapsulated AVPs, therefore can't but ignore that +%% one of these might set the M-bit. It should be no worse if we know +%% the AVP to have type Grouped. +%% +%% Similarly, for the 3588 text: if we receive an AVP that doesn't set +%% the M-bit and don't know that the AVP has type Grouped then we +%% can't realize that its data contains an AVP that sets the M-bit, so +%% can't regard the AVP as erroneous on this account. Again, it should +%% be no worse if the type is known to be Grouped, but in this case +%% the RFC forces us to regard the AVP as erroneous. This is +%% inconsistent, and the 3588 text has never been enforced. +%% +%% So, if an AVP doesn't set the M-bit then we're free to ignore it, +%% regardless of the AVP's type. If we know the type to be Grouped +%% then we must ignore the M-bit on an encapsulated AVP. That means +%% packing such an encapsulated AVP into an 'AVP' field if need be, +%% not regarding the lack of a specific field as an error as is +%% otherwise the case. (The lack of an AVP-specific field being how we +%% defined the RFC's "unrecognized", which is slightly stronger than +%% "not defined".) -%% Don't try to decode the value of a Failed-AVP component since it -%% probably won't. Note that matching on 'Failed-AVP' assumes that -%% this is the RFC AVP, with code 279. Strictly, this doesn't need to -%% be the case, so we're assuming no one defines another Failed-AVP. -d('Failed-AVP' = Name, Avp, Acc) -> - decode_AVP(Name, Avp, Acc); +%% d/3 -%% Or try to decode. -d(Name, Avp, {Avps, Acc}) -> +d(Name, Avp, Acc) -> #diameter_avp{name = AvpName, - data = Data} + data = Data, + type = Type, + is_mandatory = M} = Avp, + %% Use the process dictionary is to keep track of whether or not + %% to ignore an M-bit on an encapsulated AVP. Not ideal, but the + %% alternative requires widespread changes to be able to pass the + %% value around through the entire decode. The solution here is + %% simple in comparison, both to implement and to understand. + + Strict = relax(Type, M), + + %% Use the process dictionary again to keep track of whether we're + %% decoding within Failed-AVP and should ignore decode errors + %% altogether. + + Failed = relax(Name), %% Not AvpName or else a failed Failed-AVP + %% decode is packed into 'AVP'. try avp(decode, Data, AvpName) of V -> + {Avps, T} = Acc, {H, A} = ungroup(V, Avp), - {[H | Avps], pack_avp(Name, A, Acc)} + {[H | Avps], pack_avp(Name, A, T)} catch error: Reason -> - %% Failures here won't be visible since they're a "normal" - %% occurrence if the peer sends a faulty AVP that we need to - %% respond sensibly to. Log the occurence for traceability, - %% but the peer will also receive info in the resulting - %% answer-message. - diameter_lib:log({decode, failure}, - ?MODULE, - ?LINE, - {Reason, Avp, erlang:get_stacktrace()}), - {Rec, Failed} = Acc, - {[Avp|Avps], {Rec, [rc(Reason, Avp) | Failed]}} + d(undefined == Failed orelse is_failed(), Reason, Name, Avp, Acc) + after + reset(?STRICT_KEY, Strict), + reset(?FAILED_KEY, Failed) end. +%% Ignore a decode error within Failed-AVP ... +d(true, _, Name, Avp, Acc) -> + decode_AVP(Name, Avp, Acc); + +%% ... or not. Failures here won't be visible since they're a "normal" +%% occurrence if the peer sends a faulty AVP that we need to respond +%% sensibly to. Log the occurence for traceability, but the peer will +%% also receive info in the resulting answer message. +d(false, Reason, Name, Avp, {Avps, Acc}) -> + Stack = diameter_lib:get_stacktrace(), + diameter_lib:log(decode_error, + ?MODULE, + ?LINE, + {Reason, Name, Avp#diameter_avp.name, Stack}), + {Rec, Failed} = Acc, + {[Avp|Avps], {Rec, [rc(Reason, Avp) | Failed]}}. + +%% Set false in the process dictionary as soon as we see a Grouped AVP +%% that doesn't set the M-bit, so that is_strict() can say whether or +%% not to ignore the M-bit on an encapsulated AVP. +relax('Grouped', M) -> + case getr(?STRICT_KEY) of + undefined when not M -> + putr(?STRICT_KEY, M); + _ -> + false + end; +relax(_, _) -> + false. + +is_strict() -> + false /= getr(?STRICT_KEY). + +%% Set true in the process dictionary as soon as we see Failed-AVP. +%% Matching on 'Failed-AVP' assumes that this is the RFC AVP. +%% Strictly, this doesn't need to be the case. +relax('Failed-AVP') -> + case getr(?FAILED_KEY) of + undefined -> + putr(?FAILED_KEY, true); + true = Yes -> + Yes + end; +relax(_) -> + is_failed(). + +is_failed() -> + true == getr(?FAILED_KEY). + +reset(Key, undefined) -> + eraser(Key); +reset(_, _) -> + ok. + %% decode_AVP/3 %% %% Don't know this AVP: see if it can be packed in an 'AVP' field @@ -310,15 +436,25 @@ pack_avp(_, Arity, Avp, Acc) -> %% pack_AVP/3 -%% Give Failed-AVP special treatment since it'll contain any -%% unrecognized mandatory AVP's. -pack_AVP(Name, #diameter_avp{is_mandatory = true} = Avp, Acc) - when Name /= 'Failed-AVP' -> +%% Length failure was induced because of a header/payload length +%% mismatch. The AVP Length is reset to match the received data if +%% this AVP is encoded in an answer message, since the length is +%% computed. +%% +%% Data is a truncated header if command_code = undefined, otherwise +%% payload bytes. The former is padded to the length of a header if +%% the AVP reaches an outgoing encode in diameter_codec. +%% +%% RFC 6733 says that an AVP returned with 5014 can contain a minimal +%% payload for the AVP's type, but in this case we don't know the +%% type. + +pack_AVP(_, #diameter_avp{data = <<0:1, Data/binary>>} = Avp, Acc) -> {Rec, Failed} = Acc, - {Rec, [{5001, Avp} | Failed]}; + {Rec, [{5014, Avp#diameter_avp{data = Data}} | Failed]}; pack_AVP(Name, #diameter_avp{is_mandatory = M} = Avp, Acc) -> - case avp_arity(Name, 'AVP') of + case pack_arity(Name, M) of 0 -> {Rec, Failed} = Acc, {Rec, [{if M -> 5001; true -> 5008 end, Avp} | Failed]}; @@ -326,6 +462,24 @@ pack_AVP(Name, #diameter_avp{is_mandatory = M} = Avp, Acc) -> pack(Arity, 'AVP', Avp, Acc) end. +%% Give Failed-AVP special treatment since it'll contain any +%% unrecognized mandatory AVP's. +pack_arity(Name, M) -> + NF = Name /= 'Failed-AVP' andalso not is_failed(), + %% Not testing just Name /= 'Failed-AVP' means we're changing the + %% packing of AVPs nested within Failed-AVP, but the point of + %% ignoring errors within Failed-AVP is to decode as much as + %% possible, and failing because a mandatory AVP couldn't be + %% packed into a dedicated field defeats that point. Note that we + %% can't just test not is_failed() since this will be 'true' when + %% packing an unknown AVP directly within Failed-AVP. + case NF andalso M andalso is_strict() of + true -> + 0; + false -> + avp_arity(Name, 'AVP') + end. + %% 3588: %% %% DIAMETER_AVP_UNSUPPORTED 5001 |