用libpcap抓包的簡單流程:
1. 獲得裝置名稱:pcap_findalldevs列出所有裝置——選擇列出的網路裝置,在鏈表中找到。得到裝置名稱以後把鏈表釋放。
或者直接存在字串裡
2. pcap_open_live用裝置名稱開啟裝置(另一個函數是pcap_open_dead,略獵奇)
3. pcap_loop開始抓包。這裡沒有使用混雜模式。(第二個參數是0)
4. 利用回呼函數處理捕獲的資料包。這裡直接寫在pcap_loop裡了。傳進來的資料不需要free掉。
note:
其實在linux下 最合適的語言是C。不過libpcap與c++的相性也還不錯,沒有出現任何坑爹的情況。
本來這個程式是純C的(所以有scanf/printf),為了精簡,使用了lambda函數,也就改成了c++。
revision 3:嘗試使用getopt_long處理命令列參數。原來用"%02x"就可以正確輸出hex了,而且前面補0.原來一直都沒想到怎麼在前面補0,輸出都是自己寫的超複雜函數的說= =。去掉了顯示裝置名稱,改成用-i或者 --iface指定。所以仍然壓縮到了50行。
#include "iostream"#include "ctime"#include "cstdlib"#include "cstring"#include "getopt.h"#ifndef __USE_BSD#define __USE_BSD#endif#include <sys/types.h>//u_short types#include "pcap.h"#define str_(x) x//這樣可以解決c++處理format的問題#define Debug(format, ...) fprintf(stderr, "%s:%d: " str_(format) "\n", __FILE__, __LINE__, ##__VA_ARGS__)#define assert_(expr_, extra_op)do { if (!(expr_)) { Debug("在函數 `%s'中: 斷言錯誤: " #expr_, __FUNCTION__);\extra_op;exit (-1);} } while (0)#define pcap_try(func_) assert_(func_!=-1, Debug("Message: %s", errbuf))#define pcap_ptr_try(func_) assert_((func_) != 0, Debug("Message: %s", errbuf))using namespace std;static char buf[10000], errbuf[10000];//optionsoption options[]={//長選項{"iface", required_argument, 0, 'i'},};//global argschar *ifname;int main(int argc, char **argv){char opt;while (-1!=( opt=getopt_long(argc, argv, "i:", options, 0) )){switch(opt){case 'i': ifname=optarg; break;}}pcap_t *hdev;pcap_ptr_try(hdev = pcap_open_live(ifname, 0xffff, 0, 1000, errbuf));Debug("Start capture on %s...", ifname);pcap_loop(hdev, 0, [buf](u_char *param, const struct pcap_pkthdr *header, const u_char *data){strftime(buf, sizeof(buf), "%H:%M:%S", localtime(&header->ts.tv_sec)); Debug("%s,%.6d us, len:%d", buf, header->ts.tv_usec, header->len);for(int i=0; i<header->len; i++)printf("%02x%c", data[i], i<header->len-1?'-':'\n');}, 0);return 0;}
運行:
$ sudo ./capture -i eth0capture.cpp:41: Start capture on eth0...capture.cpp:45: 14:30:31,550031 us, len:12100-1e-c9-37-c8-10-a0-21-b7-ae-3e-fa-08-00-45-00-00-6b-05-8f-40-00-7b-06-49-ac-de-14-fc-cf-c0-a8-14-c5-0d-3d-a0-76-4a-89-f9-09-74-d1-8b-50-80-18-01-01-5c-22-00-00-01-01-08-0a-00-1f-1c-a0-00-15-f4-a0-80-37-86-6a-4c-a9-3a-b3-65-94-f1-92-a7-ed-0e-bd-0a-f6-4c-48-6d-75-8e-88-4f-7a-33-e4-99-1d-54-f1-ab-53-44-64-72-9c-8d-34-45-4b-53-04-69-f8-fd-9e-a7-74-f6-e1-ca-e9-adcapture.cpp:45: 14:30:31,550702 us, len:66a0-21-b7-ae-3e-fa-00-1e-c9-37-c8-10-08-00-45-00-00-34-34-bb-40-00-40-06-55-b7-c0-a8-14-c5-de-14-fc-cf-a0-76-0d-3d-74-d1-8b-50-4a-89-f9-40-80-10-39-f3-b0-78-00-00-01-01-08-0a-00-15-f4-b9-00-1f-1c-a0
revision 2:修改了宏定義裡面的"#format"錯誤。見文章最後,老版本程式後面的note。
#include <cstdio>#include <cstdlib>#include <ctime>#ifndef __USE_BSD#define __USE_BSD#endif#include <sys/types.h>//u_short types#include "pcap.h"#define str_(x) x//這樣可以解決c++處理format的問題#define Debug(format, ...) fprintf(stderr, "%s:%d: " str_(format) "\n", __FILE__, __LINE__, ##__VA_ARGS__)#define assert_(expr_, extra_op)do { if (!(expr_)) { Debug("在函數 `%s'中: 斷言錯誤: " #expr_, __FUNCTION__);\extra_op;exit (-1);} } while (0)#define pcap_try(func_) assert_(func_!=-1, Debug("Message: %s", errbuf))#define pcap_ptr_try(func_) assert_((func_) != 0, Debug("Message: %s", errbuf))static char buf[10000], errbuf[10000];int main(){int i=0, devid;pcap_t *hdev;pcap_if_t *alldevs, *pdev;//get device listpcap_try(pcap_findalldevs(&alldevs, errbuf));for (pdev = alldevs; pdev; pdev=pdev->next)printf("#%d: %s %s\n", ++i, pdev->name, pdev->description? pdev->description:"");//select deviceprintf("select a device: "); scanf("%d", &devid);pdev=alldevs; while (--devid) pdev=pdev->next;printf("Selected %s", pdev->name);//open devicepcap_ptr_try(hdev = pcap_open_live(pdev->name, 0xffff, 0, 1000, errbuf));Debug("Start capture on %s...", pdev->name);//free device listpcap_freealldevs(alldevs);//start capturepcap_loop(hdev, 0, [buf](u_char *param, const struct pcap_pkthdr *header, const u_char *data){/* 將時間戳記轉變為易讀的標準格式*/strftime(buf, sizeof(buf), "%H:%M:%S", localtime(&header->ts.tv_sec)); Debug("%s,%.6d us, len:%d", buf, header->ts.tv_usec, header->len);}, 0);return 0;}
運行:sudo運行
viktor@buxiang-OptiPlex-330:~/proj/pcap$ sudo ./capture #1: peth0 #2: eth0 #3: usbmon1 USB bus number 1#4: usbmon2 USB bus number 2#5: usbmon3 USB bus number 3#6: usbmon4 USB bus number 4#7: usbmon5 USB bus number 5#8: any Pseudo-device that captures on all interfaces#9: lo select a device: 2capture_short.cpp:42: Start capture on �b... //擦 記憶體capture_short.cpp:46: 09:35:39,720743 us, len:79capture_short.cpp:46: 09:35:39,721226 us, len:66capture_short.cpp:46: 09:35:39,829933 us, len:79capture_short.cpp:46: 09:35:39,830169 us, len:66capture_short.cpp:46: 09:35:39,853439 us, len:60capture_short.cpp:46: 09:35:39,939339 us, len:79
昨天的程式(rev1)的相關部分:
#define Debug(format, ...) fprintf(stderr, "%s:%d: " #format "\n", __FILE__, __LINE__, ##__VA_ARGS__)#define assert_(expr_, extra_op)do { if (!(expr_)) { Debug("在函數 `%s'中: 斷言錯誤: " #expr_, __FUNCTION__);\extra_op;exit (-1);} } while (0)#define pcap_assert(func_) assert_(func_!=-1, Debug("Message: %s", errbuf))#define pcap_ptr_assert(func_) assert_((func_) != 0, Debug("Message: %s", errbuf))
在宏定義的地方出了一個問題:
fprintf(stderr, "%s:%d: " #format "\n", __FILE__, __LINE__, ##__VA_ARGS__)
這裡如果寫成"%s:%d: "format"\n",C編譯正常(我一開始的C代碼裡面就是這樣寫的),但是g++會報錯:undefined string literal override (operator "" format)
似乎是觸發了c++0x的特性“自訂的字面量尾碼” 例如 356f, 356adk——這裡好像把我準備串連的識別成了一個字面量【 "%s:%d:"format】
如果寫成"%s:%d:"#format (也就是上面這樣)那麼輸出時候會多出一堆引號,很難看
如果寫成 ##format 他又會把"%s:%d:"format串連成一個標識符,仍然不對。
note:正確的修改辦法是
#define str_(x) x#define Debug(format, ...) fprintf(stderr, "%s:%d: " str_(format) "\n", __FILE__, __LINE__, ##__VA_ARGS__)
用一個宏把它套起來,避免在同一遍parse。
精簡以後數了數,剛好50行。果然自己一天 只能寫50行的有效代碼啊……
note:以上代碼的幾個宏,應該叫做try而不是assert。
如果是assert的話,應該把"!=-1"這個判斷條件寫在調用裡面,這樣讀程式顯得更加清晰,像這樣
pcap_assert(func != -1)
但是我的寫法完全是為了代替以下的麻煩寫法:
retvar = func(...);if (retvar == -1){ fprintf(stderr, "error message!\n"); exit(-1)}
我的寫法:
pcap_try(func(...));
所以為了處理不同類型的函數的不同的出錯值範圍(例如這裡用到了==-1和==null),我要定義不同類型的宏。完全是為了調用方便。