檢視原始碼 代理程式實作範例

這個實作範例章節描述如何使用 SNMP 開發工具組實作 MIB。

範例可以在工具組發行版中找到。

代理程式會使用組態工具進行設定,除了管理節點以外,其他都使用預設建議。

MIB

此範例中使用的 MIB 名為 EX1-MIB。它包含兩個物件,一個帶有名稱的變數和一個帶有朋友的表格。

EX1-MIB DEFINITIONS ::= BEGIN

          IMPORTS
                  experimental   FROM RFC1155-SMI
                  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

預設實作

在不編寫任何檢測函數的情況下,我們可以編譯 MIB 並使用它的預設實作。請注意,當編譯時,"EX1-MIB.mib" 匯入的 MIB 必須存在並已在目前目錄中編譯 ("./STANDARD-MIB.bin","./RFC1213-MIB.bin")。

unix> erl -config ./sys
1> application:start(snmp).
ok
2> snmpc:compile("EX1-MIB").
No accessfunction for 'friendsTable', using default.
No accessfunction for 'myName', using default.
{ok, "EX1-MIB.bin"}
3> snmpa:load_mibs(snmp_master_agent, ["EX1-MIB"]).
ok

這個 MIB 現在已載入到代理程式中,並且管理員可以提出問題。作為範例,我們啟動另一個 Erlang 系統和工具組中的簡單 Erlang 管理器

1> snmp_test_mgr:start_link([{agent,"dront.ericsson.se"},{community,"all-rights"},
 %% making it understand symbolic names: {mibs,["EX1-MIB","STANDARD-MIB"]}]).
{ok, <0.89.0>}
%% a get-next request with one OID.
2> snmp_test_mgr:gn([[1,3,6,1,3,7]]).
ok
* Got PDU:
[myName,0] = []
%% A set-request (now using symbolic names for convenience)
3> snmp_test_mgr:s([{[myName,0], "Martin"}]).
ok
* Got PDU:
[myName,0] = "Martin"
%% Try the same get-next request again
4> snmp_test_mgr:gn([[1,3,6,1,3,7]]).
ok
* Got PDU:
[myName,0] = "Martin"
%% ... and we got the new value.
%% you can event do row operations. How to add a row:
5> snmp_test_mgr:s([{[fName,0], "Martin"}, {[fAddress,0],"home"}, {[fStatus,0],4}]).
 %% createAndGo
ok
* Got PDU:
[fName,0] = "Martin"
[fAddress,0] = "home"
[fStatus,0] = 4
6> snmp_test_mgr:gn([[myName,0]]).
ok
* Got PDU:
[fName,0] = "Martin"
7> snmp_test_mgr:gn().
ok
* Got PDU:
[fAddress,0] = "home"
8> snmp_test_mgr:gn().
ok
* Got PDU:
[fStatus,0] = 1
9>

手動實作

以下範例展示了在 Erlang 中 "手動" 實作 EX1-MIB。在此範例中,物件的值儲存在 Erlang 伺服器中。伺服器具有一個 2 元組作為迴圈資料,其中第一個元素是變數 myName 的值,第二個是表格 friendsTable 中已排序的列清單。每列都是一個 4 元組。

注意

有更有效率的方法可以手動建立表格,例如使用模組 snmp_index

程式碼

-module(ex1).
-author('dummy@flop.org').
%% 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.

關聯檔案

真實實作的關聯檔案 EX1-MIB.funcs 如下

{myName, {ex1, my_name, []}}.
{friendsTable, {ex1, friends_table, []}}.

文字記錄

要使用真實實作,我們必須重新編譯 MIB 並將其載入到代理程式中。

1> application:start(snmp).
ok
2> snmpc:compile("EX1-MIB").
{ok,"EX1-MIB.bin"}
3> snmpa:load_mibs(snmp_master_agent, ["EX1-MIB"]).
ok
4> ex1:start().
<0.115.0>
%% Now all requests operates on this "real" implementation.
%% The output from the manager requests will *look* exactly the
%% same as for the default implementation.

陷阱發送

本節說明如何從主代理程式發送 fTrap 來發送陷阱。主代理程式已載入 MIB EX1-MIB,其中定義了陷阱。這個陷阱指定應該與陷阱一起發送兩個變數,myNamefIndexfIndex 是一個表格欄位,因此我們必須在呼叫 snmpa:send_notification2/3 時提供其值和列的索引。在下面的範例中,我們假設有問題的列由 2 索引(具有 fIndex 2 的列)。

我們使用一個簡單的 Erlang SNMP 管理器,它可以接收陷阱。

[MANAGER]
1> snmp_test_mgr:start_link([{agent,"dront.ericsson.se"},{community,"public"}
 %% does not have write-access
1>{mibs,["EX1-MIB","STANDARD-MIB"]}]).
{ok, <0.100.0>}
2> snmp_test_mgr:s([{[myName,0], "Klas"}]).
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> SendOpts = [{receiver, no_receiver}, {varbinds, [{fIndex,[2],2}]}, {name, "standard trap"}, {context, ""}],
4> snmpa:send_notification2(snmp_master_agent, fTrap, SendOpts).
[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>