檢視原始碼 進階代理程式主題

本章進階代理程式主題描述了 SNMP 開發工具中更進階的代理程式相關功能。涵蓋以下主題:

  • 何時使用子代理程式
  • 代理程式語意
  • 子代理程式和相依性
  • 分散式表格
  • 容錯能力
  • 使用 Mnesia 表格作為 SNMP 表格
  • 稽核追蹤記錄
  • 與標準的偏差

何時使用子代理程式

何時使用子代理程式章節描述了載入和卸載 MIB 的機制不足的情況。在這些情況下,需要使用子代理程式。

特殊的設定交易機制

每個子代理程式都可以實作自己的 setgetget-next 機制。例如,如果應用程式要求 get 機制為非同步,或需要 N 階段的 set 機制,則應使用專用的子代理程式。

該工具組允許同時使用不同種類的子代理程式。因此,不同的 MIB 可以有不同的 setget 機制。

程序通訊

一個簡單的分散式代理程式可以在沒有子代理程式的情況下進行管理。檢測功能可以使用分散式 Erlang 與應用程式的其他部分進行通訊。但是,如果這樣產生太多不必要的流量,則可以在每個節點上使用子代理程式。子代理程式處理每個傳入的 SNMP 請求,而不是每個變數的請求。因此,網路流量會最小化。

如果檢測功能與 UNIX 程序進行通訊,則使用特殊的子代理程式可能是一個好主意。此子代理程式會將 SNMP 請求以一個封包發送到另一個程序,以最大程度地減少內容切換。例如,如果整個 MIB 在 UNIX 中以 C 語言層級實作,但您仍然想使用 Erlang SNMP 工具,則您可以有一個特殊的子代理程式,將請求中的變數作為單一操作發送到 C。

頻繁載入 MIB

載入和卸載 MIB 是相當廉價的操作。但是,如果應用程式非常頻繁地執行此操作,可能每分鐘多次,則應在子代理程式中一次載入所有 MIB。此子代理程式僅在另一個代理程式下註冊和取消註冊,而不是每次都載入 MIB。這比載入 MIB 更便宜。

與其他 SNMP 代理程式工具組的互動

如果 SNMP 代理程式需要與在另一個套件中建構的子代理程式互動,則應使用特殊的子代理程式,該子代理程式通過另一個套件指定的協定進行通訊。

代理程式語意

可以將代理程式配置為多執行緒,一次處理一個傳入的請求,或啟用請求限制(這可用於負載控制或限制 DoS 攻擊的影響)。如果是多執行緒,讀取請求(getget-nextget-bulk)和陷阱會與 set 請求並行處理。但是,所有 set 請求都是序列化的,這表示如果代理程式正在等待應用程式完成複雜的寫入操作,則它將不會處理任何新的寫入請求,直到此操作完成。它會同時處理讀取請求並傳送陷阱。不平行處理寫入請求的原因是即使在最簡單的情況下也需要複雜的鎖定機制。即使使用上述方案,使用者也必須小心,不要違反 set 請求是原子性的原則。如果很難做到這一點,請不要使用多執行緒功能。

請求內的順序是未定義的,並且變數不會以定義的順序處理。不要假設 PDU 中的第一個變數會在第二個變數之前處理,即使代理程式以這種順序處理變數也是如此。甚至不能假設屬於不同子代理程式的請求有任何順序。

如果管理員嘗試在同一個 PDU 中多次設定同一個變數,代理程式可以自由發揮。沒有任何定義可以確定檢測功能會被呼叫一次還是兩次。如果僅呼叫一次,則沒有定義可以確定將提供哪個新值。

當代理程式收到請求時,它會在傳送回應後保留請求 ID 一秒鐘。如果代理程式在此時間內從相同的 IP 位址和 UDP 連接埠收到具有相同請求 ID 的另一個請求,則該請求將被捨棄。此機制與 snmpa:current_request_id/0 函式無關。

子代理程式和相依性

該工具組支援使用不同類型的子代理程式,但不支援建構子代理程式。

此外,該工具組不支援子代理程式之間的相依性。根據定義,子代理程式應該是獨立的,因此在它們之間建立相依性並不是一個好的設計。

分散式表格

在更複雜的系統中,一個常見的情況是表格中的資料是分散的。不同的表格列在不同的位置實作。一些 SNMP 工具組會為表格的每個部分專門建立一個 SNMP 子代理程式,並將對應的 MIB 載入到所有子代理程式中。主代理程式負責將分散式表格作為單一表格呈現給管理員。提供的工具組使用不同的方法。

使用此 SNMP 工具實作分散式表格的方法是實作一個表格協調器程序,該程序負責協調保存表格資料的程序,這些程序稱為表格持有者。所有表格持有者必須以某種方式被協調器知道;表格資料的結構決定了如何實現這一點。協調器可能要求表格持有者明確註冊自己並指定他們的信息。在其他情況下,表格持有者可以在編譯時一次確定。

