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];
//options
option options[]={ //长选项
{"iface", required_argument, 0, 'i'},
};
//global args
char *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 eth0
capture.cpp:41: Start capture on eth0...
capture.cpp:45: 14:30:31,550031 us, len:121
00-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-ad
capture.cpp:45: 14:30:31,550702 us, len:66
a0-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 list
pcap_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 device
printf("select a device: "); scanf("%d", &devid);
pdev=alldevs; while (--devid) pdev=pdev->next;
printf("Selected %s", pdev->name);
//open device
pcap_ptr_try(hdev = pcap_open_live(pdev->name, 0xffff, 0, 1000, errbuf));
Debug("Start capture on %s...", pdev->name);
//free device list
pcap_freealldevs(alldevs);
//start capture
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);
}, 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: 2
capture_short.cpp:42: Start capture on �b... //擦 内存
capture_short.cpp:46: 09:35:39,720743 us, len:79
capture_short.cpp:46: 09:35:39,721226 us, len:66
capture_short.cpp:46: 09:35:39,829933 us, len:79
capture_short.cpp:46: 09:35:39,830169 us, len:66
capture_short.cpp:46: 09:35:39,853439 us, len:60
capture_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),我要定义不同类型的宏。完全是为了调用方便。