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。

 

下面说一个题外话,年后遇到了一个问题,。。。。。。但这个问题确实勾起了我的兴趣,后面如果有机会让我插手,我会默默排查的。

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