當呼叫分散式表格的檢測功能時,應將請求轉發到表格協調器。協調器在表格持有者中尋找請求的信息,然後將答案返回給檢測功能。SNMP 工具組不包含對表格協調的支援,因為這必須獨立於實作。

將表格協調器與 SNMP 工具分離的優點是:

  • 我們不需要每個表格持有者都有一個子代理程式。通常,需要子代理程式來處理通訊,但在分散式 Erlang 中,我們使用普通的訊息傳遞。
  • 最有可能的是,已經存在某種類型的表格協調器。此程序應處理表格的檢測。
  • 呈現分散式表格的方法在很大程度上取決於應用程式。使用不同的遮罩技術僅對一小部分問題有效,並且在分散式表格中註冊每一列會使其非分散式。

容錯能力

SNMP 代理程式工具組會從三個不同的來源獲取輸入:

  • 來自網路的 UDP 封包
  • 來自使用者定義的檢測功能的傳回值
  • 來自 MIB 的傳回值。

代理程式具有高度的容錯能力。如果管理員從代理程式收到意外的回應,則可能是某些檢測功能傳回了錯誤的值。即使檢測功能崩潰,代理程式也不會崩潰。應該注意的是,如果檢測功能進入無限迴圈,代理程式也將永遠被阻塞。監督者或應用程式會指定如何重新啟動代理程式。

在分散式環境中使用 SNMP 代理程式

在分散式環境中使用代理程式的正常方法是在一個節點上使用一個主代理程式,在其他節點上使用零個或多個子代理程式。但是,此配置會使主代理程式節點成為單點故障。如果該節點發生故障,代理程式將無法工作。

此問題的一個解決方案是使 snmp 應用程式成為分散式 Erlang 應用程式,這表示代理程式可以配置為在多個節點之一上執行。如果它運行的節點發生故障,另一個節點會重新啟動代理程式。這稱為故障轉移。當節點再次啟動時,它可以接管應用程式。此問題的解決方案帶來了另一個問題。一般來說,新節點的 IP 位址與第一個節點不同,這可能會導致 SNMP 管理員和代理程式之間的通訊出現問題。

如果將 snmp 代理程式配置為分散式 Erlang 應用程式,它將在接管期間嘗試載入與舊節點載入的 MIB 相同的 MIB。它使用與舊節點相同的檔案名稱。如果 MIB 未位於不同節點的相同路徑中,則必須在接管後明確載入 MIB。

使用 Mnesia 表格作為 SNMP 表格

Mnesia DBMS 可用於儲存 SNMP 表格的資料。這表示 SNMP 表格可以實作為 Mnesia 表格,並且 Mnesia 表格可以透過 SNMP 可見。這種對應在很大程度上是自動化的。

使用這種對應有三個主要原因:

  • 我們可以獲得 Mnesia 的所有功能,例如容錯、持久資料儲存、複寫等等。
  • 所涉及的大部分工作都是自動化的。這包括 get-next 處理和 RowStatus 處理。
  • 該表格可以用作普通的 Mnesia 表格,在應用程式內部使用 Mnesia API 的同時,它也可以透過 SNMP 可見。

當使用此對應時,原始 Mnesia 表格中的插入和刪除速度會變慢,因數為 O(log n)。讀取存取不受影響。

將 SNMP 表格實作為 Mnesia 表格的一個缺點是,內部資源被迫使用來自 MIB 的表格定義,這表示必須在內部使用外部資料模型。實際上,這僅部分正確。Mnesia 表格可以擴展 SNMP 表格,這表示 Mnesia 表格可能具有內部使用且 SNMP 看不到的列。儘管如此,仍然必須維護來自 SNMP 的資料模型。儘管這是不需要的,但在許多情況下,與抽象相比,簡單高效的實作是一種務實的折衷方案。

建立 Mnesia 表格

表格必須在管理員可以使用之前在 Mnesia 中建立。表格必須宣告為 snmp 類型。這使得表格根據 SNMP 的詞彙順序規則排序。Mnesia 表格的名稱必須與 SNMP 表格名稱相同。必須指定對應 SNMP 表格中 INDEX 欄位的類型。

如果 SNMP 表格有多個 INDEX 欄位,對應的 Mnesia 列會是一個元組,其中第一個元素是包含 INDEX 欄位的元組。一般而言,如果 SNMP 表格有 N 個 INDEX 欄位和 C 個資料欄位,Mnesia 表格的元數 (arity) 會是 (C-N)+1,其中鍵值 (key) 如果 N > 1,則是一個元數為 N 的元組;如果 N = 1,則是一個單一的項。

請參閱 Mnesia 使用者指南,以瞭解如何將 Mnesia 表格宣告為 SNMP 表格。

