1. 程式人生 > >bro框架-- 輸入框架

bro框架-- 輸入框架

原文地址:

輸入框架(Input Framework

Bro提供了輸入框架,讓使用者可以匯入資料到Bro中去。資料可以讀到Bro表中,或者轉換為指令碼可以處理的事件。

讀取資料到表中

輸入框架的最有趣的用例是讀資料到一個Bro表中。預設情況下,輸入框架讀取的資料和由日誌框架在Bro中寫的資料是同樣的格式(由tab分隔的ASCII檔案)。

我們將用一個簡單的例子說明將檔案讀入Bro的集中方法。我們假設我們想從一個包含伺服器IP地址、時間戳以及塊(block)的reason的黑名單(blacklist)最近中匯入資料。

一個示例的輸入檔案如下所示(注意所有的區域都是用tab分隔的):

#fields ip timestamp reason

192.168.17.1 1333252748 Malware host

192.168.27.2 1330235733 Botnet server

192.168.250.3 1333145108 Virus detected

為了將一個檔案讀到Bro表中,需要定義兩種record型別。一個包含構成表鍵的列的型別和名稱,另一個包含構成表值的列的型別和名稱。

在我們的例子中,我們想查詢IP。因此,我們的key record僅包含伺服器IP,所有其他的元素都作為表內容來儲存。

兩個record定義如下:

type Idx: record {

ip: addr;

};

type Val: record {

timestamp: time;

reason: string;

};

注意上面record定義中的域的名稱必須和日誌檔案中‘#fields’行中定義的域相同,在本例中就是ip,timestamp,reason。另外注意到列的順序是無關緊要的,因為每一個列都是由它的名稱來標識的。

可以通過簡單地呼叫Input::add_table方法來將日誌檔案讀入表中:

global blacklist: table[addr] of Val = table();

event bro_init() {
    Input::add_table([$source="blacklist.file", $name="blacklist",
                      $idx=Idx, $val=Val, $destination=blacklist]);
    Input::remove("blacklist");
}

這裡我們先建立一個空的表(用於存放blacklist中的資料)並用輸入框架開啟一個名為blacklist的輸入流將資料讀入到表中。最後又將輸入流移除了,這是因為我們在資料讀完之後不再需要它了。

因為有一些資料檔案可能會非常大,所以輸入框架是非同步地工作的。為每個輸入流建立一個新的執行緒,這個執行緒開啟輸入資料檔案,將資料轉化為Bro格式並將它發回給Bro主執行緒。

因此,資料不是立即可得的。在所有資料展示在表中之前可能要花費幾毫秒到幾秒不等的時間來將資料從資料來源中取出,具體時間依賴於資料來源的大小。也就是說當Bro在沒有輸入源或者是工作在非常小的抓取檔案上的時候,它可能在資料展示在表中之前就已經終止了(因為Bro在匯入執行緒結束之前就已經處理完所有的包了)。

之後順序到來的對輸入源的呼叫會進入佇列直到前面的工作都已經完成。比如,呼叫了add_table和remove,在相繼的兩行中,remove在第一個read完成之前一直呆在佇列中。

一旦輸入框架結束從一個數據源中讀取資料,它將觸發Input::end_of_data事件。一旦這個時間已經獲取了表中可獲取的所有輸入檔案中的資料的時候。。

event Input::end_of_data(name: string, source: string) {
        # now all data is in the table
        print blacklist;
}

只要這個資料還在被讀,這個表就繼續使用。這可能在事件觸發之前沒能包含輸入檔案中的所有行。填好之後就可以像這樣。。

if ( 192.168.18.12 in blacklist )
        # take action

重讀和流資料(Re-reading and streaming data)

對於很多資料來源,比如很多blacklists,源資料是不斷變化的。對這些情況,Bro輸入框架支援處理變化的資料檔案(changing data files)。

首先,最基礎的方法就是重新整理輸入流。當輸入流開啟的時候(也就是說還沒有通過呼叫Input::remove來去除它的時候),可以呼叫函式Input::force_update。這會觸發對錶的一次完整的重新整理,任何的檔案中變化了的元素都會被更新。在更新完成的時候,會觸發Input::end_of_data事件。

在我們的例子中,是這樣呼叫的:

Input::force_update("blacklist");

輸入框架可以在它探測到輸入檔案變化的時候自動重新整理表的內容。為了使用這個特性,你需要通過設定Input::add_table呼叫的mode選項指定一個non-default read模式。有效的值有:Input::MANUAL(預設的),Input::REREAD和Input::STREAM。就可以這樣:

Input::add_table([$source="blacklist.file", $name="blacklist",
                  $idx=Idx, $val=Val, $destination=blacklist,
                  $mode=Input::REREAD]);

當使用reread模式的時候(如 $mode=Input::REREAD),Bro會持續地檢查輸入檔案是否已經發生變化。如果這個檔案已經發生變化,為了反映當前狀態,會重讀這個檔案並將Bro表中的資料更新。每一次探測到變化並且新資料被讀到表中的時候,會觸發end_of_data事件。

當使用streaming模式的時候(如 $mode=Input::STREAM),Bro假設源資料是僅能在末尾追加的檔案(append-only file),新資料將在其末尾追加。Bro不斷地在檔案末尾檢查新資料,然後把新資料新增到表中。如果檔案中的新的行(newer lines)和前面的行有相同的索引(index),它們將覆蓋輸出表中的值。因為流讀取的特性(資料不斷地往表中追加),所以當使用streaming模式的時候永遠都不會觸發end_of_data事件。

接收變化事件(receiving change events)

當重讀(re-reading)檔案的時候,能夠知道原始檔中的哪些行發生了變化該多好啊。
出於這個原因,當有一個新的資料項在表中新增、移除或者更改的時候,輸入框架會觸發一個事件。

事件就像這樣(注意你可以在自己的Bro指令碼中改變事件的名稱):

event entry(description: Input::TableDescription, tpe: Input::Event,
            left: Idx, right: Val) {
        # do something here...
        print fmt("%s = %s", left, right);
}

事件必須在add_table呼叫的$ev中指明:

Input::add_table([$source="blacklist.file", $name="blacklist",
                  $idx=Idx, $val=Val, $destination=blacklist,
                  $mode=Input::REREAD, $ev=entry]);

這裡事件的description引數包含那些一開始應用給add_table呼叫的引數。因此,流的名字可以這樣獲取(description$name)。事件的tpe引數是一個包含發生了的變化的型別的一個enum型資料。

如果一個未曾出現在表中的一行被新增到了表中,tpe的值會變成Input::EVENT_NEW。在這個例子中left包含了已新增的表項的索引,right包含了已新增的表項的值。

如果在對一個檔案進行重讀或者流讀取的時候,表中的一個已經存在的表項被修改,tpe的值會變成Input::EVENT_CHANGED。在這個情況下,left包含被修改的表項的索引,right包含表項被修改之前的值。原因是,當事件觸發的時候,表已經被修改了。表中的當前值可以通過查詢表值來弄清楚。因此,把表的新值和舊值進行比較也是可能的。

如果通過一次重讀,表中的一個元素(本來存在的)不復存在了,tpe的值將變為Input::EVENT_REMOVED。在這個情況下,left包含索引並且right包含被移除元素的值。

在匯入過程中過濾資料(Filtering data during import)

輸入框架也允許使用者在匯入的過程中過濾資料。使用了斷定函式。斷定函式在新的元素被新增/改變/移除的時候呼叫。斷定函式可以通過對要接受的變化返回true值或對要拒絕的變化返回false值來接受抑或是否決一個變化。更多的是,它可以在資料寫入表之前就修改資料。

下面的例子會拒絕在表中新增項(那種在一個月之前就生成的項)。但它會接受對當前已經在表中的項的值的所有改變或者是移除。

Input::add_table([$source="blacklist.file", $name="blacklist",
                  $idx=Idx, $val=Val, $destination=blacklist,
                  $mode=Input::REREAD,
                  $pred(typ: Input::Event, left: Idx, right: Val) = {
                    if ( typ != Input::EVENT_NEW ) {
                        return T;
                    }
                    return (current_time() - right$timestamp) < 30day;
                  }]);

為了在元素匯入的時候修改它們,斷定函式(predicate function)可以操縱left和right。注意一下,斷定函式在變化提交給表之前就呼叫了。因此,當一個表元素變化的時候(typ為Input::EVENT_CHANGED),left和right包含了舊的值。這允許斷定函式在判斷這些變化是否應該被允許之前先檢驗一下舊版本和新版本之間的變化。

不同的讀者(different readers)

輸入框架對不同種類的源資料檔案智齒不同種類的讀者。在這個時候,預設的讀者讀Bro日誌檔案格式的ASCII檔案(有#fields頭部行且用tab分隔的值)。但也有其他的讀者。

raw讀者讀由指定記錄分隔符(預設是newline)分隔的檔案。內容以一行一行的形式返回,它可以用於讀配置檔案或者類似的檔案,可能僅在事件模式且不是往表中讀資料的時候才行。

二進位制讀者(binary reader)在讀檔案分析輸入流的時候使用的,在處理檔案分析輸入流(file analysis input streams)的時候預設就是用的二進位制讀者。

參照讀者(benchmark reader)被使用用來優化輸入框架的速度。它可以生成任意數量的所有bro資料型別的且輸入框架支援的半隨機資料。

讀取資料到事件中(reading data to events)

輸入框架支援的第二種模式是從讀資料到Bro事件中,而不是讀到表中。

事件流和表流的工作機制在很多已經討論過的地方非常相似。為了將前面例子中blacklist讀入一個事件流,我們使用了Input::add_event函式。

type Val: record {
        ip: addr;
        timestamp: time;
        reason: string;
};

event blacklistentry(description: Input::EventDescription,
                     t: Input::Event, data: Val) {
        # do something here...
        print "data:", data;
}

event bro_init() {
        Input::add_event([$source="blacklist.file", $name="blacklist",
                          $fields=Val, $ev=blacklistentry]);
}
事件流的宣告中,主要不同的地方是:事件流不需要單獨的index和value宣告,所有的資料型別都在record定義中提供了。

此外,事件流工作和表流極其相似且提供了絕大多數表流支援的選項。