suricata學習

suricata簡介

Suricata是一個高性能的網絡IDS,IPS和網絡安全監控引擎。

IPS:入侵預防系統(IPS: Intrusion Prevention System)是電腦網絡安全設施,是對防病毒軟件(Antivirus Programs)和防火牆(Packet Filter, Application Gateway)的補充。 入侵預防系統(Intrusion-prevention system)是一部能夠監視網絡或網絡設備的網絡資料傳輸行爲的計算機網絡安全設備,能夠即時的中斷、調整或隔離一些不正常或是具有傷害性的網絡資料傳輸行爲。是新一代的侵入檢測系統(IDS)。

 

Suricata是一個網絡入侵檢測防護引擎,由開放信息安全基金會及其支持的廠商開發。該引擎是多線程的,內置支持IPV6。可加載現有的Snort規則和簽名,支持Barnyard  Barnyard2 工具.

 

IDS英文“Intrusion Detection Systems”的縮寫,中文意思是“入侵檢測系統”。依照一定的安全策略,通過軟、硬件,對網絡、系統的運行狀況進行監視,儘可能發現各種攻擊企圖、攻擊行爲或者攻擊結果,以保證網絡系統資源的機密性、完整性和可用性。

 

Barnyard知名的開源IDS的日誌工具,具有快速的響應速度,優異的數據庫寫入功能,是做自定義的入侵檢測系統不可缺少的插件。


至於IDS和IPS的區別,可以查看下網絡中其他文章,本人理解:

IDS只是發現攻擊、產生報警,而IPS不但可以發現攻擊,更重要的是針對攻擊採取行動。


<suricata編譯安裝>

想到研究suricata只讀源碼估計還不湊效,需要了解下真實環境下怎麼應用,這樣理解起來估計會更有感覺,於是在自己本地虛擬機中安裝編譯一下:

1、虛擬機操作系統Linux centos5.0
2、下載suricata源碼
   下載yaml庫,主要用於配置文件操作

   由於自己本機原來安裝配置過snort,所以suricata+yaml庫已經可以正常運行。
   否則需要安裝:libdnet-1.12.tgz、libpcap-1.1.1.tar.gz、pcre-8.32.tar.gz等庫。

3、安裝yaml庫
   解壓:tar -zxvf yaml-0.1.4.tar.gz到特定目錄
   ./configure
   make
   make install
   
4、安裝suricata
   解碼:tar -zxvf suricata-1.4.7.tar.gz 到特定目錄
   ./configure
   make 
   make install
5、以上完成編譯安裝,接下來需要更改配置
   suricata默認安裝路徑:/usr/local/bin
   創建默認配置目錄:mkdir -p /usr/local/etc/suricata
   拷貝配置文件:cp 源碼目錄中 suricata.yaml /usr/local/etc/suricata
                 cp 源碼目錄中 reference.config /usr/local/etc/suricata
                 cp 源碼目錄中 classification.config /usr/local/etc/suricata
                 cp 源碼目錄中 threshold.config /usr/local/etc/suricata
   創建默認日誌目錄:mkdir -p /usr/local/var/log/suricata
6、下載規則庫
   解壓到/usr/local/etc/suricata目錄下
  
7、默認啓動:./suricata -i eth0 (eth0需要根據本地網卡配置)
   這時suricata正常啓動,如下圖所示:
   

當只執行./suricata時顯示如下:


=================================================================================
以上是編譯安裝的整個過程,爲了更好的理解suricata的功能,自己做了一個簡單的測試:
1、自己編寫一條規則,規則書寫參考snort規則(suricata完全兼容snort規則)
   例如以百度網站爲例:
   alert http any any -> any any (msg:"hit baidu.com..."; reference:url, www.baidu.com;)
   將文件命名爲test.rules,存放在目錄/usr/local/etc/suricata下(直接存放在該目錄下rules裏面也可以,這裏爲了說明,自己單獨存放)。
2、啓動suricata 
  ./suricata  -s /usr/local/etc/suricata/test.rules -i eth0

3、打開虛擬機中瀏覽器,訪問www.baidu.com
   此時,查看log文件/user/local/var/log/suricata/目錄下
   fast.log顯示數據包匹配的條數
   http.log (目前不清楚內容含義)
   unified2.alert.13XXXXXXXX (目前不清楚內容含義)

目前只研究到這裏,內容等待後續完善。

suricata源碼閱讀main函數開始

從官方網站上查看到資料,目前最新的穩定版本爲1.4.7,測試版本最新爲2.0beta2
http://www.openinfosecfoundation.org/download/suricata-2.0beta2.tar.gz
suricata目前正處在開發階段,版本更新的比較頻繁,爲此選擇一個穩定版本來分析。網絡中中文資料相對較少,介於自己英文水平soso,還是決定直接閱讀源碼。
開源中國中blog文章可以拿來參考:
http://my.oschina.net/openadrian/blog/184621

目前分析版本suricata-1.4.7:
suricata.c文件中包含程序主函數main(),該函數一共有1K多行代碼,閱讀起來真是費勁,難道作者就不能封裝下嗎?我表示費勁…… 廢話少說,直接深入。
1、開頭定義的變量直接略過。。。

2、 sc_set_caps = FALSE; 
    int sc_set_caps;
    #define TRUE   1
    #define FALSE  0
    標識是否對主線程進行特權去除(drop privilege),主要是出於安全性考慮。
       (目前自己還不知道是什麼作用,出自上面的文章)

