這條信息在運行tcpdump的時候都會見到,那它到底代表什麼,又是怎麼產生的。這篇文章就說一下這個事。這條信息共有4個內容,分別是:ens33,EN10MB,Ethernet 和 262144.這是最全的情況,查看tcpdump代碼你會發現還有3個內容的情況,那個比這個要簡單。忘了說了,我使用的tcpdump是4.9.2,libpcap是1.9.0.下面就把這4個內容分別介紹一下。
ens33
tcpdump中的pcap_findalldevs接口引入,實現在libpcap中。在libpcap中用3種辦法確保獲取到網卡名。首先是系統函數getifaddrs,這個基本就都成功了。如果不好使,程序會讀/sys/class/net,裏面每個網卡都有自己的文件夾。如果這個還不好用,那麼就讀/proc/net/dev。這3種辦法應該就能兼容所有linux設備了。關於getifaddrs上網查一下的話會發現有說它bug的文章,內存泄漏,有興趣可以自己看一下。網上只有一篇文章介紹,不會找錯的。
EN10MB 和 Ethernet
這兩個是一體的,查看 libpcap 中的pcap.c文件中的 dlt_choices[ ]就會發現。結構體如下所示:
struct dlt_choice {
const char *name;
const char *description;
int dlt;
};
#define DLT_CHOICE(code, description) { #code, description, DLT_ ## code }
#define DLT_CHOICE_SENTINEL { NULL, NULL, 0 }
static struct dlt_choice dlt_choices[] = {
DLT_CHOICE(NULL, "BSD loopback"),
DLT_CHOICE(EN10MB, "Ethernet"),
DLT_CHOICE(IEEE802, "Token ring"),
DLT_CHOICE(ARCNET, "BSD ARCNET"),
DLT_CHOICE(SLIP, "SLIP"),
... ...
};
其中Ethernet是根據EN10MB拿到的,所以需要知道EN10MB是如何獲取到的,查看tcpdump的main函數會發現。我下面把關鍵函數都列一下。具體情況不贅述,只說一些我認爲有價值的地方。
dlt_name 這個字符串指針就是 EN10MB,是 pcap_datalink_val_to_name(dlt) 的返回值。那麼關鍵就轉移到了 dlt 這個整型變量上面。dlt 又是 pcap_datalink(pd) 接口的返回值,關鍵又轉移到了 pd ,這個指向 pcap_t 的指針上,這個真的是關鍵!
Breakpoint 1, map_arphrd_to_dlt (handle=0x555555a04aa0, sock_fd=3, arptype=1,
device=0x555555a05800 "ens33", cooked_ok=1) at ./pcap-linux.c:3129
3129 {
(gdb) bt
#0 map_arphrd_to_dlt (handle=0x555555a04aa0, sock_fd=3, arptype=1, device=0x555555a05800 "ens33",
cooked_ok=1) at ./pcap-linux.c:3129
#1 0x000055555560a033 in activate_new (handle=0x555555a04aa0) at ./pcap-linux.c:3691
#2 pcap_activate_linux (handle=0x555555a04aa0) at ./pcap-linux.c:1564
#3 0x000055555560e2ed in pcap_activate (p=0x555555a04aa0) at ./pcap.c:2418
#4 0x0000555555594a5c in open_interface (device=0x555555a04a80 "ens33", ndo=<optimized out>,
ebuf=0x7fffffffd170 "") at ./tcpdump.c:1036
#5 0x0000555555592514 in main (argc=<optimized out>, argv=0x7fffffffe398) at ./tcpdump.c:1698
pd 是 pd = open_interface(device, ndo, ebuf) 接口的返回值,上面這個棧信息清晰的展現了 pd 中 linktype 這個關鍵變量賦值的過程!3幀到2幀那裏是 activate_op 這個函數指針,在 pcap_create 的時候賦值爲 pcap_activate_linux 接口,可以查看 pcap_create_interface 接口。
activate_new 接口裏面調用 iface_get_arptype接口,這個是 linktype 賦值的最關鍵函數!下面貼出 iface_get_arptype 的實現方式。
static int
iface_get_arptype(int fd, const char *device, char *ebuf)
{
struct ifreq ifr;
memset(&ifr, 0, sizeof(ifr));
strlcpy(ifr.ifr_name, device, sizeof(ifr.ifr_name));
printf("%s %d %s here fd=%d device=%s ebuf=%s\n", __FILE__, __LINE__, __FUNCTION__,fd,device,ebuf);
if (ioctl(fd, SIOCGIFHWADDR, &ifr) == -1) {
pcap_fmt_errmsg_for_errno(ebuf, PCAP_ERRBUF_SIZE,
errno, "SIOCGIFHWADDR");
if (errno == ENODEV) {
/*
* No such device.
*/
return PCAP_ERROR_NO_SUCH_DEVICE;
}
return PCAP_ERROR;
}
printf("%s %d %s ifr.ifr_hwaddr.sa_family=%d\n", __FILE__, __LINE__, __FUNCTION__,ifr.ifr_hwaddr.sa_family);
return ifr.ifr_hwaddr.sa_family;
}
ioctl 提供了方法,libpcap 用的是這個。查看 ioctls.h 會發現有很多其它功能的宏,詳細介紹的博客也有很多。看其他博客的時候偶然告訴了我 ifconfig 就是基於這個做的!ifconfig 是基於 ioctl 做的!ifconfig 是基於 ioctl 做的!驚不驚喜意不意外~
上面這個接口還有另外一個提高B格的地方。那就是拷貝 device 這個網卡名字的時候用的是 strlcpy 這個字符串拷貝的第三版接口,第一版是strcpy,第二版是strncpy。三者的區分可以看看這篇文章 我做了6年的嵌入式Linux C 開發,在上一家公司 strcpy 已經被摒棄,但也纔剛開始三年左右時間,strcpy 確實容易引起問題,都用 strncpy 替換。但 strlcpy 是我第一次見到!在系統不支持的情況下,libpcap 也給出了宏重定義,可以借鑑,如下所示。
<portability.h>
#define strlcpy(x, y, z) \
(strncpy((x), (y), (z)), \
((z) <= 0 ? 0 : ((x)[(z) - 1] = '\0')), \
(void) strlen((y)))
map_arphrd_to_dlt 根據 arptype 做 switch 判斷賦值。後面過程就比較簡單了,這裏不再描述。
在 map_arphrd_to_dlt 上面有一大段註釋,如下所示。整個 tcpdump 和 libpcap 工程裏面有非常多的註釋,堪稱典範。我還無法完全理解。
/*
* Linux uses the ARP hardware type to identify the type of an
* interface. pcap uses the DLT_xxx constants for this. This
* function takes a pointer to a "pcap_t", and an ARPHRD_xxx
* constant, as arguments, and sets "handle->linktype" to the
* appropriate DLT_XXX constant and sets "handle->offset" to
* the appropriate value (to make "handle->offset" plus link-layer
* header length be a multiple of 4, so that the link-layer payload
* will be aligned on a 4-byte boundary when capturing packets).
* (If the offset isn't set here, it'll be 0; add code as appropriate
* for cases where it shouldn't be 0.)
*
* If "cooked_ok" is non-zero, we can use DLT_LINUX_SLL and capture
* in cooked mode; otherwise, we can't use cooked mode, so we have
* to pick some type that works in raw mode, or fail.
*
* Sets the link type to -1 if unable to map the type.
*/
262144
這個是包大小的默認值 DEFAULT_SNAPLEN 256KB。可以用 -s 指定,經常用tcpdump解決問題自然會用到這個選項,man手冊裏面有還算詳細的介紹。我另外那篇寫抓包的文章好像提到過。這個是鏈接
到這裏關於這一條出現過無數次的信息就算介紹完了。後面的文章會繼續深入瞭解 tcpdump。
下面說一個題外話,年後遇到了一個問題,。。。。。。但這個問題確實勾起了我的興趣,後面如果有機會讓我插手,我會默默排查的。