%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 1999-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
%% 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(snmp_framework_mib).
-include("snmp_types.hrl").
-include("STANDARD-MIB.hrl").
-define(VMODULE,"FRAMEWORK-MIB").
-include("snmp_verbosity.hrl").
-ifndef(default_verbosity).
-define(default_verbosity,silence).
-endif.
%%%-----------------------------------------------------------------
%%% This module implements the init- configure- and instrumentation-
%%% functions for the SNMP-FRAMEWORK-MIB.
%%%
%%% We also keep internal datastructures here, e.g. a table
%%% over all known contexts.
%%%-----------------------------------------------------------------
%% External exports
%% Avoid warning for local function error/1 clashing with autoimported BIF.
-compile({no_auto_import,[error/1]}).
-export([init/0, configure/1]).
-export([intContextTable/1, intContextTable/3,
intAgentTransportDomain/1, intAgentTransports/1,
intAgentUDPPort/1, intAgentIpAddress/1,
snmpEngineID/1,
snmpEngineBoots/1,
snmpEngineTime/1,
snmpEngineMaxMessageSize/1,
get_engine_id/0, get_engine_max_message_size/0,
get_engine_boots/0, get_engine_time/0,
set_engine_boots/1, set_engine_time/1,
table_next/2, check_status/3]).
-export([add_context/1, delete_context/1]).
-export([check_agent/2, check_context/1, order_agent/2]).
%%-----------------------------------------------------------------
%% Func: init/0
%% Purpose: Creates the tables and variables necessary for the SNMP
%% mechanism to work properly.
%% Note that this function won't destroy any old values.
%% This function should be called only once.
%%-----------------------------------------------------------------
init() ->
maybe_create_table(intContextTable),
init_engine().
%%-----------------------------------------------------------------
%% Func: configure/1
%% Args: Dir is the directory with trailing dir_separator where
%% the configuration files can be found.
%% Purpose: Reads the config-files for the internal tables, and
%% inserts the data. Makes sure that all old data in
%% the tables are deleted, and the new data inserted.
%% This function makes sure that all (and only)
%% config-file-data are in the tables.
%% Returns: ok
%% Fails: exit(configuration_error)
%% PRE: init/1 has been successfully called
%%-----------------------------------------------------------------
configure(Dir) ->
set_sname(),
case snmpa_agent:get_agent_mib_storage() of
mnesia ->
ok;
_ ->
case (catch do_configure(Dir)) of
ok ->
ok;
{error, Reason} ->
?vinfo("configure error: ~p", [Reason]),
config_err("configure failed: ~p", [Reason]),
exit(configuration_error);
Error ->
?vinfo("configure failed: ~p", [Error]),
config_err("configure failed: ~p", [Error]),
exit(configuration_error)
end
end,
ok.
do_configure(Dir) ->
?vdebug("read internal config files",[]),
Contexts = read_internal_config_files(Dir),
?vdebug("read agent config files",[]),
Agent = read_agent(Dir),
?vdebug("initiate vars",[]),
init_vars(Agent),
%% Add default context, if not present.
NContexts = [{""} | lists:delete({""}, Contexts)],
?vdebug("initiate tables",[]),
init_tabs(NContexts),
ok.
read_internal_config_files(Dir) ->
?vdebug("read context config file",[]),
Gen = fun gen_context/2,
Order = fun snmp_conf:no_order/2,
Filter = fun snmp_conf:no_filter/1,
Check = fun(Entry, State) -> {check_context(Entry), State} end,
[Ctxs] =
snmp_conf:read_files
(Dir, [{"context.conf", Gen, Order, Check, Filter}]),
Ctxs.
read_agent(Dir) ->
?vdebug("read agent config file", []),
FileName = "agent.conf",
File = filename:join(Dir, FileName),
Conf0 =
try
snmp_conf:read(File, fun order_agent/2, fun check_agent/2)
catch
throw:{error, Reason} ->
error({failed_reading_config_file, Dir, FileName, Reason})
end,
Mand =
[{intAgentTransports, mandatory},
{snmpEngineMaxMessageSize, mandatory},
{snmpEngineID, mandatory}],
{ok, Conf} = snmp_conf:check_mandatory(Conf0, Mand),
Conf.
%%-----------------------------------------------------------------
%% Generate a context.conf file.
%%-----------------------------------------------------------------
gen_context(Dir, _Reason) ->
config_err("missing context.conf file => generating a default file", []),
File = filename:join(Dir, "context.conf"),
case file:open(File, [write]) of
{ok, Fid} ->
ok = io:format(Fid, "~s\n", [context_header()]),
ok = io:format(Fid, "%% The default context\n\"\".\n", []),
file:close(Fid),
[];
{error, Reason} ->
file:delete(File),
error({failed_creating_file, File, Reason})
end.
context_header() ->
{Y,Mo,D} = date(),
{H,Mi,S} = time(),
io_lib:format("%% This file was automatically generated by "
"snmp_config v~s ~w-~2.2.0w-~2.2.0w "
"~2.2.0w:~2.2.0w:~2.2.0w\n",
[?version, Y, Mo, D, H, Mi, S]).
%%-----------------------------------------------------------------
%% Context
%% Context.
%%-----------------------------------------------------------------
check_context(Context) ->
?vtrace("check_context -> entry with"
"~n Context: ~p", [Context]),
case (catch snmp_conf:check_string(Context)) of
ok ->
{ok, {Context}};
_ ->
error({invalid_context, Context})
end.
%%-----------------------------------------------------------------
%% Agent
%% {Name, Value}.
%%-----------------------------------------------------------------
check_agent(Entry, undefined) ->
check_agent(Entry, {snmp_target_mib:default_domain(), undefined});
check_agent({intAgentTransportDomain, Domain}, {_, Port}) ->
{snmp_conf:check_domain(Domain), {Domain, Port}};
check_agent({intAgentUDPPort, Port}, {Domain, _}) ->
ok = snmp_conf:check_port(Port),
{ok, {Domain, Port}};
check_agent({intAgentIpAddress, _}, {_, undefined}) ->
error({missing_mandatory, intAgentUDPPort});
check_agent({intAgentIpAddress = Tag, Ip} = Entry, {Domain, Port} = State) ->
{case snmp_conf:check_ip(Domain, Ip) of
ok ->
[Entry, {intAgentTransports, [{Domain, {Ip, Port}}]}];
{ok, FixedIp} ->
[{Tag, Ip}, {intAgentTransports, [{Domain, {FixedIp, Port}}]}]
end, State};
check_agent({intAgentTransports = Tag, Transports}, {_, Port} = State) ->
CheckedTransports =
[case
case Port of
undefined ->
snmp_conf:check_address(Domain, Address);
_ ->
snmp_conf:check_address(Domain, Address, Port)
end
of
ok ->
Transport;
{ok, FixedAddress} ->
{Domain, FixedAddress}
end
|| {Domain, Address} = Transport <- Transports],
{{ok, {Tag, CheckedTransports}}, State};
check_agent(Entry, State) ->
{check_agent(Entry), State}.
%% This one is kept for backwards compatibility
check_agent({intAgentMaxPacketSize, Value}) ->
snmp_conf:check_packet_size(Value);
check_agent({snmpEngineMaxMessageSize, Value}) ->
snmp_conf:check_packet_size(Value);
check_agent({snmpEngineID, Value}) ->
snmp_conf:check_string(Value);
check_agent(X) ->
error({invalid_agent_attribute, X}).
%% Ordering function to sort intAgentTransportDomain first
%% hence before intAgentIpAddress. Sort other entries on the key.
order_agent(EntryA, EntryB) ->
snmp_conf:keyorder(
1, EntryA, EntryB,
[intAgentTransportDomain, intAgentUDPPort | sort]).
maybe_create_table(Name) ->
case snmpa_local_db:table_exists(db(Name)) of
true ->
ok;
_ ->
?vtrace("create table: ~w",[Name]),
snmpa_local_db:table_create(db(Name))
end.
init_vars(Vars) ->
lists:map(fun init_var/1, Vars).
init_var({Var, Val}) ->
?vtrace("init var: "
"~n set ~w to ~w",[Var, Val]),
snmp_generic:variable_set(db(Var), Val).
init_tabs(Contexts) ->
?vdebug("create context table",[]),
snmpa_local_db:table_delete(db(intContextTable)),
snmpa_local_db:table_create(db(intContextTable)),
init_context_table(Contexts).
init_context_table([Row | T]) ->
Context = element(1, Row),
Key = [length(Context) | Context],
?vtrace("create intContextTable table row for: ~w",[Key]),
snmpa_local_db:table_create_row(db(intContextTable), Key, Row),
init_context_table(T);
init_context_table([]) -> true.
table_cre_row(Tab, Key, Row) ->
snmpa_mib_lib:table_cre_row(db(Tab), Key, Row).
table_del_row(Tab, Key) ->
snmpa_mib_lib:table_del_row(db(Tab), Key).
%% FIXME: does not work with mnesia
add_context(Ctx) ->
case (catch check_context(Ctx)) of
{ok, Row} ->
Context = element(1, Row),
Key = [length(Context) | Context],
case table_cre_row(intContextTable, Key, Row) of
true ->
{ok, Key};
false ->
{error, create_failed}
end;
{error, Reason} ->
{error, Reason};
Error ->
{error, Error}
end.
%% FIXME: does not work with mnesia
delete_context(Key) ->
case table_del_row(intContextTable, Key) of
true ->
ok;
false ->
{error, delete_failed}
end.
%%-----------------------------------------------------------------
%% Instrumentation functions
%% Retreive functions are also used internally by the agent, so
%% don't change the interface without changing those functions.
%% Note that if these functions implementations are changed,
%% an error can make the agent crash, as no error detection is
%% performed for the internal data.
%% These functions cannot use the default functions as is, because
%% the default functions rely on that the mib is loaded, and
%% these functions must work even if the OTP-FRAMEWORK-MIB isn't loaded.
%% So we hardcode the information necessary for the functions
%% called by the default functions in snmp_generic. This info is
%% normally provided by the mib compiler, and inserted into
%% snmpa_symbolic_store at load mib time.
%%-----------------------------------------------------------------
%%-----------------------------------------------------------------
%% None if the int* objects are defined in any MIB.
%%
%% intContextTable keeps all
%% known contexts internally, but is ordered as an SNMP table. It
%% could be defined as:
%% intContextTable OBJECT-TYPE
%% SYNTAX SEQUENCE OF IntContextEntry
%% MAX-ACCESS not-accessible
%% STATUS current
%% DESCRIPTION "A table of locally available contexts."
%% ::= { xx }
%%
%% intContextEntry OBJECT-TYPE
%% SYNTAX IntContextEntry
%% MAX-ACCESS not-accessible
%% STATUS current
%% DESCRIPTION "Information about a particular context."
%% INDEX {
%% intContextName
%% }
%% ::= { intContextTable 1 }
%%
%% IntContextEntry ::= SEQUENCE
%% {
%% intContextName SnmpAdminString
%% }
%%
%% intContextName OBJECT-TYPE
%% SYNTAX SnmpAdminString (SIZE(0..32))
%% MAX-ACCESS read-only
%% STATUS current
%% DESCRIPTION "A human readable name identifying a particular
%% context at a particular SNMP entity.
%%
%% The empty contextName (zero length) represents the
%% default context.
%% "
%% ::= { intContextEntry 1 }
%%-----------------------------------------------------------------
%% Op == new | delete
intContextTable(Op) ->
snmp_generic:table_func(Op, db(intContextTable)).
%% Op == get get_next -- READ only table
intContextTable(get, RowIndex, Cols) ->
get(intContextTable, RowIndex, Cols);
intContextTable(get_next, RowIndex, Cols) ->
next(intContextTable, RowIndex, Cols);
intContextTable(Op, Arg1, Arg2) ->
snmp_generic:table_func(Op, Arg1, Arg2, db(intContextTable)).
%% FIXME: exported, not used by agent, not documented - remove?
table_next(Name, RestOid) ->
snmp_generic:table_next(db(Name), RestOid).
%% FIXME: exported, not used by agent, not documented - remove?
%% FIXME: does not work with mnesia
check_status(Name, Indexes, StatusNo) ->
case snmpa_local_db:table_get_element(db(Name), Indexes, StatusNo) of
{value, ?'RowStatus_active'} -> true;
_ -> false
end.
db(intContextTable) -> {intContextTable, volatile};
db(X) -> snmpa_agent:db(X).
fa(intContextTable) -> 1.
foi(intContextTable) -> 1.
noc(intContextTable) -> 1.
next(Name, RowIndex, Cols) ->
snmp_generic:handle_table_next(db(Name), RowIndex, Cols,
fa(Name), foi(Name), noc(Name)).
get(Name, RowIndex, Cols) ->
snmp_generic:handle_table_get(db(Name), RowIndex, Cols, foi(Name)).
%% Op == new | delete | get
intAgentUDPPort(Op) ->
snmp_generic:variable_func(Op, db(intAgentUDPPort)).
intAgentIpAddress(Op) ->
snmp_generic:variable_func(Op, db(intAgentIpAddress)).
intAgentTransportDomain(Op) ->
snmp_generic:variable_func(Op, db(intAgentTransportDomain)).
intAgentTransports(Op) ->
snmp_generic:variable_func(Op, db(intAgentTransports)).
snmpEngineID(print) ->
VarAndValue = [{snmpEngineID, snmpEngineID(get)}],
snmpa_mib_lib:print_variables(VarAndValue);
snmpEngineID(Op) ->
snmp_generic:variable_func(Op, db(snmpEngineID)).
snmpEngineMaxMessageSize(print) ->
VarAndValue = [{snmpEngineMaxMessageSize, snmpEngineMaxMessageSize(get)}],
snmpa_mib_lib:print_variables(VarAndValue);
snmpEngineMaxMessageSize(Op) ->
snmp_generic:variable_func(Op, db(snmpEngineMaxMessageSize)).
snmpEngineBoots(print) ->
VarAndValue = [{snmpEngineBoots, snmpEngineBoots(get)}],
snmpa_mib_lib:print_variables(VarAndValue);
snmpEngineBoots(Op) ->
snmp_generic:variable_func(Op, db(snmpEngineBoots)).
snmpEngineTime(print) ->
VarAndValue = [{snmpEngineTime, snmpEngineTime(get)}],
snmpa_mib_lib:print_variables(VarAndValue);
snmpEngineTime(get) ->
{value, get_engine_time()}.
init_engine() ->
case snmp_generic:variable_get(db(snmpEngineBoots)) of
{value, Val} when Val < 2147483647 ->
snmp_generic:variable_set(db(snmpEngineBoots), Val+1);
{value, _} ->
ok;
undefined ->
snmp_generic:variable_set(db(snmpEngineBoots), 1)
end,
reset_engine_base().
reset_engine_base() ->
ets:insert(snmp_agent_table, {snmp_engine_base, snmp_misc:now(sec)}).
get_engine_id() ->
{value, EngineID} = snmpEngineID(get),
EngineID.
get_engine_max_message_size() ->
{value, MPS} = snmpEngineMaxMessageSize(get),
MPS.
get_engine_time() ->
[{_, EngineBase}] = ets:lookup(snmp_agent_table, snmp_engine_base),
snmp_misc:now(sec) - EngineBase.
get_engine_boots() ->
{value, Val} = snmpEngineBoots(get),
Val.
set_engine_boots(Boots) ->
snmp_generic:variable_func(set, Boots, db(snmpEngineBoots)).
set_engine_time(Time) ->
Base = snmp_misc:now(sec) - Time,
ets:insert(snmp_agent_table, {snmp_engine_base, Base}).
set_sname() ->
set_sname(get(sname)).
set_sname(undefined) ->
put(sname,conf);
set_sname(_) -> %% Keep it, if already set.
ok.
%% ------------------------------------------------------------------
error(Reason) ->
throw({error, Reason}).
config_err(F, A) ->
snmpa_error:config_err("[FRAMEWORK-MIB]: " ++ F, A).