%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 2019-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. %% You may obtain a copy of the License at %% %% http://www.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, software %% distributed under the License is distributed on an "AS IS" BASIS, %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %% See the License for the specific language governing permissions and %% limitations under the License. %% %% %CopyrightEnd% %% -module(snmpa_get). -behaviour(snmpa_get_mechanism). %%%----------------------------------------------------------------- %%% snmpa_get_mechanism exports %%%----------------------------------------------------------------- -export([ do_get/3, do_get/4, do_get_next/3, do_get_bulk/7 ]). -include("snmpa_internal.hrl"). -include("snmp_types.hrl"). -include("snmp_debug.hrl"). -include("snmp_verbosity.hrl"). -ifndef(default_verbosity). -define(default_verbosity,silence). -endif. -define(empty_pdu_size, 21). -ifdef(snmp_extended_verbosity). -define(vt(F,A), ?vtrace(F, A)). -else. -define(vt(_F, _A), ok). -endif. -define(AGENT, snmpa_agent). -define(LIB, snmpa_get_lib). %%%----------------------------------------------------------------- %%% 3. GET REQUEST %%% -------------- %%% According to RFC1157, section 4.1.2 and RFC1905, section 4.2.1. %%% In rfc1157:4.1.2 it isn't specified if noSuchName should be %%% returned even if some other varbind generates a genErr. %%% In rfc1905:4.2.1 this is not a problem since exceptions are %%% used, and thus a genErr will be returned anyway. %%%----------------------------------------------------------------- %%----------------------------------------------------------------- %% Func: do_get/2 %% Purpose: Handles all VBs in a request that is inside the %% mibview (local). %% Returns: {noError, 0, ListOfNewVarbinds} | %% {ErrorStatus, ErrorIndex, []} %%----------------------------------------------------------------- do_get(UnsortedVarbinds, IsNotification, _Extra) -> {MyVarbinds, SubagentVarbinds} = ?LIB:agent_sort_vbs(UnsortedVarbinds), case do_get_local(MyVarbinds, IsNotification) of {noError, 0, NewMyVarbinds} -> case do_get_subagents(SubagentVarbinds, IsNotification) of {noError, 0, NewSubagentVarbinds} -> {noError, 0, NewMyVarbinds ++ NewSubagentVarbinds}; {ErrorStatus, ErrorIndex, _} -> {ErrorStatus, ErrorIndex, []} end; {ErrorStatus, ErrorIndex, _} -> {ErrorStatus, ErrorIndex, []} end. %%----------------------------------------------------------------- %% Func: do_get/3 %% Purpose: do_get handles "getRequests". %% Pre: incoming varbinds have type == 'NULL', value == unSpecified %% Returns: {noError, 0, ListOfNewVarbinds} | %% {ErrorStatus, ErrorIndex, []} %%----------------------------------------------------------------- do_get(MibView, UnsortedVarbinds, IsNotification, Extra) -> ?vtrace("do_get -> entry with" "~n MibView: ~p" "~n UnsortedVarbinds: ~p" "~n IsNotification: ~p", [MibView, UnsortedVarbinds, IsNotification]), %% This is me, the master, so go ahead {OutSideView, InSideView} = ?LIB:split_vbs_view(UnsortedVarbinds, MibView), {Error, Index, NewVbs} = do_get(InSideView, IsNotification, Extra), {Error, Index, NewVbs ++ OutSideView}. %%----------------------------------------------------------------- %% Func: do_get_local/2,3 %% Purpose: Loop the variablebindings list. We know that each varbind %% in that list belongs to us. %% Returns: {noError, 0, ListOfNewVarbinds} | %% {ErrorStatus, ErrorIndex, []} %%----------------------------------------------------------------- do_get_local(VBs, IsNotification) -> do_get_local(VBs, [], IsNotification). do_get_local([Vb | Vbs], Res, IsNotification) -> case try_get(Vb, IsNotification) of NewVb when is_record(NewVb, varbind) -> do_get_local(Vbs, [NewVb | Res], IsNotification); ListOfNewVb when is_list(ListOfNewVb) -> do_get_local(Vbs, lists:append(ListOfNewVb, Res), IsNotification); {error, Error, OrgIndex} -> {Error, OrgIndex, []} end; do_get_local([], Res, _IsNotification) -> {noError, 0, Res}. %%----------------------------------------------------------------- %% Func: do_get_subagents/2 %% Purpose: Loop the list of varbinds for different subagents. %% For each of them, call sub_agent_get to retreive %% the values for them. %% Returns: {noError, 0, ListOfNewVarbinds} | %% {ErrorStatus, ErrorIndex, []} %%----------------------------------------------------------------- do_get_subagents(SubagentVarbinds, IsNotification) -> do_get_subagents(SubagentVarbinds, [], IsNotification). do_get_subagents([{SubAgentPid, SAVbs} | Tail], Res, IsNotification) -> {_SAOids, Vbs} = ?LIB:sa_split(SAVbs), case catch ?AGENT:subagent_get(SubAgentPid, Vbs, IsNotification) of {noError, 0, NewVbs} -> do_get_subagents(Tail, lists:append(NewVbs, Res), IsNotification); {ErrorStatus, ErrorIndex, _} -> {ErrorStatus, ErrorIndex, []}; {'EXIT', Reason} -> ?LIB:user_err("Lost contact with subagent (get) ~w. Using genErr", [Reason]), {genErr, 0, []} end; do_get_subagents([], Res, _IsNotification) -> {noError, 0, Res}. %%----------------------------------------------------------------- %% Func: try_get/2 %% Returns: {error, ErrorStatus, OrgIndex} | %% #varbind | %% List of #varbind %%----------------------------------------------------------------- try_get(IVb, IsNotification) when is_record(IVb, ivarbind) -> ?vtrace("try_get(ivarbind) -> entry with" "~n IVb: ~p", [IVb]), get_var_value_from_ivb(IVb, IsNotification); try_get({TableOid, TableVbs}, IsNotification) -> ?vtrace("try_get(table) -> entry with" "~n TableOid: ~p" "~n TableVbs: ~p", [TableOid, TableVbs]), [#ivarbind{mibentry = MibEntry}|_] = TableVbs, {NoAccessVbs, AccessVbs} = check_all_table_vbs(TableVbs, IsNotification, [], []), case get_tab_value_from_mib(MibEntry, TableOid, AccessVbs) of {error, ErrorStatus, OrgIndex} -> {error, ErrorStatus, OrgIndex}; NVbs -> NVbs ++ NoAccessVbs end. %%----------------------------------------------------------------- %% Make sure all requested columns are accessible. %%----------------------------------------------------------------- check_all_table_vbs([IVb| IVbs], IsNotification, NoA, A) -> #ivarbind{mibentry = Me, varbind = Vb} = IVb, case Me#me.access of 'not-accessible' -> NNoA = [Vb#varbind{value = noSuchInstance} | NoA], check_all_table_vbs(IVbs, IsNotification, NNoA, A); 'accessible-for-notify' when IsNotification =:= false -> NNoA = [Vb#varbind{value = noSuchInstance} | NoA], check_all_table_vbs(IVbs, IsNotification, NNoA, A); 'write-only' -> NNoA = [Vb#varbind{value = noSuchInstance} | NoA], check_all_table_vbs(IVbs, IsNotification, NNoA, A); _ -> check_all_table_vbs(IVbs, IsNotification, NoA, [IVb | A]) end; check_all_table_vbs([], _IsNotification, NoA, A) -> {NoA, A}. %%----------------------------------------------------------------- %% Returns: {error, ErrorStatus, OrgIndex} | %% #varbind %%----------------------------------------------------------------- get_var_value_from_ivb(IVb, IsNotification) when IVb#ivarbind.status =:= noError -> ?vtrace("get_var_value_from_ivb(noError) -> entry", []), #ivarbind{mibentry = Me, varbind = Vb} = IVb, #varbind{org_index = OrgIndex, oid = Oid} = Vb, case Me#me.access of 'not-accessible' -> Vb#varbind{value = noSuchInstance}; 'accessible-for-notify' when IsNotification =:= false -> Vb#varbind{value = noSuchInstance}; 'write-only' -> Vb#varbind{value = noSuchInstance}; _ -> case get_var_value_from_mib(Me, Oid) of {value, Type, Value} -> Vb#varbind{variabletype = Type, value = Value}; {error, ErrorStatus} -> {error, ErrorStatus, OrgIndex} end end; get_var_value_from_ivb(#ivarbind{status = Status, varbind = Vb}, _) -> ?vtrace("get_var_value_from_ivb(~p) -> entry", [Status]), Vb#varbind{value = Status}. %%----------------------------------------------------------------- %% Func: get_var_value_from_mib/1 %% Purpose: %% Returns: {error, ErrorStatus} | %% {value, Type, Value} %%----------------------------------------------------------------- %% Pre: Oid is a correct instance Oid (lookup checked that). %% Returns: A correct return value (see ?AGENT:make_value_a_correct_value) get_var_value_from_mib(#me{entrytype = variable, asn1_type = ASN1Type, mfa = {Mod, Func, Args}}, _Oid) -> ?vtrace("get_var_value_from_mib(variable) -> entry when" "~n Mod: ~p" "~n Func: ~p" "~n Args: ~p", [Mod, Func, Args]), Result = (catch ?LIB:dbg_apply(Mod, Func, [get | Args])), %% mib shall return {value, } | %% {noValue, noSuchName} (v1) | %% {noValue, noSuchObject | noSuchInstance} (v2, v1) %% everything else (including 'genErr') will generate 'genErr'. ?AGENT:make_value_a_correct_value(Result, ASN1Type, {Mod, Func, Args}); get_var_value_from_mib(#me{entrytype = table_column, oid = MeOid, asn1_type = ASN1Type, mfa = {Mod, Func, Args}}, Oid) -> ?vtrace("get_var_value_from_mib(table_column) -> entry when" "~n MeOid: ~p" "~n Mod: ~p" "~n Func: ~p" "~n Args: ~p" "~n Oid: ~p", [MeOid, Mod, Func, Args, Oid]), Col = lists:last(MeOid), Indexes = snmp_misc:diff(Oid, MeOid), [Result] = (catch ?LIB:dbg_apply(Mod, Func, [get, Indexes, [Col] | Args])), ?AGENT:make_value_a_correct_value(Result, ASN1Type, {Mod, Func, Args, Indexes, Col}). %% For table operations we need to pass RestOid down to the table-function. %% Its up to the table-function to check for noSuchInstance (ex: a %% non-existing row). %% Returns: {error, ErrorStatus, OrgIndex} | %% {value, Type, Value} get_tab_value_from_mib(#me{mfa = {Mod, Func, Args}}, TableOid, TableVbs) -> ?vtrace("get_tab_value_from_mib -> entry when" "~n Mod: ~p" "~n Func: ~p" "~n Args: ~p", [Mod, Func, Args]), TableOpsWithShortOids = ?LIB:delete_prefixes(TableOid, TableVbs), SortedVBsRows = snmpa_svbl:sort_varbinds_rows(TableOpsWithShortOids), case get_value_all_rows(SortedVBsRows, Mod, Func, Args, []) of {Error, Index} -> #ivarbind{varbind = Vb} = lists:nth(Index, TableVbs), {error, Error, Vb#varbind.org_index}; ListOfValues -> merge_varbinds_and_value(TableVbs, ListOfValues) end. %%----------------------------------------------------------------- %% Values is a scrambled list of {CorrectValue, Index}, where Index %% is index into the #ivarbind list. So for each Value, we must %% find the corresponding #ivarbind, and merge them into a new %% #varbind. %% The Values list comes from validate_tab_res. %%----------------------------------------------------------------- merge_varbinds_and_value(IVbs, [{{value, Type, Value}, Index} | Values]) -> #ivarbind{varbind = Vb} = lists:nth(Index, IVbs), [Vb#varbind{variabletype = Type, value = Value} | merge_varbinds_and_value(IVbs, Values)]; merge_varbinds_and_value(_, []) -> []. get_value_all_rows([{[], OrgCols} | Rows], Mod, Func, Args, Res) -> ?vtrace("get_value_all_rows -> entry when" "~n OrgCols: ~p", [OrgCols]), Cols = [{{value, noValue, noSuchInstance}, Index} || {_Col, _ASN1Type, Index} <- OrgCols], NewRes = lists:append(Cols, Res), get_value_all_rows(Rows, Mod, Func, Args, NewRes); get_value_all_rows([{RowIndex, OrgCols} | Rows], Mod, Func, Args, Res) -> ?vtrace("get_value_all_rows -> entry when" "~n RowIndex: ~p" "~n OrgCols: ~p", [RowIndex, OrgCols]), {DOrgCols, Dup} = remove_duplicates(OrgCols), Cols = delete_index(DOrgCols), Result = (catch ?LIB:dbg_apply(Mod, Func, [get, RowIndex, Cols | Args])), case validate_tab_res(Result, DOrgCols, {Mod, Func, Args}) of Values when is_list(Values) -> NVals = restore_duplicates(Dup, Values), NewRes = lists:append(NVals, Res), get_value_all_rows(Rows, Mod, Func, Args, NewRes); {error, ErrorStatus, Index} -> ?AGENT:validate_err(row_set, {ErrorStatus, Index}, {Mod, Func, Args}) end; get_value_all_rows([], _Mod, _Func, _Args, Res) -> ?vtrace("get_value_all_rows -> entry when done" "~n Res: ~p", [Res]), Res. %%----------------------------------------------------------------- %% Args: {RowIndex, list of {ShortOid, ASN1Type}} %% Returns: list of Col %%----------------------------------------------------------------- delete_index([{Col, _Val, _OrgIndex} | T]) -> [Col | delete_index(T)]; delete_index([]) -> []. %%----------------------------------------------------------------- %% This function is called before 'get' on a table, and removes %% any duplicate columns. It returns {Cols, DupInfo}. The Cols %% are the unique columns. The instrumentation function is %% called to get the values. These values, together with the %% DupInfo, is later passed to restore_duplicates, which uses %% the retrieved values to reconstruct the original column list, %% but with the retrieved value for each column. %%----------------------------------------------------------------- remove_duplicates(Cols) -> remove_duplicates(Cols, [], []). remove_duplicates([{Col, V1, OrgIdx1}, {Col, V2, OrgIdx2} | T], NCols, Dup) -> remove_duplicates([{Col, V1, OrgIdx1} | T], NCols, [{Col, V2, OrgIdx2} | Dup]); remove_duplicates([Col | T], NCols, Dup) -> remove_duplicates(T, [Col | NCols], Dup); remove_duplicates([], NCols, Dup) -> {lists:reverse(NCols), lists:reverse(Dup)}. restore_duplicates([], Cols) -> [{Val, OrgIndex} || {_Col, Val, OrgIndex} <- Cols]; restore_duplicates([{Col, _Val2, OrgIndex2} | Dup], [{Col, NVal, OrgIndex1} | Cols]) -> [{NVal, OrgIndex2} | restore_duplicates(Dup, [{Col, NVal, OrgIndex1} | Cols])]; restore_duplicates(Dup, [{_Col, Val, OrgIndex} | T]) -> [{Val, OrgIndex} | restore_duplicates(Dup, T)]. %%----------------------------------------------------------------- %% Three cases: %% 1) All values ok %% 2) table_func returned {Error, ...} %% 3) Some value in Values list is erroneous. %% Args: Value is a list of values from table_func(get..) %% OrgCols is a list with {Col, ASN1Type, OrgIndex} %% each element in Values and OrgCols correspond to each %% other. %%----------------------------------------------------------------- validate_tab_res(Values, OrgCols, Mfa) when is_list(Values) -> {_Col, _ASN1Type, OneIdx} = hd(OrgCols), validate_tab_res(Values, OrgCols, Mfa, [], OneIdx); validate_tab_res({noValue, Error}, OrgCols, Mfa) -> Values = lists:duplicate(length(OrgCols), {noValue, Error}), validate_tab_res(Values, OrgCols, Mfa); validate_tab_res({genErr, Col}, OrgCols, Mfa) -> case lists:keysearch(Col, 1, OrgCols) of {value, {_Col, _ASN1Type, Index}} -> {error, genErr, Index}; _ -> ?LIB:user_err("Invalid column in {genErr, ~w} from ~w (get)", [Col, Mfa]), [{_Col, _ASN1Type, Index} | _] = OrgCols, {error, genErr, Index} end; validate_tab_res(genErr, [{_Col, __ASN1Type, Index} | _OrgCols], _Mfa) -> {error, genErr, Index}; validate_tab_res(Error, [{_Col, _ASN1Type, Index} | _OrgCols], Mfa) -> ?LIB:user_err("Invalid return value ~w from ~w (get)",[Error, Mfa]), {error, genErr, Index}. validate_tab_res([Value | Values], [{Col, ASN1Type, Index} | OrgCols], Mfa, Res, I) -> %% This one makes it possible to return a list of genErr, which %% is not allowed according to the manual. But that's ok, as %% everything else will generate a genErr! (the only problem is %% that it won't generate a user_error). case ?AGENT:make_value_a_correct_value(Value, ASN1Type, Mfa) of {error, ErrorStatus} -> {error, ErrorStatus, Index}; CorrectValue -> NewRes = [{Col, CorrectValue, Index} | Res], validate_tab_res(Values, OrgCols, Mfa, NewRes, I) end; validate_tab_res([], [], _Mfa, Res, _I) -> lists:reverse(Res); validate_tab_res([], [{_Col, _ASN1Type, Index}|_], Mfa, _Res, _I) -> ?LIB:user_err("Too few values returned from ~w (get)", [Mfa]), {error, genErr, Index}; validate_tab_res(_TooMany, [], Mfa, _Res, I) -> ?LIB:user_err("Too many values returned from ~w (get)", [Mfa]), {error, genErr, I}. %%%----------------------------------------------------------------- %%% 4. GET-NEXT REQUEST %%% -------------- %%% According to RFC1157, section 4.1.3 and RFC1905, section 4.2.2. %%%----------------------------------------------------------------- %%----------------------------------------------------------------- %% Func: do_get_next/3 %% Purpose: do_get_next handles "getNextRequests". %% Note: Even if it is SNMPv1, a varbind's value can be %% endOfMibView. This is converted to noSuchName in process_pdu. %% Returns: {noError, 0, ListOfNewVarbinds} | %% {ErrorStatus, ErrorIndex, []} %% Note2: ListOfNewVarbinds is not sorted in any order!!! %% Alg: First, the variables are sorted in OID order. %% %% Second, next in the MIB is performed for each OID, and %% the result is collected as: if next oid is a variable, %% perform a get to retrieve its value; if next oid is in a %% table, save this value and continue until we get an oid %% outside this table. Then perform get_next on the table, %% and continue with all endOfTables and the oid outside the %% table; if next oid is an subagent, save this value and %% continue as in the table case. %% %% Third, each response is checked for endOfMibView, or (for %% subagents) that the Oid returned has the correct prefix. %% (This is necessary since an SA can be registered under many %% separated subtrees, and if the last variable in the first %% subtree is requested in a next, the SA will return the first %% variable in the second subtree. This might be working, since %% there may be a variable in between these subtrees.) For each %% of these, a new get-next is performed, one at a time. %% This alg. might be optimised in several ways. The most %% striking one is that the same SA might be called several %% times, when one time should be enough. But it isn't clear %% that this really matters, since many nexts across the same %% subagent must be considered to be very rare. %%----------------------------------------------------------------- do_get_next(MibView, UnsortedVBs, _Extra) -> do_get_next2(MibView, UnsortedVBs, infinity). %% The third argument is only used if we are called as result %% of a get-bulk request. do_get_next2(_MibView, UnsortedVarbinds, GbMaxVBs) when (is_integer(GbMaxVBs) andalso (length(UnsortedVarbinds) > GbMaxVBs)) -> {tooBig, 0, []}; % What is the correct index in this case? do_get_next2(MibView, UnsortedVBs, GbMaxVBs) -> ?vt("do_get_next2 -> entry when" "~n MibView: ~p" "~n UnsortedVBs: ~p", [MibView, UnsortedVBs]), SortedVBs = ?LIB:oid_sort_vbs(UnsortedVBs), ?vt("do_get_next -> " "~n SortedVBs: ~p", [SortedVBs]), next_loop_varbinds([], SortedVBs, MibView, [], [], GbMaxVBs). next_loop_varbinds(_, Vbs, _MibView, Res, _LAVb, GbMaxVBs) when (is_integer(GbMaxVBs) andalso ((length(Vbs) + length(Res)) > GbMaxVBs)) -> {tooBig, 0, []}; % What is the correct index in this case? %% LAVb is Last Accessible Vb next_loop_varbinds([], [Vb | Vbs], MibView, Res, LAVb, GbMaxVBs) -> ?vt("next_loop_varbinds -> entry when" "~n Vb: ~p" "~n MibView: ~p", [Vb, MibView]), case varbind_next(Vb, MibView) of endOfMibView -> ?vt("next_loop_varbind -> endOfMibView", []), RVb = if LAVb =:= [] -> Vb; true -> LAVb end, NewVb = RVb#varbind{variabletype = 'NULL', value = endOfMibView}, next_loop_varbinds([], Vbs, MibView, [NewVb | Res], [], GbMaxVBs); {variable, ME, VarOid} when ((ME#me.access =/= 'not-accessible') andalso (ME#me.access =/= 'write-only') andalso (ME#me.access =/= 'accessible-for-notify')) -> ?vt("next_loop_varbind -> variable: " "~n ME: ~p" "~n VarOid: ~p", [ME, VarOid]), case try_get_instance(Vb, ME) of {value, noValue, _NoSuchSomething} -> ?vt("next_loop_varbind -> noValue", []), %% Try next one NewVb = Vb#varbind{oid = VarOid, value = 'NULL'}, next_loop_varbinds([], [NewVb | Vbs], MibView, Res, [], GbMaxVBs); {value, Type, Value} -> ?vt("next_loop_varbind -> value" "~n Type: ~p" "~n Value: ~p", [Type, Value]), NewVb = Vb#varbind{oid = VarOid, variabletype = Type, value = Value}, next_loop_varbinds([], Vbs, MibView, [NewVb | Res], [], GbMaxVBs); {error, ErrorStatus} -> ?vdebug("next loop varbinds:" "~n ErrorStatus: ~p",[ErrorStatus]), {ErrorStatus, Vb#varbind.org_index, []} end; {variable, _ME, VarOid} -> ?vt("next_loop_varbind -> variable: " "~n VarOid: ~p", [VarOid]), RVb = if LAVb =:= [] -> Vb; true -> LAVb end, NewVb = Vb#varbind{oid = VarOid, value = 'NULL'}, next_loop_varbinds([], [NewVb | Vbs], MibView, Res, RVb, GbMaxVBs); {table, TableOid, TableRestOid, ME} -> ?vt("next_loop_varbind -> table: " "~n TableOid: ~p" "~n TableRestOid: ~p" "~n ME: ~p", [TableOid, TableRestOid, ME]), next_loop_varbinds({table, TableOid, ME, [{tab_oid(TableRestOid), Vb}]}, Vbs, MibView, Res, [], GbMaxVBs); {subagent, SubAgentPid, SAOid} -> ?vt("next_loop_varbind -> subagent: " "~n SubAgentPid: ~p" "~n SAOid: ~p", [SubAgentPid, SAOid]), NewVb = Vb#varbind{variabletype = 'NULL', value = 'NULL'}, next_loop_varbinds({subagent, SubAgentPid, SAOid, [NewVb]}, Vbs, MibView, Res, [], GbMaxVBs) end; next_loop_varbinds({table, TableOid, ME, TabOids}, [Vb | Vbs], MibView, Res, _LAVb, GbMaxVBs) -> ?vt("next_loop_varbinds(table) -> entry with" "~n TableOid: ~p" "~n Vb: ~p", [TableOid, Vb]), case varbind_next(Vb, MibView) of {table, TableOid, TableRestOid, _ME} -> next_loop_varbinds({table, TableOid, ME, [{tab_oid(TableRestOid), Vb} | TabOids]}, Vbs, MibView, Res, [], GbMaxVBs); _ -> case get_next_table(ME, TableOid, TabOids, MibView) of {ok, TabRes, TabEndOfTabVbs} -> NewVbs = lists:append(TabEndOfTabVbs, [Vb | Vbs]), NewRes = lists:append(TabRes, Res), next_loop_varbinds([], NewVbs, MibView, NewRes, [], GbMaxVBs); {ErrorStatus, OrgIndex} -> ?vdebug("next loop varbinds: next varbind" "~n ErrorStatus: ~p" "~n OrgIndex: ~p", [ErrorStatus,OrgIndex]), {ErrorStatus, OrgIndex, []} end end; next_loop_varbinds({table, TableOid, ME, TabOids}, [], MibView, Res, _LAVb, GbMaxVBs) -> ?vt("next_loop_varbinds(table) -> entry with" "~n TableOid: ~p", [TableOid]), case get_next_table(ME, TableOid, TabOids, MibView) of {ok, TabRes, TabEndOfTabVbs} -> ?vt("next_loop_varbinds(table) -> get_next_table result:" "~n TabRes: ~p" "~n TabEndOfTabVbs: ~p", [TabRes, TabEndOfTabVbs]), NewRes = lists:append(TabRes, Res), next_loop_varbinds([], TabEndOfTabVbs, MibView, NewRes, [], GbMaxVBs); {ErrorStatus, OrgIndex} -> ?vdebug("next loop varbinds: next table" "~n ErrorStatus: ~p" "~n OrgIndex: ~p", [ErrorStatus,OrgIndex]), {ErrorStatus, OrgIndex, []} end; next_loop_varbinds({subagent, SAPid, SAOid, SAVbs}, [Vb | Vbs], MibView, Res, _LAVb, GbMaxVBs) -> ?vt("next_loop_varbinds(subagent) -> entry with" "~n SAPid: ~p" "~n SAOid: ~p" "~n Vb: ~p", [SAPid, SAOid, Vb]), case varbind_next(Vb, MibView) of {subagent, _SubAgentPid, SAOid} -> next_loop_varbinds({subagent, SAPid, SAOid, [Vb | SAVbs]}, Vbs, MibView, Res, [], GbMaxVBs); _ -> case get_next_sa(SAPid, SAOid, SAVbs, MibView) of {ok, SARes, SAEndOfMibViewVbs} -> NewVbs = lists:append(SAEndOfMibViewVbs, [Vb | Vbs]), NewRes = lists:append(SARes, Res), next_loop_varbinds([], NewVbs, MibView, NewRes, [], GbMaxVBs); {noSuchName, OrgIndex} -> %% v1 reply, treat this Vb as endOfMibView, and try again %% for the others. case lists:keysearch(OrgIndex, #varbind.org_index, SAVbs) of {value, EVb} -> NextOid = next_oid(SAOid), EndOfVb = EVb#varbind{oid = NextOid, value = {endOfMibView, NextOid}}, case lists:delete(EVb, SAVbs) of [] -> next_loop_varbinds([], [EndOfVb, Vb | Vbs], MibView, Res, [], GbMaxVBs); TryAgainVbs -> next_loop_varbinds({subagent, SAPid, SAOid, TryAgainVbs}, [EndOfVb, Vb | Vbs], MibView, Res, [], GbMaxVBs) end; false -> %% bad index from subagent {genErr, (hd(SAVbs))#varbind.org_index, []} end; {ErrorStatus, OrgIndex} -> ?vdebug("next loop varbinds: next subagent" "~n Vb: ~p" "~n ErrorStatus: ~p" "~n OrgIndex: ~p", [Vb,ErrorStatus,OrgIndex]), {ErrorStatus, OrgIndex, []} end end; next_loop_varbinds({subagent, SAPid, SAOid, SAVbs}, [], MibView, Res, _LAVb, GbMaxVBs) -> ?vt("next_loop_varbinds(subagent) -> entry with" "~n SAPid: ~p" "~n SAOid: ~p", [SAPid, SAOid]), case get_next_sa(SAPid, SAOid, SAVbs, MibView) of {ok, SARes, SAEndOfMibViewVbs} -> NewRes = lists:append(SARes, Res), next_loop_varbinds([], SAEndOfMibViewVbs, MibView, NewRes, [], GbMaxVBs); {noSuchName, OrgIndex} -> %% v1 reply, treat this Vb as endOfMibView, and try again for %% the others. case lists:keysearch(OrgIndex, #varbind.org_index, SAVbs) of {value, EVb} -> NextOid = next_oid(SAOid), EndOfVb = EVb#varbind{oid = NextOid, value = {endOfMibView, NextOid}}, case lists:delete(EVb, SAVbs) of [] -> next_loop_varbinds([], [EndOfVb], MibView, Res, [], GbMaxVBs); TryAgainVbs -> next_loop_varbinds({subagent, SAPid, SAOid, TryAgainVbs}, [EndOfVb], MibView, Res, [], GbMaxVBs) end; false -> %% bad index from subagent {genErr, (hd(SAVbs))#varbind.org_index, []} end; {ErrorStatus, OrgIndex} -> ?vdebug("next loop varbinds: next subagent" "~n ErrorStatus: ~p" "~n OrgIndex: ~p", [ErrorStatus,OrgIndex]), {ErrorStatus, OrgIndex, []} end; next_loop_varbinds([], [], _MibView, Res, _LAVb, _GbMaxVBs) -> ?vt("next_loop_varbinds -> entry when done", []), {noError, 0, Res}. try_get_instance(_Vb, #me{mfa = {M, F, A}, asn1_type = ASN1Type}) -> ?vtrace("try_get_instance -> entry with" "~n M: ~p" "~n F: ~p" "~n A: ~p", [M,F,A]), Result = (catch ?LIB:dbg_apply(M, F, [get | A])), % mib shall return {value, } | % {noValue, noSuchName} (v1) | % {noValue, noSuchObject | noSuchInstance} (v2, v1) % everything else (including 'genErr') will generate 'genErr'. ?AGENT:make_value_a_correct_value(Result, ASN1Type, {M, F, A}). tab_oid([]) -> [0]; tab_oid(X) -> X. %%----------------------------------------------------------------- %% Perform a next, using the varbinds Oid if value is simple %% value. If value is {endOf, NextOid}, use NextOid. %% This case happens when a table has returned endOfTable, or %% a subagent has returned endOfMibView. %%----------------------------------------------------------------- varbind_next(#varbind{value = Value, oid = Oid}, MibView) -> ?vt("varbind_next -> entry with" "~n Value: ~p" "~n Oid: ~p" "~n MibView: ~p", [Value, Oid, MibView]), case Value of {endOfTable, NextOid} -> snmpa_mib:next(get(mibserver), NextOid, MibView); {endOfMibView, NextOid} -> snmpa_mib:next(get(mibserver), NextOid, MibView); _ -> snmpa_mib:next(get(mibserver), Oid, MibView) end. get_next_table(#me{mfa = {M, F, A}}, TableOid, TableOids, MibView) -> % We know that all TableOids have at least a column number as oid ?vt("get_next_table -> entry with" "~n M: ~p" "~n F: ~p" "~n A: ~p" "~n TableOid: ~p" "~n TableOids: ~p" "~n MibView: ~p", [M, F, A, TableOid, TableOids, MibView]), Sorted = snmpa_svbl:sort_varbinds_rows(TableOids), case get_next_values_all_rows(Sorted, M,F,A, [], TableOid) of NewVbs when is_list(NewVbs) -> ?vt("get_next_table -> " "~n NewVbs: ~p", [NewVbs]), % We must now check each Vb for endOfTable and that it is % in the MibView. If not, it becomes a endOfTable. We % collect all of these together. transform_tab_next_result(NewVbs, {[], []}, MibView); {ErrorStatus, OrgIndex} -> {ErrorStatus, OrgIndex} end. get_next_values_all_rows([Row | Rows], M, F, A, Res, TabOid) -> {RowIndex, TableOids} = Row, Cols = delete_index(TableOids), ?vt("get_next_values_all_rows -> " "~n Cols: ~p", [Cols]), Result = (catch ?LIB:dbg_apply(M, F, [get_next, RowIndex, Cols | A])), ?vt("get_next_values_all_rows -> " "~n Result: ~p", [Result]), case validate_tab_next_res(Result, TableOids, {M, F, A}, TabOid) of Values when is_list(Values) -> ?vt("get_next_values_all_rows -> " "~n Values: ~p", [Values]), NewRes = lists:append(Values, Res), get_next_values_all_rows(Rows, M, F, A, NewRes, TabOid); {ErrorStatus, OrgIndex} -> {ErrorStatus, OrgIndex} end; get_next_values_all_rows([], _M, _F, _A, Res, _TabOid) -> Res. transform_tab_next_result([Vb | Vbs], {Res, EndOfs}, MibView) -> case Vb#varbind.value of {endOfTable, _} -> {ResVBs, EndOfVBs} = ?LIB:split_vbs(Vbs, Res, [Vb | EndOfs]), {ok, ResVBs, EndOfVBs}; _ -> case snmpa_acm:validate_mib_view(Vb#varbind.oid, MibView) of true -> transform_tab_next_result(Vbs, {[Vb|Res], EndOfs},MibView); _ -> Oid = Vb#varbind.oid, NewEndOf = Vb#varbind{value = {endOfTable, Oid}}, transform_tab_next_result(Vbs, {Res, [NewEndOf | EndOfs]}, MibView) end end; transform_tab_next_result([], {Res, EndOfs}, _MibView) -> ?vt("transform_tab_next_result -> entry with: " "~n Res: ~p" "~n EndIfs: ~p",[Res, EndOfs]), {ok, Res, EndOfs}. %%----------------------------------------------------------------- %% Three cases: %% 1) All values ok %% 2) table_func returned {Error, ...} %% 3) Some value in Values list is erroneous. %% Args: Value is a list of values from table_func(get_next, ...) %% TableOids is a list of {TabRestOid, OrgVb} %% each element in Values and TableOids correspond to each %% other. %% Returns: List of NewVarbinds | %% {ErrorStatus, OrgIndex} %% (In the NewVarbinds list, the value may be endOfTable) %%----------------------------------------------------------------- validate_tab_next_res(Values, TableOids, Mfa, TabOid) -> ?vt("validate_tab_next_res -> entry with: " "~n Values: ~p" "~n TableOids: ~p" "~n Mfa: ~p" "~n TabOid: ~p", [Values, TableOids, Mfa, TabOid]), {_Col, _ASN1Type, OneIdx} = hd(TableOids), validate_tab_next_res(Values, TableOids, Mfa, [], TabOid, next_oid(TabOid), OneIdx). validate_tab_next_res([{NextOid, Value} | Values], [{_ColNo, OrgVb, _Index} | TableOids], Mfa, Res, TabOid, TabNextOid, I) -> ?vt("validate_tab_next_res -> entry with: " "~n NextOid: ~p" "~n Value: ~p" "~n Values: ~p" "~n TableOids: ~p" "~n Mfa: ~p" "~n TabOid: ~p", [NextOid, Value, Values, TableOids, Mfa, TabOid]), #varbind{org_index = OrgIndex} = OrgVb, ?vt("validate_tab_next_res -> OrgIndex: ~p", [OrgIndex]), NextCompleteOid = lists:append(TabOid, NextOid), case snmpa_mib:lookup(get(mibserver), NextCompleteOid) of {table_column, #me{asn1_type = ASN1Type}, _TableEntryOid} -> ?vt("validate_tab_next_res -> ASN1Type: ~p", [ASN1Type]), case ?AGENT:make_value_a_correct_value({value, Value}, ASN1Type, Mfa) of {error, ErrorStatus} -> ?vt("validate_tab_next_res -> " "~n ErrorStatus: ~p", [ErrorStatus]), {ErrorStatus, OrgIndex}; {value, Type, NValue} -> ?vt("validate_tab_next_res -> " "~n Type: ~p" "~n NValue: ~p", [Type, NValue]), NewVb = OrgVb#varbind{oid = NextCompleteOid, variabletype = Type, value = NValue}, validate_tab_next_res(Values, TableOids, Mfa, [NewVb | Res], TabOid, TabNextOid, I) end; Error -> ?LIB:user_err("Invalid oid ~w from ~w (get_next). Using genErr => ~p", [NextOid, Mfa, Error]), {genErr, OrgIndex} end; validate_tab_next_res([endOfTable | Values], [{_ColNo, OrgVb, _Index} | TableOids], Mfa, Res, TabOid, TabNextOid, I) -> ?vt("validate_tab_next_res(endOfTable) -> entry with: " "~n Values: ~p" "~n OrgVb: ~p" "~n TableOids: ~p" "~n Mfa: ~p" "~n Res: ~p" "~n TabOid: ~p" "~n TabNextOid: ~p" "~n I: ~p", [Values, OrgVb, TableOids, Mfa, Res, TabOid, TabNextOid, I]), NewVb = OrgVb#varbind{value = {endOfTable, TabNextOid}}, validate_tab_next_res(Values, TableOids, Mfa, [NewVb | Res], TabOid, TabNextOid, I); validate_tab_next_res([], [], _Mfa, Res, _TabOid, _TabNextOid, _I) -> Res; validate_tab_next_res([], [{_Col, _OrgVb, Index}|_], Mfa, _Res, _, _, _I) -> ?LIB:user_err("Too few values returned from ~w (get_next)", [Mfa]), {genErr, Index}; validate_tab_next_res({genErr, ColNumber}, OrgCols, Mfa, _Res, _TabOid, _TabNextOid, _I) -> OrgIndex = snmpa_svbl:col_to_orgindex(ColNumber, OrgCols), ?AGENT:validate_err(table_next, {genErr, OrgIndex}, Mfa); validate_tab_next_res({error, Reason}, [{_ColNo, OrgVb, _Index} | _TableOids], Mfa, _Res, _TabOid, _TabNextOid, _I) -> #varbind{org_index = OrgIndex} = OrgVb, ?LIB:user_err("Erroneous return value ~w from ~w (get_next)", [Reason, Mfa]), {genErr, OrgIndex}; validate_tab_next_res(Error, [{_ColNo, OrgVb, _Index} | _TableOids], Mfa, _Res, _TabOid, _TabNextOid, _I) -> #varbind{org_index = OrgIndex} = OrgVb, ?LIB:user_err("Invalid return value ~w from ~w (get_next)", [Error, Mfa]), {genErr, OrgIndex}; validate_tab_next_res(TooMany, [], Mfa, _Res, _, _, I) -> ?LIB:user_err("Too many values ~w returned from ~w (get_next)", [TooMany, Mfa]), {genErr, I}. %%----------------------------------------------------------------- %% Func: get_next_sa/4 %% Purpose: Loop the list of varbinds for the subagent. %% Call subagent_get_next to retreive %% the next varbinds. %% Returns: {ok, ListOfNewVbs, ListOfEndOfMibViewsVbs} | %% {ErrorStatus, ErrorIndex} %%----------------------------------------------------------------- get_next_sa(SAPid, SAOid, SAVbs, MibView) -> case catch ?AGENT:subagent_get_next(SAPid, MibView, SAVbs) of {noError, 0, NewVbs} -> NewerVbs = transform_sa_next_result(NewVbs,SAOid,next_oid(SAOid)), {ResVBs, EndOfVBs} = ?LIB:split_vbs(NewerVbs), {ok, ResVBs, EndOfVBs}; {ErrorStatus, ErrorIndex, _} -> {ErrorStatus, ErrorIndex}; {'EXIT', Reason} -> ?LIB:user_err("Lost contact with subagent (next) ~w. Using genErr", [Reason]), {genErr, 0} end. %%----------------------------------------------------------------- %% Check for wrong prefix returned or endOfMibView, and convert %% into {endOfMibView, SANextOid}. %%----------------------------------------------------------------- transform_sa_next_result([Vb | Vbs], SAOid, SANextOid) when Vb#varbind.value =:= endOfMibView -> [Vb#varbind{value = {endOfMibView, SANextOid}} | transform_sa_next_result(Vbs, SAOid, SANextOid)]; transform_sa_next_result([Vb | Vbs], SAOid, SANextOid) -> case lists:prefix(SAOid, Vb#varbind.oid) of true -> [Vb | transform_sa_next_result(Vbs, SAOid, SANextOid)]; _ -> [Vb#varbind{oid = SANextOid, value = {endOfMibView, SANextOid}} | transform_sa_next_result(Vbs, SAOid, SANextOid)] end; transform_sa_next_result([], _SAOid, _SANextOid) -> []. next_oid(Oid) -> case lists:reverse(Oid) of [H | T] -> lists:reverse([H+1 | T]); [] -> [] end. %%%----------------------------------------------------------------- %%% 5. GET-BULK REQUEST %%% %%% In order to prevent excesses in reply sizes there are two %%% preventive methods in place. One is to check that the encode %%% size does not exceed Max PDU size (this is mentioned in the %%% standard). The other is a simple VBs limit. That is, the %%% resulting response cannot contain more then this number of VBs. %%%----------------------------------------------------------------- do_get_bulk(MibView, NonRepeaters, MaxRepetitions, PduMS, Varbinds, GbMaxVBs, _Extra) -> ?vtrace("do_get_bulk -> entry with" "~n MibView: ~p" "~n NonRepeaters: ~p" "~n MaxRepetitions: ~p" "~n PduMS: ~p" "~n Varbinds: ~p" "~n GbMaxVBs: ~p", [MibView, NonRepeaters, MaxRepetitions, PduMS, Varbinds, GbMaxVBs]), {NonRepVbs, RestVbs} = ?LIB:split_vbs_gb(NonRepeaters, Varbinds), ?vt("do_get_bulk -> split: " "~n NonRepVbs: ~p" "~n RestVbs: ~p", [NonRepVbs, RestVbs]), case do_get_next2(MibView, NonRepVbs, GbMaxVBs) of {noError, 0, UResNonRepVbs} -> ?vt("do_get_bulk -> next noError: " "~n UResNonRepVbs: ~p", [UResNonRepVbs]), ResNonRepVbs = lists:keysort(#varbind.org_index, UResNonRepVbs), %% Decode the first varbinds, produce a reversed list of %% listOfBytes. case (catch enc_vbs(PduMS - ?empty_pdu_size, ResNonRepVbs)) of {error, Idx, Reason} -> ?LIB:user_err("failed encoding varbind ~w:~n~p", [Idx, Reason]), {genErr, Idx, []}; {SizeLeft, Res} when is_integer(SizeLeft) and is_list(Res) -> ?vtrace("do_get_bulk -> encoded: " "~n SizeLeft: ~p" "~n Res: ~w", [SizeLeft, Res]), case (catch do_get_rep(SizeLeft, MibView, MaxRepetitions, RestVbs, Res, length(UResNonRepVbs), GbMaxVBs)) of {error, Idx, Reason} -> ?LIB:user_err("failed encoding varbind ~w:~n~p", [Idx, Reason]), {genErr, Idx, []}; Res when is_list(Res) -> ?vtrace("do get bulk -> Res: " "~n ~w", [Res]), {noError, 0, conv_res(Res)}; {noError, 0, Data} = OK -> ?vtrace("do get bulk -> OK: " "~n length(Data): ~w", [length(Data)]), OK; Else -> ?vtrace("do get bulk -> Else: " "~n ~w", [Else]), Else end; Res when is_list(Res) -> {noError, 0, conv_res(Res)} end; {ErrorStatus, Index, _} -> ?vdebug("do get bulk: " "~n ErrorStatus: ~p" "~n Index: ~p",[ErrorStatus, Index]), {ErrorStatus, Index, []} end. enc_vbs(SizeLeft, Vbs) -> ?vt("enc_vbs -> entry with" "~n SizeLeft: ~w", [SizeLeft]), Fun = fun(Vb, {Sz, Res}) when Sz > 0 -> ?vt("enc_vbs -> (fun) entry with" "~n Vb: ~p" "~n Sz: ~p" "~n Res: ~w", [Vb, Sz, Res]), case (catch snmp_pdus:enc_varbind(Vb)) of {'EXIT', Reason} -> ?vtrace("enc_vbs -> encode failed: " "~n Reason: ~p", [Reason]), throw({error, Vb#varbind.org_index, Reason}); X -> ?vt("enc_vbs -> X: ~w", [X]), Lx = length(X), ?vt("enc_vbs -> Lx: ~w", [Lx]), if Lx < Sz -> {Sz - length(X), [X | Res]}; true -> throw(Res) end end; (_Vb, {_Sz, [_H | T]}) -> ?vt("enc_vbs -> (fun) entry with" "~n T: ~p", [T]), throw(T); (_Vb, {_Sz, []}) -> ?vt("enc_vbs -> (fun) entry", []), throw([]) end, lists:foldl(Fun, {SizeLeft, []}, Vbs). do_get_rep(Sz, MibView, MaxRepetitions, Varbinds, Res, GbNumVBs, GbMaxVBs) when MaxRepetitions >= 0 -> do_get_rep(Sz, MibView, 0, MaxRepetitions, Varbinds, Res, GbNumVBs, GbMaxVBs); do_get_rep(Sz, MibView, _MaxRepetitions, Varbinds, Res, GbNumVBs, GbMaxVBs) -> do_get_rep(Sz, MibView, 0, 0, Varbinds, Res, GbNumVBs, GbMaxVBs). conv_res(ResVarbinds) -> conv_res(ResVarbinds, []). conv_res([VbListOfBytes | T], Bytes) -> conv_res(T, VbListOfBytes ++ Bytes); conv_res([], Bytes) -> Bytes. %% The only other value, then a positive integer, is infinity. do_get_rep(_Sz, _MibView, Count, Max, _, _Res, GbNumVBs, GbMaxVBs) when (is_integer(GbMaxVBs) andalso (GbNumVBs > GbMaxVBs)) -> ?vinfo("Max Get-BULK VBs limit (~w) exceeded (~w) when:" "~n Count: ~p" "~n Max: ~p", [GbMaxVBs, GbNumVBs, Count, Max]), {tooBig, 0, []}; do_get_rep(_Sz, _MibView, Max, Max, _, Res, _GbNumVBs, _GbMaxVBs) -> ?vt("do_get_rep -> done when: " "~n Res: ~p", [Res]), {noError, 0, conv_res(Res)}; do_get_rep(Sz, MibView, Count, Max, Varbinds, Res, GbNumVBs, GbMaxVBs) -> ?vt("do_get_rep -> entry when: " "~n Sz: ~p" "~n Count: ~p" "~n Res: ~w", [Sz, Count, Res]), case try_get_bulk(Sz, MibView, Varbinds, GbMaxVBs) of {noError, NextVarbinds, SizeLeft, Res2} -> ?vt("do_get_rep -> noError: " "~n SizeLeft: ~p" "~n Res2: ~p", [SizeLeft, Res2]), do_get_rep(SizeLeft, MibView, Count+1, Max, NextVarbinds, Res2 ++ Res, GbNumVBs + length(Varbinds), GbMaxVBs); {endOfMibView, _NextVarbinds, _SizeLeft, Res2} -> ?vt("do_get_rep -> endOfMibView: " "~n Res2: ~p", [Res2]), {noError, 0, conv_res(Res2 ++ Res)}; {ErrorStatus, Index} -> ?vtrace("do_get_rep -> done when error: " "~n ErrorStatus: ~p" "~n Index: ~p", [ErrorStatus, Index]), {ErrorStatus, Index, []} end. try_get_bulk(Sz, MibView, Varbinds, GbMaxVBs) -> ?vt("try_get_bulk -> entry with" "~n Sz: ~w" "~n MibView: ~w" "~n Varbinds: ~w", [Sz, MibView, Varbinds]), case do_get_next2(MibView, Varbinds, GbMaxVBs) of {noError, 0, UNextVarbinds} -> ?vt("try_get_bulk -> noError: " "~n UNextVarbinds: ~p", [UNextVarbinds]), NextVarbinds = ?LIB:org_index_sort_vbs(UNextVarbinds), case (catch enc_vbs(Sz, NextVarbinds)) of {error, Idx, Reason} -> ?LIB:user_err("failed encoding varbind ~w:~n~p", [Idx, Reason]), ?vtrace("try_get_bulk -> encode error: " "~n Idx: ~p" "~n Reason: ~p", [Idx, Reason]), {genErr, Idx}; {SizeLeft, Res} when is_integer(SizeLeft) andalso is_list(Res) -> ?vt("try get bulk -> encode ok: " "~n SizeLeft: ~w" "~n Res: ~w", [SizeLeft, Res]), {check_end_of_mibview(NextVarbinds), NextVarbinds, SizeLeft, Res}; Res when is_list(Res) -> ?vt("try get bulk -> Res: " "~n ~w", [Res]), {endOfMibView, [], 0, Res} end; {ErrorStatus, Index, _} -> ?vt("try_get_bulk -> error: " "~n ErrorStatus: ~p" "~n Index: ~p", [ErrorStatus, Index]), {ErrorStatus, Index} end. %% If all variables in this pass are endOfMibView, %% there is no reason to continue. check_end_of_mibview([#varbind{value = endOfMibView} | T]) -> check_end_of_mibview(T); check_end_of_mibview([]) -> endOfMibView; check_end_of_mibview(_) -> noError.