以下範例說明了一種情況,我們希望將 SNMP 表格實作為 Mnesia 表格。該表格儲存有關公司員工的資訊。每位員工都以部門編號和姓名作為索引。

       empTable OBJECT-TYPE
              SYNTAX      SEQUENCE OF EmpEntry
              ACCESS      not-accessible
              STATUS      mandatory
              DESCRIPTION
                      "A table with information about employees."
       ::= { emp 1}
       empEntry OBJECT-TYPE
              SYNTAX      EmpEntry
              ACCESS      not-accessible
              STATUS      mandatory
              DESCRIPTION
                 ""
              INDEX      { empDepNo, empName }
       ::= { empTable 1 }
       EmpEntry ::=
              SEQUENCE {
                  empDepNo         INTEGER,
                  empName          DisplayString,
                  empTelNo         DisplayString,
                  empStatus        RowStatus
              }

對應的 Mnesia 表格規格如下:

mnesia:create_table([{name, employees},
                     {snmp, [{key, {integer, string}}]},
                     {attributes, [key, telno, row_status]}]).

注意

在 Mnesia 表格中,兩個鍵值欄位儲存為包含兩個元素的元組。因此,表格的元數為 3。

儀器功能 (Instrumentation Functions)

上一節中顯示的 MIB 表格可以編譯如下:

1> snmpc:compile("EmpMIB", [{db, mnesia}]).

這就是全部要做的!現在管理員可以讀取、新增和修改列。此外,您可以使用一般的 Mnesia API 從您的程式存取表格。唯一明確的動作是建立 Mnesia 表格,這是使用者為了建立所需的表格綱要而必須執行的動作。

新增自訂動作

通常在修改表格時,需要執行一些特定動作。這可以使用儀器功能來完成。它會在設定表格時執行一些特定的程式碼,並將所有其他請求傳遞到預先定義的功能。

以下範例說明了這個概念

emp_table(set, RowIndex, Cols) ->
    notify_internal_resources(RowIndex, Cols),
    snmp_generic:table_func(set, RowIndex, Cols, {empTable, mnesia});
emp_table(Op, RowIndex, Cols) ->
    snmp_generic:table_func(Op, RowIndex, Cols, {empTable, mnesia}).

預設的儀器功能定義在 snmp_generic 模組中。詳細資訊請參閱參考手冊的 SNMP 章節中的 snmp_generic 模組。

擴展 Mnesia 表格

表格可能包含內部使用的欄位,但不應對管理員可見。這些內部欄位必須是表格中的最後幾個欄位。set 操作不會使用此配置,因為存在 Agent 不知道的欄位。這種情況可以透過在 set 函數中新增內部欄位的值來處理。

為了說明這一點,假設我們用一個內部欄位擴展我們的 Mnesia empTable。我們像以前一樣建立它,但透過新增另一個屬性,使其元數為 4。

mnesia:create_table([{name, employees},
                     {snmp, [{key, {integer, string}}]},
                     {attributes, {key, telno, row_status, internal_col}}]).

最後一個欄位是內部欄位。當執行 set 操作 (建立列) 時,我們必須為內部欄位提供一個值。儀器功能現在會如下所示:

-define(createAndGo, 4).
-define(createAndWait, 5).

emp_table(set, RowIndex, Cols) ->
  notify_internal_resources(RowIndex, Cols),
  NewCols =
    case is_row_created(empTable, Cols) of
      true -> Cols ++ [{4, "internal"}]; % add internal column
      false -> Cols                      % keep original cols
  end,
  snmp_generic:table_func(set, RowIndex, NewCols, {empTable, mnesia});
emp_table(Op, RowIndex, Cols) ->
  snmp_generic:table_func(Op, RowIndex, Cols, {empTable, mnesia}).

is_row_created(Name, Cols) ->
  case snmp_generic:get_status_col(Name, Cols) of
    {ok, ?createAndGo} -> true;
    {ok, ?createAndWait} -> true;
    _ -> false
  end.

如果建立了一個列,我們總是將內部欄位設定為 "internal"

與標準的偏差

在某些方面,Agent 並未完全實作 SNMP。以下是差異之處:

  • 預設功能和 snmp_generic 無法將 NetworkAddress 類型的物件作為 INDEX (僅限 SNMPv1!) 處理。請改用 IpAddress
  • Agent 不會檢查為 INTEGER 物件指定的複雜範圍。在這些情況下,它只會檢查該值是否在指定的最小值和最大值範圍內。例如,如果範圍指定為 1..10 | 12..20,Agent 會允許 11 通過,但不允許 0 或 21。儀器功能必須自行檢查複雜範圍。
  • Agent 永遠不會產生 wrongEncoding 錯誤。如果變數綁定編碼錯誤,則會遞增 asn1ParseError 計數器。
  • SNMPv1 封包中的 tooBig 錯誤永遠會在所有變數綁定中使用 'NULL' 值。
  • 預設函數和 snmp_generic 不會檢查從 OCTET STRING 衍生之文字慣例中每個 OCTET 的範圍,例如 DisplayStringDateAndTime。這必須在重載的 is_set_ok 函數中檢查。