aboutsummaryrefslogtreecommitdiffstats
path: root/lib/snmp/doc/src/snmp_impl_example_agent.xml
diff options
context:
space:
mode:
Diffstat (limited to 'lib/snmp/doc/src/snmp_impl_example_agent.xml')
-rw-r--r--lib/snmp/doc/src/snmp_impl_example_agent.xml510
1 files changed, 510 insertions, 0 deletions
diff --git a/lib/snmp/doc/src/snmp_impl_example_agent.xml b/lib/snmp/doc/src/snmp_impl_example_agent.xml
new file mode 100644
index 0000000000..3bba6467ed
--- /dev/null
+++ b/lib/snmp/doc/src/snmp_impl_example_agent.xml
@@ -0,0 +1,510 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE chapter SYSTEM "chapter.dtd">
+
+<chapter>
+ <header>
+ <copyright>
+ <year>1997</year><year>2009</year>
+ <holder>Ericsson AB. All Rights Reserved.</holder>
+ </copyright>
+ <legalnotice>
+ 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.
+
+ </legalnotice>
+
+ <title>Agent Implementation Example</title>
+ <prepared></prepared>
+ <responsible></responsible>
+ <docno></docno>
+ <approved></approved>
+ <checked></checked>
+ <date></date>
+ <rev></rev>
+ <file>snmp_impl_example_agent.xml</file>
+ </header>
+ <p>This <em>Implementation Example</em> section describes how an
+ MIB can be implemented with the SNMP Development Toolkit. </p>
+ <p>The example shown can be found in the toolkit distribution. </p>
+ <p>The agent is configured with the configuration tool, using
+ default suggestions for everything but the manager node. </p>
+
+ <section>
+ <title>MIB</title>
+ <p>The MIB used in this example is called EX1-MIB. It contains two
+ objects, a variable with a name and a table with friends.
+ </p>
+ <code type="none">
+EX1-MIB DEFINITIONS ::= BEGIN
+
+ IMPORTS
+ RowStatus FROM STANDARD-MIB
+ DisplayString FROM RFC1213-MIB
+ OBJECT-TYPE FROM RFC-1212
+ ;
+
+ example1 OBJECT IDENTIFIER ::= { experimental 7 }
+
+ myName OBJECT-TYPE
+ SYNTAX DisplayString (SIZE (0..255))
+ ACCESS read-write
+ STATUS mandatory
+ DESCRIPTION
+ "My own name"
+ ::= { example1 1 }
+
+ friendsTable OBJECT-TYPE
+ SYNTAX SEQUENCE OF FriendsEntry
+ ACCESS not-accessible
+ STATUS mandatory
+ DESCRIPTION
+ "A list of friends."
+ ::= { example1 4 }
+
+ friendsEntry OBJECT-TYPE
+ SYNTAX FriendsEntry
+ ACCESS not-accessible
+ STATUS mandatory
+ DESCRIPTION
+ ""
+ INDEX { fIndex }
+ ::= { friendsTable 1 }
+
+ FriendsEntry ::=
+ SEQUENCE {
+ fIndex
+ INTEGER,
+ fName
+ DisplayString,
+ fAddress
+ DisplayString,
+ fStatus
+ RowStatus }
+
+ fIndex OBJECT-TYPE
+ SYNTAX INTEGER
+ ACCESS not-accessible
+ STATUS mandatory
+ DESCRIPTION
+ "number of friend"
+ ::= { friendsEntry 1 }
+
+ fName OBJECT-TYPE
+ SYNTAX DisplayString (SIZE (0..255))
+ ACCESS read-write
+ STATUS mandatory
+ DESCRIPTION
+ "Name of friend"
+ ::= { friendsEntry 2 }
+ fAddress OBJECT-TYPE
+ SYNTAX DisplayString (SIZE (0..255))
+ ACCESS read-write
+ STATUS mandatory
+ DESCRIPTION
+ "Address of friend"
+ ::= { friendsEntry 3 }
+ fStatus OBJECT-TYPE
+ SYNTAX RowStatus
+ ACCESS read-write
+ STATUS mandatory
+ DESCRIPTION
+ "The status of this conceptual row."
+ ::= { friendsEntry 4 }
+ fTrap TRAP-TYPE
+ ENTERPRISE example1
+ VARIABLES { myName, fIndex }
+ DESCRIPTION
+ "This trap is sent when something happens to
+ the friend specified by fIndex."
+ ::= 1
+END
+ </code>
+ </section>
+
+ <section>
+ <title>Default Implementation</title>
+ <p>Without writing any instrumentation functions, we can compile
+ the MIB and use the default implementation of it. Recall that MIBs
+ imported by "EX1-MIB.mib" must be present and compiled in the
+ current directory ("./STANDARD-MIB.bin","./RFC1213-MIB.bin") when
+ compiling.
+ </p>
+ <pre>
+unix> <input>erl -config ./sys</input>
+1> <input>application:start(snmp).</input>
+ok
+2> <input>snmpc:compile("EX1-MIB").</input>
+No accessfunction for 'friendsTable', using default.
+No accessfunction for 'myName', using default.
+{ok, "EX1-MIB.bin"}
+3> <input>snmpa:load_mibs(snmp_master_agent, ["EX1-MIB"]).</input>
+ok
+ </pre>
+ <p>This MIB is now loaded into the agent, and a manager can ask
+ questions. As an example of this, we start another Erlang system
+ and the simple Erlang manager in the toolkit:
+ </p>
+ <pre>
+1> <input>snmp_test_mgr:start_link([{agent,"dront.ericsson.se"},{community,"all-rights"},</input>
+ %% making it understand symbolic names: {mibs,["EX1-MIB","STANDARD-MIB"]}]).
+{ok, &lt;0.89.0&gt;}
+%% a get-next request with one OID.
+2> <input>snmp_test_mgr:gn([[1,3,6,1,3,7]]).</input>
+ok
+* Got PDU:
+[myName,0] = []
+%% A set-request (now using symbolic names for convenience)
+3> <input>snmp_test_mgr:s([{[myName,0], "Martin"}]).</input>
+ok
+* Got PDU:
+[myName,0] = "Martin"
+%% Try the same get-next request again
+4> <input>snmp_test_mgr:gn([[1,3,6,1,3,7]]).</input>
+ok
+* Got PDU:
+[myName,0] = "Martin"
+%% ... and we got the new value.
+%% you can event do row operations. How to add a row:
+5> <input>snmp_test_mgr:s([{[fName,0], "Martin"}, {[fAddress,0],"home"}, {[fStatus,0],4}]).</input>
+ %% createAndGo
+ok
+* Got PDU:
+[fName,0] = "Martin"
+[fAddress,0] = "home"
+[fStatus,0] = 4
+6> <input>snmp_test_mgr:gn([[myName,0]]).</input>
+ok
+* Got PDU:
+[fName,0] = "Martin"
+7> <input>snmp_test_mgr:gn().</input>
+ok
+* Got PDU:
+[fAddress,0] = "home"
+8> <input>snmp_test_mgr:gn().</input>
+ok
+* Got PDU:
+[fStatus,0] = 1
+9>
+ </pre>
+ </section>
+
+ <section>
+ <title>Manual Implementation</title>
+ <p>The following example shows a "manual" implementation of the
+ EX1-MIB in Erlang. In this example, the values of the objects are
+ stored in an Erlang server. The server has a 2-tuple as loop
+ data, where the first element is the value of variable
+ <c>myName</c>, and the second is a sorted list of rows in the
+ table <c>friendsTable</c>. Each row is a 4-tuple.
+ </p>
+ <note>
+ <p>There are more efficient ways to create tables manually, i.e.
+ to use the module <c>snmp_index</c>.</p>
+ </note>
+
+ <section>
+ <title>Code</title>
+ <code type="none"><![CDATA[
+-module(ex1).
+-author('[email protected]').
+%% External exports
+-export([start/0, my_name/1, my_name/2, friends_table/3]).
+%% Internal exports
+-export([init/0]).
+-define(status_col, 4).
+-define(active, 1).
+-define(notInService, 2).
+-define(notReady, 3).
+-define(createAndGo, 4). % Action; written, not read
+-define(createAndWait, 5). % Action; written, not read
+-define(destroy, 6). % Action; written, not read
+start() ->
+ spawn(ex1, init, []).
+%%----------------------------------------------------------------
+%% Instrumentation function for variable myName.
+%% Returns: (get) {value, Name}
+%% (set) noError
+%%----------------------------------------------------------------
+my_name(get) ->
+ ex1_server ! {self(), get_my_name},
+ Name = wait_answer(),
+ {value, Name}.
+my_name(set, NewName) ->
+ ex1_server ! {self(), {set_my_name, NewName}},
+ noError.
+%%----------------------------------------------------------------
+%% Instrumentation function for table friendsTable.
+%%----------------------------------------------------------------
+friends_table(get, RowIndex, Cols) ->
+ case get_row(RowIndex) of
+ {ok, Row} ->
+ get_cols(Cols, Row);
+ _ ->
+ {noValue, noSuchInstance}
+ end;
+friends_table(get_next, RowIndex, Cols) ->
+ case get_next_row(RowIndex) of
+ {ok, Row} ->
+ get_next_cols(Cols, Row);
+ _ ->
+ case get_next_row([]) of
+ {ok, Row} ->
+ % Get next cols from first row.
+ NewCols = add_one_to_cols(Cols),
+ get_next_cols(NewCols, Row);
+ _ ->
+ end_of_table(Cols)
+ end
+ end;
+%%----------------------------------------------------------------
+%% If RowStatus is set, then:
+%% *) If set to destroy, check that row does exist
+%% *) If set to createAndGo, check that row does not exist AND
+%% that all columns are given values.
+%% *) Otherwise, error (for simplicity).
+%% Otherwise, row is modified; check that row exists.
+%%----------------------------------------------------------------
+friends_table(is_set_ok, RowIndex, Cols) ->
+ RowExists =
+ case get_row(RowIndex) of
+ {ok, _Row} -> true;
+ _ -> false
+ end,
+ case is_row_status_col_changed(Cols) of
+ {true, ?destroy} when RowExists == true ->
+ {noError, 0};
+ {true, ?createAndGo} when RowExists == false,
+ length(Cols) == 3 ->
+ {noError, 0};
+ {true, _} ->
+ {inconsistentValue, ?status_col};
+ false when RowExists == true ->
+ {noError, 0};
+ _ ->
+ [{Col, _NewVal} | _Cols] = Cols,
+ {inconsistentName, Col}
+ end;
+friends_table(set, RowIndex, Cols) ->
+ case is_row_status_col_changed(Cols) of
+ {true, ?destroy} ->
+ ex1_server ! {self(), {delete_row, RowIndex}};
+ {true, ?createAndGo} ->
+ NewRow = make_row(RowIndex, Cols),
+ ex1_server ! {self(), {add_row, NewRow}};
+ false ->
+ {ok, Row} = get_row(RowIndex),
+ NewRow = merge_rows(Row, Cols),
+ ex1_server ! {self(), {delete_row, RowIndex}},
+ ex1_server ! {self(), {add_row, NewRow}}
+ end,
+ {noError, 0}.
+
+%%----------------------------------------------------------------
+%% Make a list of {value, Val} of the Row and Cols list.
+%%----------------------------------------------------------------
+get_cols([Col | Cols], Row) ->
+ [{value, element(Col, Row)} | get_cols(Cols, Row)];
+get_cols([], _Row) ->
+ [].
+%%----------------------------------------------------------------
+%% As get_cols, but the Cols list may contain invalid column
+%% numbers. If it does, we must find the next valid column,
+%% or return endOfTable.
+%%----------------------------------------------------------------
+get_next_cols([Col | Cols], Row) when Col < 2 ->
+ [{[2, element(1, Row)], element(2, Row)} |
+ get_next_cols(Cols, Row)];
+get_next_cols([Col | Cols], Row) when Col > 4 ->
+ [endOfTable |
+ get_next_cols(Cols, Row)];
+get_next_cols([Col | Cols], Row) ->
+ [{[Col, element(1, Row)], element(Col, Row)} |
+ get_next_cols(Cols, Row)];
+get_next_cols([], _Row) ->
+ [].
+%%----------------------------------------------------------------
+%% Make a list of endOfTable with as many elems as Cols list.
+%%----------------------------------------------------------------
+end_of_table([Col | Cols]) ->
+ [endOfTable | end_of_table(Cols)];
+end_of_table([]) ->
+ [].
+add_one_to_cols([Col | Cols]) ->
+ [Col + 1 | add_one_to_cols(Cols)];
+add_one_to_cols([]) ->
+ [].
+is_row_status_col_changed(Cols) ->
+ case lists:keysearch(?status_col, 1, Cols) of
+ {value, {?status_col, StatusVal}} ->
+ {true, StatusVal};
+ _ -> false
+ end.
+get_row(RowIndex) ->
+ ex1_server ! {self(), {get_row, RowIndex}},
+ wait_answer().
+get_next_row(RowIndex) ->
+ ex1_server ! {self(), {get_next_row, RowIndex}},
+ wait_answer().
+wait_answer() ->
+ receive
+ {ex1_server, Answer} ->
+ Answer
+ end.
+%%%---------------------------------------------------------------
+%%% Server code follows
+%%%---------------------------------------------------------------
+init() ->
+ register(ex1_server, self()),
+ loop("", []).
+
+loop(MyName, Table) ->
+ receive
+ {From, get_my_name} ->
+ From ! {ex1_server, MyName},
+ loop(MyName, Table);
+ {From, {set_my_name, NewName}} ->
+ loop(NewName, Table);
+ {From, {get_row, RowIndex}} ->
+ Res = table_get_row(Table, RowIndex),
+ From ! {ex1_server, Res},
+ loop(MyName, Table);
+ {From, {get_next_row, RowIndex}} ->
+ Res = table_get_next_row(Table, RowIndex),
+ From ! {ex1_server, Res},
+ loop(MyName, Table);
+ {From, {delete_row, RowIndex}} ->
+ NewTable = table_delete_row(Table, RowIndex),
+ loop(MyName, NewTable);
+ {From, {add_row, NewRow}} ->
+ NewTable = table_add_row(Table, NewRow),
+ loop(MyName, NewTable)
+ end.
+%%%---------------------------------------------------------------
+%%% Functions for table operations. The table is represented as
+%%% a list of rows.
+%%%---------------------------------------------------------------
+table_get_row([{Index, Name, Address, Status} | _], [Index]) ->
+ {ok, {Index, Name, Address, Status}};
+table_get_row([H | T], RowIndex) ->
+ table_get_row(T, RowIndex);
+table_get_row([], _RowIndex) ->
+ no_such_row.
+table_get_next_row([Row | T], []) ->
+ {ok, Row};
+table_get_next_row([Row | T], [Index | _])
+when element(1, Row) > Index ->
+ {ok, Row};
+table_get_next_row([Row | T], RowIndex) ->
+ table_get_next_row(T, RowIndex);
+table_get_next_row([], RowIndex) ->
+ endOfTable.
+table_delete_row([{Index, _, _, _} | T], [Index]) ->
+ T;
+table_delete_row([H | T], RowIndex) ->
+ [H | table_delete_row(T, RowIndex)];
+table_delete_row([], _RowIndex) ->
+ [].
+table_add_row([Row | T], NewRow)
+ when element(1, Row) > element(1, NewRow) ->
+ [NewRow, Row | T];
+table_add_row([H | T], NewRow) ->
+ [H | table_add_row(T, NewRow)];
+table_add_row([], NewRow) ->
+ [NewRow].
+make_row([Index], [{2, Name}, {3, Address} | _]) ->
+ {Index, Name, Address, ?active}.
+merge_rows(Row, [{Col, NewVal} | T]) ->
+ merge_rows(setelement(Col, Row, NewVal), T);
+merge_rows(Row, []) ->
+ Row.
+ ]]></code>
+ </section>
+
+ <section>
+ <title>Association File</title>
+ <p>The association file <c>EX1-MIB.funcs</c> for the real
+ implementation looks as follows:
+ </p>
+ <code type="none">
+{myName, {ex1, my_name, []}}.
+{friendsTable, {ex1, friends_table, []}}.
+ </code>
+ </section>
+
+ <section>
+ <title>Transcript</title>
+ <p>To use the real implementation, we must recompile the MIB and
+ load it into the agent.
+ </p>
+ <pre>
+1> <input>application:start(snmp).</input>
+ok
+2> <input>snmpc:compile("EX1-MIB").</input>
+{ok,"EX1-MIB.bin"}
+3> <input>snmpa:load_mibs(snmp_master_agent, ["EX1-MIB"]).</input>
+ok
+4> <input>ex1:start().</input>
+&lt;0.115.0&gt;
+%% Now all requests operates on this "real" implementation.
+%% The output from the manager requests will *look* exactly the
+%% same as for the default implementation.
+ </pre>
+ </section>
+
+ <section>
+ <title>Trap Sending</title>
+ <p>How to send a trap by sending the
+ <c>fTrap</c> from the master agent is shown in this section.
+ The master agent has the MIB <c>EX1-MIB</c> loaded, where the
+ trap is defined. This trap specifies that two variables should
+ be sent along with the trap, <c>myName</c> and <c>fIndex</c>.
+ <c>fIndex</c> is a table column, so we must provide its value
+ and the index for the row in the call to <c>snmpa:send_trap/4</c>.
+ In the example below, we assume that the row in question is
+ indexed by 2 (the row with <c>fIndex</c> 2).
+ </p>
+ <p>we use a simple Erlang SNMP manager, which can receive traps.
+ </p>
+ <pre>
+[MANAGER]
+1> <input>snmp_test_mgr:start_link([{agent,"dront.ericsson.se"},{community,"public"}</input>
+ %% does not have write-access
+1><input>{mibs,["EX1-MIB","STANDARD-MIB"]}]).</input>
+{ok, &lt;0.100.0&gt;}
+2> <input>snmp_test_mgr:s([{[myName,0], "Klas"}]).</input>
+ok
+* Got PDU:
+Received a trap:
+ Generic: 4 %% authenticationFailure
+ Enterprise: [iso,2,3]
+ Specific: 0
+ Agent addr: [123,12,12,21]
+ TimeStamp: 42993
+2>
+[AGENT]
+3> <input>snmpa:send_trap(snmp_master_agent, fTrap,"standard trap", [{fIndex,[2],2}]).</input>
+[MANAGER]
+2>
+* Got PDU:
+Received a trap:
+ Generic: 6
+ Enterprise: [example1]
+ Specific: 1
+ Agent addr: [123,12,12,21]
+ TimeStamp: 69649
+[myName,0] = "Martin"
+[fIndex,2] = 2
+2>
+ </pre>
+ </section>
+ </section>
+</chapter>
+