libpcap/tcpdump—2—網絡信息(listening on ens33, link-type EN10MB (Ethernet), capture size 262144 bytes)

這條信息在運行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) 接口的返回值,上面這個棧信息清晰的展現了 pdlinktype 這個關鍵變量賦值的過程!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 上面有一大段註釋,如下所示。整個 tcpdumplibpcap 工程裏面有非常多的註釋,堪稱典範。我還無法完全理解。

/*
 *  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。

 

下面說一個題外話,年後遇到了一個問題,。。。。。。但這個問題確實勾起了我的興趣,後面如果有機會讓我插手,我會默默排查的。

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