3、 SC_ATOMIC_INIT(engine_stage);
    初始化原子變量engine_stage –> 記錄程序當前的運行階段:SURICATA_INIT、SURICATA_RUNTIME、SURICATA_FINALIZE。
    #define SC_ATOMIC_INIT(name) \
        (name ## _sc_atomic__) = 0
     上述這個宏定義中的操作時什麼意思呢?目前不解,後期再詢問。

4、 SCLogInitLogModule(NULL);
    初始化日誌模塊,因爲後續的執行流程中將使用日誌輸出,所以需要最先初始化該模塊。

5、SCSetThreadName("Suricata-Main") 
    設置當前主線程名字爲“Suricata-Main”。線程名字還是挺重要的,至少在gdb調試時info threads可以看到各個線程名,從而可以精確地找到想要查看的線程。另外,在top -H時,也能夠顯示出線程名字(然而ps -efL時貌似還是隻是顯示進程名)。

6、 RunModeRegisterRunModes();具體詳見本人另一篇《suricata中模式概念詳解 》
   註冊各種運行模式。Suricata對“運行模式”這個概念也進行了封裝。運行模式存儲在runmodes數組中,定義爲RunModes runmodes[RUNMODE_USER_MAX]。
首先,數組中每一項(例如runmodes[RUNMODE_PCAP_DEV]),對應一組運行模式,模式組包括(RunModes類型):“IDS+Pcap”模式組、“File+Pcap”模式組、“UnixSocket”模式組等(另外還有其他一些內部模式,如:“列出關鍵字”模式、“打印版本號”模式等,這些沒有存儲在runmodes數組中)。
然後,每一個模式組,其中可以包含若干個運行模式(RunMode類型),例如:single、auto、autofp、workers。
運行模式的註冊,則是爲各個模式組(如RunModeIdsPcapRegister)添加其所支持的運行模式(通過調用RunModeRegisterNewRunMode),並定義改組的默認運行模式,以及非常重要的:註冊各個模式下的初始化函數(如RunModeIdsPcapSingle),等後續初始化階段確定了具體的運行模式後,就會調用這裏註冊的對應的初始化函數,對該模式下的運行環境進行進一步配置。

7、 SET_ENGINE_MODE_IDS(engine_mode);
   初始化引擎模式爲IDS模式。引擎模式只有兩種:IDS、IPS,初始默認爲IDS,而在nfq或ipfw啓用時,就會切換成IPS模式,該模式下能夠執行“Drop”操作,即攔截數據包。

8、 ConfInit();
   初始化配置模塊。爲配置節點樹建立root節點。

9、以下是命令行參數解析,具體調用getopt_long()函數實現(函數具體實現參加本人另外一篇博文《getopt_long()解析命令行選項參數》)。感覺這裏是理解的一個重點地方,首先理解命令行都有哪些參數,每個參數的大概作用是什麼,進而調用的函數關係如何,所以本人提取在網上查找了下suricata命令行參數的文章閱讀了一下,具體參加本人博文《suricata命令行》。
   這裏的代碼很長:
   while ((opt = getopt_long(argc, argv, short_opts, long_opts, &option_index)) != -1) {
   ……
// 這個裏面主要是解析命令行參數,解析完後操作
        pcap_dev = "eth0";           // 這裏是啓動時參數,例如:./suricata -i eth0     
        run_mode = RUNMODE_PCAP_DEV; // 設置運行模式
        LiveRegisterDevice(pcap_dev); // 添加pcap設備監控,作用目前還不清楚
   }
   其中,與包捕獲相關的選項(如“-i”)都會調用LiveRegisterDevice,以註冊一個數據包捕獲設備接口(如eth0)。全局的所有已註冊的設備接口存儲在變量live_devices中,類型爲LiveDevice。注意,用多設備同時捕獲數據包這個特性在Suricata中目前還只是實驗性的。“-v”選項可多次使用,每個v都能將當前日誌等級提升一級。
   SetBpfString(optind, argv); // 解析BPF過濾器
10、 UtilCpuPrintSummary();// 打印cpu信息

11、 CheckValidDaemonModes(daemon, run_mode)
若運行模式爲內部模式daemon,則進入該模式執行,完畢後退出程序。

12、初始化全局變量、隊列、時間等

13、 SupportFastPatternForSigMatchTypes();
爲快速模式匹配註冊關鍵字。調用SupportFastPatternForSigMatchList函數,按照優先級大小插入到sm_fp_support_smlist_list鏈表中。

14、MpmTableSetup(); // 設置多模式匹配表
該表中每一項就是一個實現了某種多模式匹配算法(如WuManber、AC)的匹配器。以註冊AC匹配器爲例,MpmTableSetup會調用MpmACRegister函數實現AC註冊,函數內部其實只是填充mpm_table中對應AC的那一項(mpm_table[MPM_AC])的各個字段,如:匹配器名稱("ac")、初始化函數(SCACInitCtx)、增加模式函數(SCACAddPatternCS)、實際的搜索執行函數(SCACSearch)。

15、如果用戶沒有設置配置文件,使用默認配置文件suricata.yaml

16、加載配置文件yaml格式
調用LoadYamlConfig讀取Yaml格式配置文件。Yaml格式解析是通過libyaml庫來完成的,解析的結果存儲在配置節點樹(見conf.c)中。對include機制的支持:在第一遍調用ConfYamlLoadFile載入主配置文件後,將在當前配置節點樹中搜尋“include”節點,並對其每個子節點的值(即通過include語句所指定的子配置文件路徑),同樣調用ConfYamlLoadFile進行載入。

17、AppLayerDetectProtoThreadInit();
初始化應用層協議檢測模塊。其中,AlpProtoInit函數初始化該模塊所用到的多模式匹配器,RegisterAppLayerParsers函數註冊各種應用層協議的解析器(如RegisterHTPParsers函數對應HTTP協議),而AlpProtoFinalizeGlobal函數完成一些收尾工作,包括調用匹配器的預處理(Prepare)函數、建立模式ID和規則簽名之間的映射等。

18、 AppLayerParsersInitPostProcess();// 建立了一個解析器之間的映射

19、設置並驗證日誌目錄是否存在

20、 獲取與包捕獲相關的一些配置參數

21、SCHInfoLoadFromConfig()
從配置文件中載入host os policy(主機OS策略)信息。網絡入侵通常是針對某些特定OS的漏洞,因此如果能夠獲取部署環境中主機的OS信息,肯定對入侵檢測大有裨益。具體這些信息是怎麼使用的,暫時也還不清楚。

22、DefragInit()
初始化IP分片重組模塊。

23、SigTableSetu()
初始化檢測引擎,主要是註冊檢測引擎所支持的規則格式(跟Snort規則基本一致)中的關鍵字,比如sid、priority、msg、within、distance等等。

24、TmqhSetup()
初始化queue handler(隊列處理函數),這個是銜接線程模塊和數據包隊列之間的橋樑,目前共有5類handler:simple, nfq, packetpool, flow, ringbuffer。每類handler內部都有一個InHandler和OutHandler,一個用於從上一級隊列中獲取數據包,另一個用於處理完畢後將數據包送入下一級隊列。

25、接下來幾個函數
StorageInit:初始化存儲模塊,這個模塊可以用來臨時存儲一些數據,數據類型目前有兩種:host、flow。具體在何種場景下用,目前未知。
CIDRInit:初始化CIDR掩碼數組,cidrs[i]對應前i位爲1的掩碼。
SigParsePrepare:爲規則簽名解析器的正則表達式進行編譯(pcre_compile)和預處理(pcre_study)。
SCPerfInitCounterApi:初始化性能計數器模塊。這個模塊實現了累加計數器(例如統計收到的數據包個數、字節數)、平均值計數器(統計平均包長、處理時間)、最大計數器(最大包長、處理時間)、基於時間間隔的計數器(當前流量速率)等,默認輸出到日誌目錄下的stats.log文件。
幾個Profiling模塊的初始化函數。Profiling模塊提供內建的模塊性能分析功能,可以用來分析模塊性能、各種鎖的實際使用情況(競爭時間)、規則的性能等。
SCReputationInitCtx:初始化IP聲望模塊。IP聲望數據在內部是以Radix tree的形式存儲的,但目前還不知道數據源是從哪來的,而且也沒看到這個模塊的函數在哪調用。
SCProtoNameInit:讀取/etc/protocols文件,建立IP層所承載的上層協議號和協議名的映射(如6-> ”TCP”,17-> ”UDP“)。
TagInitCtx、ThresholdInit:與規則中的tag、threshould關鍵字的實現相關,這裏用到了Storage模塊,調用HostStorageRegister和FlowStorageRegister註冊了幾個(與流/主機綁定的?)存儲區域。
DetectAddressTestConfVars、DetectPortTestConfVars:檢查配置文件中"vars"選項下所預定義的一些IP地址(如局域網地址塊)、端口變量(如HTTP端口號)是否符合格式要求。

26、註冊模塊
註冊Suricata所支持的所有線程模塊(Thread Module)。
以pcap相關模塊爲例,TmModuleReceivePcapRegister函數註冊了Pcap捕獲模塊,而TmModuleDecodePcapRegister函數註冊了Pcap數據包解碼模塊。所謂註冊,就是在tmm_modules模塊數組中對應的那項中填充TmModule結構的所有字段,這些字段包括:模塊名字、線程初始化函數、包處理或包獲取函數、線程退出清理函數、一些標誌位等等。

27、 AppLayerHtpNeedFileInspection()
設置suricata內部模塊與libhtp(HTTP處理庫)對接關係的函數,具體細節暫時不管。

28、DetectEngineRegisterAppInspectionEngines()
作用不詳

29、註冊規則加載信號函數
若設置了rule_reload標誌,則註冊相應的信號處理函數(目前設置的函數都是些提示函數,沒有做實際重載)。這裏用的是比較慣用的SIGUSR2信號來觸發rule reload。

30、TmModuleRunInit()
調用之前註冊的線程模塊的初始化函數進行初始化。

31、檢查是否進入Daemon模式
若需要進入Daemon模式,則會檢測pidfile是否已經存在(daemon下只能有一個實例運行),然後進行Daemonize,最後創建一個pidfile。Daemonize的主要思路是:fork->子進程調用setsid創建一個新的session,關閉stdin、stdout、stderr,並告訴父進程 –> 父進程等待子進程通知,然後退出 –> 子進程繼續執行。

32、註冊信號
    UtilSignalHandlerSetup(SIGINT, SignalHandlerSigint);
    UtilSignalHandlerSetup(SIGTERM, SignalHandlerSigterm);
    UtilSignalHandlerSetup(SIGPIPE, SIG_IGN);
    UtilSignalHandlerSetup(SIGSYS, SIG_IGN);
首先爲SIGINT(ctrl-c觸發)和SIGTERM(不帶參數kill時觸發)這兩個常規退出信號分別註冊handler,對SIGINT的處理是設置程序的狀態標誌爲STOP,即讓程序優雅地退出;而對SIGTERM是設置爲KILL,即強殺。接着,程序會忽略SIGPIPE(這個信號通常是在Socket通信時向已關閉的連接另一端發送數據時收到)和SIGSYS(當進程嘗試執行一個不存在的系統調用時收到)信號,以加強程序的容錯性和健壯性。

33、獲取配置文件中指定的Suricata運行時的user和group
如果命令行中沒有指定的話。然後,將指定的user和group通過getpwuid、getpwnam、getgrnam等函數轉換爲uid和gid,爲後續的實際設置uid和gid做準備。

34、PacketPoolInit(max_pending_packets)
初始化Packet pool,即預分配一些Packet結構體,分配的數目由之前配置的max_pending_packets確定,而數據包的數據大小由default_packet_size確定(一個包的總佔用空間爲default_packet_size+sizeof(Packet))。在調用PacketGetFromAlloc新建並初始化一個數據包後,再調用PacketPoolStorePacket將該數據包存入ringbuffer。Suricata中用於數據包池的Ring Buffer類型爲RingBuffer16,即容量爲2^16=65536(但爲什麼max_pending_packets的最大值被限定爲65534呢?)。

35、HostInitConfig(HOST_VERBOSE)

36、 FlowInitConfig(FLOW_VERBOSE)
初始化Flow engine。跟前面的host engine類似,不過這個的用處就很明顯了,就是用來表示一條TCP/UDP/ICMP/SCTP流的,程序當前所記錄的所有流便組成了流表,在flow引擎中,流表爲flow_hash這個全局變量,其類型爲FlowBucket *,而FlowBucket中則能夠存儲一個Flow鏈表,典型的一張chained hash Table。在初始化函數FlowInitConfig中,首先會使用配置文件信息填充flow_config,然後會按照配置中的hash_size爲流表實際分配內存,接着按照prealloc進行流的預分配(FlowAlloc->FlowEnqueue,存儲在flow_spare_q這個FlowQueue類型的隊列中),最後調用FlowInitFlowProto爲流表所用於的各種流協議進行配置,主要是設置timeout時間。

37、DetectEngineCtxInit()
初始化Decect engine。若配置文件中未指定mpm(多模式匹配器),則默認使用AC,即使用mpm_table中AC那一項。SRepInit函數(與前面的SCReputationInitCtx不同!)會初始化檢測引擎中域reputaion相關信息,即從配置文件中指定的文件中讀取聲望數據。其餘配置比較複雜,暫不關注。

38、讀取和解析classification.config和reference.config文件
SCClassConfLoadClassficationConfigFile(de_ctx);
SCRConfLoadReferenceConfigFile(de_ctx);
這兩個文件用於支持規則格式中的classification(規則分類)和refercence(規則參考資料)字段。

39、ActionInitConfig()
設置規則的動作優先級順序,默認爲Pass->Drop->Reject->Alert。舉例來說,若有一條Pass規則和Drop規則都匹配到了某個數據庫,則會優先應用Pass規則。

40、MagicInit()
初始化Magic模塊。Magic模塊只是對libmagic庫進行了一層封裝,通過文件中的magic字段來檢測文件的類型(如”PDF-1.3“對應PDF文件)。

41、接下來貌似和延遲有關
設置是否延遲檢測。若delayed-detect爲yes,則系統將在載入規則集之前就開始處理數據包,這樣能夠在IPS模式下將少系統的down time(宕機時間)。
如果沒有設置延遲檢測,就調用LoadSignatures載入規則集。
如果設置了live_reload,則重新註冊用於規則重載的SIGUSR2信號處理函數(這次是設置爲真正的重載處理函數)。放在這裏是爲了防止在初次載入規則集時就被觸發重載。

41、SCAsn1LoadConfig()
初始化ASN.1解碼模塊。Wikipedia:ASN.1(Abstract Syntax Notation One) 是一套標準,是描述數據的表示、編碼、傳輸、解碼的靈活的記法。應用層協議如X.400(email)、X.500和LDAP(目錄服務)、H.323(VoIP)和SNMP使用 ASN.1 描述它們交互的協議數據單元。

42、 CoredumpLoadConfig()
處理CoreDump相關配置。Linux下可用prctl函數獲取和設置進程dumpable狀態,設置corefile大小則是通過通用的setrlimit函數。

43、gettimeofday(&start_time, NULL)
調用gettimeofday保存當前時間,存儲在suri->start_time中,作爲系統的啓動時間。

44、SCDropMainThreadCaps(userid, groupid)
去除主線程的權限。這個是通過libcap-ng實現的,首先調用capng_clear清空所有權限,然後根據運行模式添加一些必要權限(主要是爲了抓包),最後調用capng_change_id設置新的uid和gid。主線程的權限應該會被新建的子線程繼承,因此只需要在主線程設置即可。

45、RunModeInitializeOutputs()
初始化所有Output模塊。這些模塊之前在線程模塊註冊函數裏已經都註冊了,這裏會根據配置文件再進行配置和初始化,最後把當前配置下啓用了的output模塊放到RunModeOutputs鏈表中。

46、若當前抓包模式下未指定設備接口(通過-i 或--pcap=等方式),則解析配置文件中指定的Interface,並調用LiveRegisterDevice對其進行註冊。

47、if(conf_test == 1)
若當前的模式爲CONF_TEST,即測試配置文件是否有效,則現在就可以退出了。這也說明,程序運行到這裏,配置工作已經基本完成了。

48、RunModeDispatch(run_mode, runmode_custom_mode, de_ctx)
初始化運行模式。首先,根據配置文件和程序中的默認值來配置運行模式(single、auto這些),而運行模式類型(PCAP_DEV、PCAPFILE這些)也在之前已經確定了,因此運行模式已經固定下來,可以從runmodes表中獲取到特定的RunMode了,接着就調用RunMode中的RunModeFunc,進入當前運行模式的初始化函數。以PCAP_DEV類型下的autofp模式爲例,該模式的初始化函數爲:RunModeIdsPcapAutoFp。這個函數的執行流程爲:
調用RunModeInitialize進行通用的運行模式初始化,目前主要是設置CPU affinity和threading_detect_ratio。
調用RunModeSetLiveCaptureAutoFp設置該模式下的模塊組合:
確實參數:接口個數nlive、線程個數thread_max(由用戶指定,或CPU個數決定)。
RunmodeAutoFpCreatePickupQueuesString:創建一個包含thread_max個接收隊列名字的字符串,如"pickup1,pickup2,pickup3"。
ParsePcapConfig:解析pcap接口相關配置,如buffer-size、bpf-filter、promisc等。
PcapConfigGeThreadsCount:獲取pcap接口配置中指定的threads(抓包線程個數,默認爲1),保存到threads_count變量。
創造threads_count個抓包線程:
TmThreadCreatePacketHandler函數專門用於創建包處理線程,函數內部會調用通用的TmThreadCreate創建線程,並將線程類型設置爲TVT_PPT。
線程名字爲"RxPcap"+接口名+i,如“RxPcapeth01”。
inq、inqh都設置爲"packetpool",表示將從數據包池(而不是某個數據包隊列)中獲取包。
outqh設置爲"flow",表示使用之前註冊的flow類型的queue handler作爲線程的輸出隊列處理器,這個類型可以保證同一條flow的包都會輸出給同一個queue,具體的包調度策略取決於autop-scheduler指定的算法。
outq設置爲前面所設置的接收隊列名字符串,而之前的flow類型handler的TmqhOutputFlowSetupCtx函數將會解析隊列名字符串,並創建出相應個數(threads_max)的隊列。
slots函數設置爲"pktacqloop",表示這個線程的插槽類型爲pktacqloop,這樣在TmThreadSetSlots函數中就會將線程執行函數(tm_func)設置爲針對該插槽類型的TmThreadsSlotPktAcqLoop函數。最終線程在被pthread_create執行時傳入的回調函數就是這個線程執行函數。
TmSlotSetFuncAppend:將“ReceivePcap"和"DecodePcap"這兩個線程模塊嵌入到前面創建的每個抓包線程的插槽中去。
TmThreadSetCPU:設置線程的CPU相關參數。
TmThreadSpawn:按照之前所填充好的ThreadVars生成實際的線程,並將該線程添加到全局線程數組tv_root中去。
創造thread_max個檢測線程:
線程名字爲"Detect"+i,每個線程都有與一個輸入隊列綁定,即inq設置爲"pickup"+i 隊列。
inqh設置爲"flow",即使用flow類型(與前面的抓包線程相匹配)的queue handler作爲線程的輸入隊列處理器。
outq、outqh都設置爲"packetpool",表示這個線程的包處理完後會直接回收到packet pool中去。
slots函數設置爲"varslot",表示這個線程的插槽類型爲varslot,對應的執行函數爲TmThreadsSlotVar。
接着,跟上面類似,把"StreamTcp"(用於TCP會話跟蹤、重組等)、"Detect"(調用檢測引擎進行實際的入侵檢測)和"RespondReject"(用於通過主動應答來拒絕連接)這三個線程模塊嵌入進去。不過,這裏在插入“Detect”模塊時,調用的是TmSlotSetFuncAppendDelayed,用於支持delayed-detect功能。
SetupOutputs:由於這組檢測線程是處理數據包的完結之處,因此這裏需要把輸出模塊也嵌入到這些線程中去,方式也是通過TmSlotSetFuncAppend函數,對象是RunModeOutputs中存儲的輸出模塊。

49、若unix-command爲enable狀態,則創建Unix-socket命令線程,可與suricata客戶端使用JSON格式信息進行通信。命令線程的創建是通過TmThreadCreateCmdThread函數,創建的線程類型爲TVT_CMD。線程執行函數爲UnixManagerThread。

50、FlowManagerThreadSpawn()
創建Flow管理線程,用於對流表進行超時刪除處理。管理線程創建是通過TmThreadCreateMgmtThread函數,類型爲TVT_MGMT,執行函數爲FlowManagerThread。

51、StreamTcpInitConfig(STREAM_VERBOSE)
初始化Stream TCP模塊。其中調用了StreamTcpReassembleInit函數進行重組模塊初始化。

52、 SCPerfSpawnThreads()
創建性能計數相關線程,包括一個定期對各計數器進行同步的喚醒線程(SCPerfWakeupThread),和一個定期輸出計數值的管理線程(SCPerfMgmtThread)。

53、TmValidateQueueState()
檢查數據包隊列的狀態是否有效:每個數據包隊列都應該至少有一個reader和一個writer。在前面線程綁定inq時會增加其reader_cnt,綁定outq時會增加其writer_cnt。

54、TmThreadWaitOnThreadInit()
等待子線程初始化完成。檢查是否初始化完成的方式是遍歷tv_root,調用TmThreadsCheckFlag檢查子線程的狀態標誌。

55、SC_ATOMIC_CAS(&engine_stage, SURICATA_INIT, SURICATA_RUNTIME)
更新engine_stage爲SURICATA_RUNTIME,即程序已經初始化完成,進入運轉狀態。這裏的更新用的是原子CAS操作,防止併發更新導致狀態不一致(但目前沒在代碼中只到到主線程有更新engine_stage操作,不存在併發更新)。

56、TmThreadContinueThreads()
讓目前處於paused狀態的線程繼續執行。在TmThreadCreate中,線程的初始狀態設置爲了PAUSE,因此初始化完成後就會等待主線程調用TmThreadContinue讓其繼續。從這以後,各線程就開始正式執行其主流程了。

57、if (delayed_detect)
若設置了delayed_detect,則現在開始調用LoadSignatures加載規則集,激活檢測線程,並註冊rule_reload信號處理函數。這裏,激活檢測線程是通過調用TmThreadActivateDummySlot函數,這個函數會將之前註冊的slot中的slotFunc替換爲實際操作函數,而不是原先在delayed_detect情況下設置的什麼都不做的TmDummyFunc。

58、進入死循環
 while(1) {
        if (suricata_ctl_flags & (SURICATA_KILL | SURICATA_STOP)) {
            SCLogInfo("Signal Received.  Stopping engine.");

            break;
        }

        TmThreadCheckThreadState();

        usleep(10* 1000);
    }
若受到引擎退出信號(SURICATA_KILL或SURICATA_STOP),則退出循環,執行後續退出操作,否則就調用TmThreadCheckThreadState檢查各線程的狀態,決定是否進行結束線程、重啓線程、終止程序等操作,然後usleep一會兒(1s),繼續循環。

59、退出階段
首先會更新engine_stage爲SURICATA_DEINIT,然後依次關閉Unix-socket線程、Flow管理線程。

停止包含抓包或解碼線程模塊的線程。這個是通過TmThreadDisableThreadsWithTMS實現,裏面會檢查每個線程的slots裏嵌入的線程模塊的flags中是否包含指定的flag(這裏是TM_FLAG_RECEIVE_TM或TM_FLAG_DECODE_TM),一個線程模塊的flags在註冊時就已經指定了。關閉是通過向線程發送KILL信號(設置線程變量的THV_KILL標誌)實現,收到該信號的線程會進入RUNNING_DONE狀態,然後等待主線程下一步發出DEINIT信號。

強制對仍有未處理的分段的流進行重組。

打印進程運行的總時間(elapsed time)。

在rule_reload開啓下,首先同樣調用TmThreadDisableThreadsWithTMS停止檢測線程。特別地,該函數對於inq不爲"packetpool"的線程(即該線程從一個PakcetQueue中獲取數據包),會等到inq中的數據包都處理完畢再關閉這個線程。然後,檢測是否reload正在進行,如果是則等待其完成,即不去打斷它。

殺死所有子線程。殺死線程的函數爲TmThreadKillThread,這個函數會同時向子線程發出KILL和DEINIT信號,然後等待子線程進入CLOSED狀態,之後,再調用線程的清理函數(InShutdownHandler)以及其對應的ouqh的清理函數(OutHandlerCtxFree),最後調用pthread_join等待子線程退出。

執行一大堆清理函數:清理性能計數模塊、關閉Flow engine、清理StreamTCP、關閉Host engine、清理HTP模塊並打印狀態、移除PID文件、關閉檢測引擎、清理應用層識別模塊、清理Tag環境、關閉所有輸出模塊,etc…

調用exit以engine_retval爲退出狀態終止程序。


suricata中模式概念詳解
截止目前,結合開源中國中“揹着筆記本流浪”的blog中文章,已經對main函數和數據包收取、解碼有了初步瞭解。其中遇到的困難和疑惑的地方記錄在這裏,便於今後學習和提升。
1、網絡編程知識經驗不足,suricata整個架構採用多線程編程的方式,如若不瞭解線程的用法,理解起來會存在一些難度。
2、底層網絡協議瞭解不夠,需要日後補充下知識,感覺僅限瞭解的層面即可,至少可以看懂。
3、在收包過程中需要熟悉libpcap庫的接口和用法。

今天根據前人的blog和源碼的研讀,對於數據包收取、解析等整個過程大致走了一遍,疑惑的地方記錄如下:
1、suricata中用到大量的註冊函數,至於其中精髓還不曾體會的到,目前只停留在表層能夠讀懂的階段。
2、一個slot概念(“槽”),貌似不理解這個會對閱讀代碼產生一定影響。自己目前感覺就是爲了註冊數據包處理流程中的函數,具體用法日後有體會了再補充。
3、現在最大的一個疑惑就是對於“模式”概念的迷惑,suricata中貌似運行模式中又會細分各種模式,廢話少說,還是停下來小結一下,便於自己理解。
● main()中調用RunModeRegisterRunModes()

● RunModeRegisterRunModes()函數中註冊各種運行模式
   RunModeIdsPcapRegister();      // IDS+pcap
   RunModeFilePcapRegister();     // File+pcap
   RunModeIdsPfringRegister();    // IDS+pfring
   RunModeIpsIPFWRegister();      // IPS+ipfw
   RunModeIpsNFQRegister();       // IPS+nfq
   RunModeErfFileRegister();      // erf+file
   RunModeErfDagRegister();       // erf+dag
   RunModeNapatechRegister();     // napatech
   RunModeIdsAFPRegister();       // IDS+AFP
   RunModeUnixSocketRegister();   // UnixSocket
  
   這些運行模式存儲在runmodes數組中,定義如下:
   static RunModes runmodes[RUNMODE_MAX];
  
   其中數組大小RUNMODE_MAX定義爲如下枚舉類型:
 
  enum {
    RUNMODE_UNKNOWN = 0,
    RUNMODE_PCAP_DEV,
    RUNMODE_PCAP_FILE,
    RUNMODE_PFRING,
    RUNMODE_NFQ,
    RUNMODE_IPFW,
    RUNMODE_ERF_FILE,
    RUNMODE_DAG,
    RUNMODE_AFP_DEV,
    RUNMODE_UNITTEST,
    RUNMODE_NAPATECH,
    RUNMODE_UNIX_SOCKET,
    RUNMODE_MAX,
  };
  
● 下面以pcap模式爲例子說明註冊操作內容
   RunModeIdsPcapRegister()函數註冊IDS+pcap模式,其餘模式類似。
   具體內容如下:
   RunModeRegisterNewRunMode(RUNMODE_PCAP_DEV, "single",
                            "Single threaded pcap live mode",
                            RunModeIdsPcapSingle);
   RunModeRegisterNewRunMode(RUNMODE_PCAP_DEV, "auto",
                            "Multi threaded pcap live mode",
                            RunModeIdsPcapAuto);
   RunModeRegisterNewRunMode(RUNMODE_PCAP_DEV, "autofp",
                            "Multi threaded pcap live mode.  Packets from "
                            "each flow are assigned to a single detect thread, "
                            "unlike "pcap_live_auto" where packets from "
                            "the same flow can be processed by any detect "
                            "thread",
                            RunModeIdsPcapAutoFp);
   RunModeRegisterNewRunMode(RUNMODE_PCAP_DEV, "workers",
                              "Workers pcap live mode, each thread does all"
                              " tasks from acquisition to logging",
                              RunModeIdsPcapWorkers);
   暫且不管這裏函數作用,但這裏可以看出運行模式又細分爲四類:
   single、auto、autofp、workers
  
● 這裏具體看一下RunModeRegisterNewRunMode()函數作用
   函數原型:
   void RunModeRegisterNewRunMode(int runmode, const char *name,
                                 const char *description,
                                 int (*RunModeFunc)(DetectEngineCtx *))
   函數作用:註冊一個新的運行模式
   函數參數:runmode是運行模式類型,對應於上面的枚舉類型定義,例如:RUNMODE_PCAP_DEV。
             name是每一種運行模式下面的自定義模式,爲每一種運行模式的主鍵。
             description是運行模式的描述。
             RunModeFunc是運行該模式的具體函數。
 
● 下面回過頭看一下RunModeIdsPcapRegister()函數中的四類自定義模式:
   single : 單線程pcap live模式
   auto   :多線程pcap live模式
   autofp :多線程pcap live模式,但每個流的數據包被分配到一個單一的detect線程(檢測線程)。
            不像auto模式中相同流的數據包會被分配到不同的detect線程處理。
   workers:workers模式,每個線程完成從接收到日誌記錄等操作。
 
● 看到這裏已經對運行模式基本瞭解,爲了進一步瞭解模式操作,接着深入
   存儲運行模式的結構體定義如下:
   typedef struct RunMode_ {
      int runmode;              // 對應RUNMODE_PCAP_DEV等
      const char *name;         // 對應autofp等
      const char *description;  // 描述
      int (*RunModeFunc)(DetectEngineCtx *); // 處理函數
   } RunMode;
  
   typedef struct RunModes_ {
      int no_of_runmodes;
      RunMode *runmodes;
   } RunModes;
  
● 接着具體看一下RunModeRegisterNewRunMode()函數的內容
   RunModeGetCustomMode(runmode, name);// 參數對應於(RUNMODE_PCAP_DEV, "autofp")
                                      // 函數作用貌似是判斷自定義模式是否已經註冊
  
   // 這裏意思是註冊一種類型,動態添加一塊內存。
   runmodes[runmode].runmodes = 
   SCRealloc(runmodes[runmode].runmodes,(runmodes[runmode].no_of_runmodes + 1) * sizeof(RunMode));
   // 指針數組用法
   RunMode *mode = &runmodes[runmode].runmodes[runmodes[runmode].no_of_runmodes];
   runmodes[runmode].no_of_runmodes++;

   // 以下是填充具體內容
   mode->runmode = runmode;
   mode->name = SCStrdup(name);
   mode->description = SCStrdup(description);
   mode->RunModeFunc = RunModeFunc;
  
● 到這裏爲止,已經對“模式”的概念基本瞭解
   suricata的運作模式是根據數據源的不同而區分,其中內部的自定義模式主要和程序架構有關,具體可以根據需求選擇。
  
● 還有一些概念性的東西不明白,例如運行模式中ipfw、dag、napatech等概念,有時間再補充吧!

結合以上知識,再看http://blog.csdn.net/firedb/article/details/7581853中《Surciata源碼分析之IpsNFQ模式》便很容易理解。

● 盜用鏈接中圖片
autofp模式:


auto模式:


worker模式:

  



線程、槽和模塊之間的關係

suricata中tv、slot和tm的關係必須要搞清楚,彙總如下:

tv:ThreadVars類型,線程。
slot:TmSlot類型,槽。
tm:TmModule類型,模塊。

下面必須要結合三者的定義,閱讀代碼的時候也關注下三者關係。
----------------------------------------
線程的定義:
typedef struct ThreadVars_ {
    pthread_t t;                      // 線程id
    char *name;                       // 線程name
    char *thread_group_name;          // 線程group name
    SC_ATOMIC_DECLARE(unsigned short, flags); // 原子聲明,不知道作用,暫且不管
    uint8_t aof;                      // 線程遇到故障時怎麼做
    uint8_t type;                     // 線程類型,例如:TVT_PPT, TVT_MGMT
    uint8_t restarted;                // 線程重新啓動失敗的次數
    Tmq *inq;
    Tmq *outq;
    void *outctx;
    char *outqh_name
    struct Packet_ * (*tmqh_in)(struct ThreadVars_ *);
    void (*InShutdownHandler)(struct ThreadVars_ *);
    void (*tmqh_out)(struct ThreadVars_ *, struct Packet_ *);
    void *(*tm_func)(void *);
    struct TmSlot_ *tm_slots;
    uint8_t thread_setup_flags;
    uint16_t cpu_affinity;
    int thread_priority;                // 線程優先級
    SCPerfContext sc_perf_pctx;
    SCPerfCounterArray *sc_perf_pca;
    SCMutex *m;
    SCCondT *cond;

    uint8_t cap_flags;
    struct ThreadVars_ *next;
    struct ThreadVars_ *prev;
} ThreadVars;
-----------------------------------------
槽slot的定義:
typedef struct TmSlot_ {

    ThreadVars *tv;                       // 擁有該slot的線程
    SC_ATOMIC_DECLARE(TmSlotFunc, SlotFunc);// 函數指針
    TmEcode (*PktAcqLoop)(ThreadVars *, void *, void *);      // 模塊數據包獲取函數
    TmEcode (*SlotThreadInit)(ThreadVars *, void *, void **); // 模塊初始化執行函數
    void (*SlotThreadExitPrintStats)(ThreadVars *, void *);   // 模塊退出打印函數
    TmEcode (*SlotThreadDeinit)(ThreadVars *, void *);        // 模塊清理執行函數
    void *slot_initdata;  // 數據存儲
    SC_ATOMIC_DECLARE(void *, slot_data);
    PacketQueue slot_pre_pq;
    PacketQueue slot_post_pq;
    int tm_id;  // tm ID
    int id;     // slot ID
    struct TmSlot_ *slot_next;
} TmSlot;
-------------------------------------------------
模塊定義:
typedef struct TmModule_ {
    char *name;          // 模塊名稱
    TmEcode (*ThreadInit)(ThreadVars *, void *, void **);
    void (*ThreadExitPrintStats)(ThreadVars *, void *);
    TmEcode (*ThreadDeinit)(ThreadVars *, void *);
    TmEcode (*Func)(ThreadVars *, Packet *, void *, PacketQueue *, PacketQueue *);
    TmEcode (*PktAcqLoop)(ThreadVars *, void *, void *);
    TmEcode (*Init)(void);
    TmEcode (*DeInit)(void);
    void (*RegisterTests)(void);
    uint8_t cap_flags;  
    uint8_t flags;
} TmModule;
===============================================
將三者的定義放在一起目的是方便查看,其中部分變量目前還不清楚具體含義,日後補充。
三者之間關係如下圖所示:


雖然上圖畫的比較山寨,但應該可以清楚的說明三者之間的關係了,每一個線程都包含一個slot的鏈表,每個slot結點都懸掛着不同的模塊,程序執行的時候會遍歷slot鏈表,按照加入鏈表的熟悉執行模塊。



再從main()函數看起 --- 模式、模塊、線程和槽

 

經過上面幾篇Blog的學習步驟,到今天再回過頭去看了下main函數的執行過程,把重點幾個步驟重新整理了下,方便更深入的理解架構。

1. 註冊各種運行模式
   RunModeRegisterRunModes()函數
   
   RunModeIdsPcapRegister();      // IDS+pcap
   RunModeFilePcapRegister();     // File+pcap
   RunModeIdsPfringRegister();    // IDS+pfring
   RunModeIpsIPFWRegister();      // IPS+ipfw
   RunModeIpsNFQRegister();       // IPS+nfq
   RunModeErfFileRegister();      // erf+file
   RunModeErfDagRegister();       // erf+dag
   RunModeNapatechRegister();     // napatech
   RunModeIdsAFPRegister();       // IDS+AFP
   RunModeUnixSocketRegister();   // UnixSocket
   其中每一種運行模式調用RunModeRegisterNewRunMode註冊各自的Custom mode(暫且翻譯爲“自定義模式”)
   
   RunModeRegisterNewRunMode設置各種運行模式的執行函數
   例如:RunModeRegisterNewRunMode(RUNMODE_PCAP_DEV, "single",
                              "Single threaded pcap live mode",
                              RunModeIdsPcapSingle);
   將執行函數添加到runmodes全局數組中。
   全局Runmodes類型數組runmodes保存運行模式,存儲結構如下圖:
   

2. 註冊模塊
   註冊suricata所支持的所有線程模塊
   
   TmModuleReceiveNFQRegister();
   TmModuleVerdictNFQRegister();
   TmModuleDecodeNFQRegister();
   
   TmModuleReceiveIPFWRegister();
   TmModuleVerdictIPFWRegister();
   TmModuleDecodeIPFWRegister();
   
   TmModuleReceivePcapRegister();
   TmModuleDecodePcapRegister();
   
   TmModuleReceivePcapFileRegister();
   TmModuleDecodePcapFileRegister();
   ……
   ……
   函數內部實現:
   void TmModuleReceivePcapRegister (void) 
   {
    tmm_modules[TMM_RECEIVEPCAP].name = "ReceivePcap";
    tmm_modules[TMM_RECEIVEPCAP].ThreadInit = ReceivePcapThreadInit;
    tmm_modules[TMM_RECEIVEPCAP].Func = NULL;
    tmm_modules[TMM_RECEIVEPCAP].PktAcqLoop = ReceivePcapLoop;
    tmm_modules[TMM_RECEIVEPCAP].ThreadExitPrintStats = ReceivePcapThreadExitStats;
    tmm_modules[TMM_RECEIVEPCAP].ThreadDeinit = NULL;
    tmm_modules[TMM_RECEIVEPCAP].RegisterTests = NULL;
    tmm_modules[TMM_RECEIVEPCAP].cap_flags = SC_CAP_NET_RAW;
    tmm_modules[TMM_RECEIVEPCAP].flags = TM_FLAG_RECEIVE_TM;
   }
   保存在全局TmModule tmm_modules[TMM_SIZE]數組中。
   typedef struct TmModule_ 
   {
      char *name;
      TmEcode (*ThreadInit)(ThreadVars *, void *, void **); // 線程初始化函數
      void (*ThreadExitPrintStats)(ThreadVars *, void *); // 線程退出打印函數
      TmEcode (*ThreadDeinit)(ThreadVars *, void *); // 線程關閉函數
      TmEcode (*Func)(ThreadVars *, Packet *, void *, PacketQueue *, PacketQueue *);
      TmEcode (*PktAcqLoop)(ThreadVars *, void *, void *);
      TmEcode (*Init)(void);// 全局初始化模塊函數
      TmEcode (*DeInit)(void);// 全局關閉模塊函數
      void (*RegisterTests)(void);
      uint8_t cap_flags;  
      uint8_t flags;
    } TmModule;
  
  存儲結構如下圖所示:
   

3. 模塊初始化
   TmModuleRunInit()函數
   調用tmm_modules[TMM_SIZE]數組中模塊各個模塊初始化函數。
    for (i = 0; i < TMM_SIZE; i++)
    {
        t = &tmm_modules[i];
        t->Init(); // 注意這裏執行的是模塊全局初始化函數
    }

4. 運行模式調度
   RunModeDispatch()函數
  • 從配置中讀取運行模式。
  • 獲得該運行模式中默認的Custom mode(如:single、auto等)。
  • 執行Custom mode中設置的執行函數,如上圖中所示的“執行函數”。

5. 運行模式執行函數
   例如:RunModeFilePcapSingle()
  • 通用模塊初始化RunModeInitialize
  • 創建tv實例TmThreadCreatePacketHandler
  • 從tmm_modules中獲得模塊TmModuleGetByName
  • 插入槽slot
  • TmThreadSpawn真正創建線程函數


整理下執行順序:
  • 運行模式註冊,設置執行函數
  • 所有模塊註冊,設置模塊相關函數
  • 所有模塊初始化
  • 從配置獲取運行模式類型,執行函數
  • 創建線程
  • 根據模塊名稱從全局數組tmm_modules中得到模塊指針
  • 插入線程槽slot


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章