2016年产生了很多的错觉。
-----------------------------------
网络问题的排查过程,能够供我们进行事后分析的,只有数据包。
通过分析pcap文件,可以得到很多的信息,但这些信息都是从数据包的属性中获得的,然而我们知道,数据包是协议栈发出的,协议栈为什么会在特定的时间发出特定的数据包,却无法获知。所以说,分析数据包得到的信息只能是一种事后的结论,无论怎么解释,都是可以自圆其说的。
如果你想洞悉协议栈的行为并且希望改变其行为,这种事后分析完全不够。如果有什么办法可以同时获知协议栈在发送对应数据包的同时的状态快照就好了。所谓的状态快照其实是一个上下文,其信息包括拥塞窗口,慢启动阈值,对端通告窗口,RTT,当前最小RTT...其实,内核已经帮我们准备了这么一个“TCP上下文容器”,是为tcp_info结构体。获取了协议栈的上下文快照之后,除了能看到那个已经在你眼前的数据包之外,还能知道这个数据包为什么被发出来。
现在有两个问题:
问题1:如何获取这个tcp_info结构体?
问题2:如何将获取到的tcp_info其与事后的pcap数据包结合在一起?
单单回答其中任何一个问题都不是什么难事。
问题1的答案可能包括以下的:
1.在socket进程内部通过getsocketopt来获取tcp_info;
2.通过tcpprobe机制HOOK住任意TCP函数,这个需要加载甚至修改tcpprobe模块;
3.编写一个Netfilter模块,在任意上下文(而非socket所在进程)导出任意tcp_info;
...
问题2的答案可能包括以下的:
1.编写一个外部脚本通过时间来映射pcap文件里的包与info文件的条目之间的关系;
2.和1类似,但是使用序列号而不是时间来建立映射;
...
但是这些方法都显得太Low!我并不觉得完成上述任意一项可以“埋没掉什么技巧”,我只是觉得上面那些都太复杂了!
几个月前,我就有个想法,能不能把tcp_info信息“附着在每一个被抓取的包之后然后一起写入pcap文件”呢?
起初,我的想法来自于以下“伪装的”事实,一般而言,我们不太关注应用层的数据,我们只关注TCP数据,比如什么序列号啊,时间戳啊...所以说,我就想在内核抓包的时候,将应用层的数据替换成tcp_info...
我承认有段时间我被洗脑了,谁说人们不关注应用层数据了?谁说TCP的重要性是最高的了?全都是臆想!把应用层数据替换成TCP的信息,赤裸裸的武装侵略(而不仅仅是道德绑架)!在被TCP洗脑的那段时间,我同时也痴迷与研究pcap以及pcap-ng的格式,我懂得太多了,以至于当时不由分说我就知道,如果不替换应用数据,将tcp_info作为pcap-ng每个包的options来存储,也是不错的主意!这太TM帅了!
【关于pcap,pcap-ng的格式,请自行参考Wireshark的文档: https://wiki.wireshark.org/Development/LibpcapFileFormat】
-----------------------------------
不管哪种方案,我都没有落实。因为没有足够的冲动,再说当时没有人很在意这个,我也没有时间,便逐渐将其遗忘。我承认,如果这是一项工作任务,有经理逼着必须在规定时间弄好,否则加班的话,这玩意儿早就上线了,但是最终它沦落成了我的个人爱好,没人管,除了自己。这种沦落在2016年的最后一天升华,如果说我必须在2016年最后一天完成点什么的话,那就是,把这个pcap附带tcp_info的工具完成一个Demo出来。
在编码的过程中,我遇到了很多问题,同时也颠覆了之前的很多认知,事后看来,这都不错。本文中,我试图把这些过程记录下来。
-----------------------------------
需要修改的是af_packet.c的tpacket_rcv函数,不要再提packet_rcv,这是个老式的逻辑了。现在统统都归于tpacket_rcv了,要问这两者的区别,简单来讲就是,tpacket_rcv不是通过recvmsg来获取数据包到libpcap的,而是通过mmap,这样的机制避免了内存拷贝(但没有完全避免)和状态切换,让抓包的效率更高且不容易遗漏。
具体来讲,我这里有两个想法:
常规的想法:
我在sk_buff结构体中分配一个tcp_info结构体,只要有skb,就能直接指针到tcp_info(非TCP数据包为NULL)。然而tcp_info结构体要168bytes,如果设备的pps很高的话,内存将会吃不消,所以权衡的做法有3样:阉割tcp_info,使用vmalloc临时分配skb的tcp_info内存,或者说懒惰方案,到了tpacket_rcv调用的时候再临时取。
简便的做法:
这个方案中,我采用了到了tpacket_rcv临时调用tcp_get_info取出tcp_info的方法。但是这样做的问题在于,从数据包离开TCP层那一刻起,到它到达抓包这个位置为止,是有个时间差的,在这段时间内,谁也不能保证协议栈状态不会被新进的ACK影响到以至于发生改变。但既然这是一种权衡,必然要放弃点什么,为了快速编码以验证,我采用了这种简便的折中方案。
快速编码:
这里之所以说快速编码,我是想先倾倒一碗鸡汤的。
-----------------------------------
如果你想快速对一个想法进行验证,需要有个前提,那就是你要对该领域足够熟悉,否则就是纸上谈兵的学院派了!以为内核增加新机制为例,我再次感谢我自己在2006/7年的时候带我的那位导师,随后的几年,我几乎通读并调试了网络方面的所有代码(除了硬件相关的),建立在对内核协议栈足够熟悉的基础上,在我做VPN,Netfilter,TCP甚至今天的抓包时,我才可以很快定位到要改哪里。如果你对代码不熟悉,即便原理性的东西你已经十分精通,在动手的时候也能兴叹!
我并不是说要忽略原理,事实上,我的学习过程中,二者是同步进行的,在早期,在我看代码,写代码的间隙,旁边总是有一本本厚厚的计算机领域经典的黑皮书,那种没有一行代码但是却有数学公式的书...想当年我刚毕业的时候,跟我现在工位旁边的那些小鲜肉的状态是差不多的,一本本厚厚的书,不懂就问...目睹在我身边的刚毕业的人能有如此的动力,看书,看代码,我也深感欣慰,我希望他们可以坚持下去,坚持10年。至于能不能坚持10年以上,我不知道,因为我自己刚好坚持了10年...
再次重申,必须熟悉代码,这是一切的基础,否则,除了跟别人头脑风暴,真要落实想法的时候,将会举步维艰。
-----------------------------------
以下是我的tpacket_rcv,所有的内核层面的修改都是基于Linux 4.9内核(而不再是2.6.32了),后面我会将这些代码整理下发到github,但在本文,我将不会贴全部代码:
在照上修改了tpacket_rcv之后,我发现orig_sk一直都是NULL,这个问题跟我之前遇到的关于NAT模块在复制skb的时候漏掉了tso相关的信息最终导致不应该的Fragment一样,我就知道在dev_queue_xmit_nit函数中出了点事故,于是Fix之:
现在暂且不管这么做的副作用,先保证抓包可以运行。
内核层面修改完毕,编译,先关闭info_proto参数加载,然后tcpdump抓包看看。挺好使。
直接用Wireshark打开,数据包完全可读,跟没有修改内核一样,那么我添加的那些tcp_info到哪里去了呢?这个你得用UE或者hexdump来看:
我比较惧怕修改libpcap以及tcpdump,因为我对这些代码不熟,幸运的是,我完全没有必要修改它们,也无需去做什么将tcp_info作为options写入pcap-ng之类的事。一切都工作的非常不错!如果说仅仅为了分析数据包以及数据包被抓时的协议栈快照,我的工作基本就结束了,后面的工作就是编写一些简单的脚本,自行解析pcap文件,把需要的数据抽离出来,然后用GNUPlot类似的工具把图画出来即可,Python也有自带的Plot库,直接在Python里搞定也不是什么困难的事,再说了,pcap或者pcap-ng文件都不复杂,自己解析起来非常容易。
但是,如果我希望在Wireshark中以一种自然而然的方式展示这些tcp_info的字段的话,那就不得不去了解一点关于Wireshark的事了。
-----------------------------------
以下的内容,如果对Wireshark展示tcp_info不感兴趣,比如温州皮鞋厂老板这类,请忽略!
-----------------------------------
然而,IP层搞定了,TCP层不一定认账!
我们知道,TCP协议头里并没有任何关于长度的信息,TCP段的数据长度完全由IP头的“数据总长”来推断,即数据总长减去IP头和TCP头,剩下的就是TCP数据的长度!我们假设发送了一个TCP段:
IP total length:160
Seq:100
除掉IP头20字节和TCP头40字节,TCP裸数据的长度应该是100字节,因此其终止序列号应该是200!然而,加入了tcp_info之后,数据段增加了168字节,该数据包的TCP/IP字段发生了变化:
IP total length:160+168
Seq:100
按照以上的计算方法,Wireshark会认为TCP数据的长度为,160+168-20-40=268(字节),因此TCP数据段的终止序列号为100+268=368(字节)!当协议栈再次发送TCP数据段的时候,初始序列号自然而然应该是201,然而在Wireshark看来,却应该是369!如果没有发出369,而是发出了201,那么Wireshark将会认为这是一个Out-of-order的数据段!
真难伺候!但可以不伺候!可以通过以下的方式禁用这种伺候:
显然,我是不懂Lua语言的,但是我会复制粘贴。以下是我为解析tcp_info而折腾出的一个十分垃圾但是却能用的Lua脚本:
参考了这篇文章《 Lua编写Wireshark插件实战》。
我的展示效果如下所示:
1>.《 How do I determine a TCP segment's length》
2>.《 need help: the code location of Ethernet trailer》
我的需求和他们的差不多。
我不想在tpacket_rcv中修改iph的tot_len,修改它是万不得已的办法!我希望的是,TCP/IP的解析照常进行,但不要把我附着的tcp_info信息作为Ethernet的Trailer来看待。如果哪位大师有办法,请帮助。
我不得不抱怨的是,为什么每次都是我遇到这种奇葩问题,为什么每次我遇到的问题都没有人回答,以至于很多时候,都是我自问自答,但现在,令人遗憾的2016年最后一天,我不再自问自答了,我要求助!我答应会发50到100的红包就一定会给,而且不光这个,以后如果有需要我帮忙的,我也会以此为乐,红包是小事,我认为交流可以共同进步,那才是大事。
这种问题已经被我解答了不下N次了,比如VMWare虚拟机里的nmap,nat问题等...唉,我竟然无语凝噎。
马上就2017年了,再见,令人不那么舒服的2016年...
-----------------------------------
网络问题的排查过程,能够供我们进行事后分析的,只有数据包。
通过分析pcap文件,可以得到很多的信息,但这些信息都是从数据包的属性中获得的,然而我们知道,数据包是协议栈发出的,协议栈为什么会在特定的时间发出特定的数据包,却无法获知。所以说,分析数据包得到的信息只能是一种事后的结论,无论怎么解释,都是可以自圆其说的。
如果你想洞悉协议栈的行为并且希望改变其行为,这种事后分析完全不够。如果有什么办法可以同时获知协议栈在发送对应数据包的同时的状态快照就好了。所谓的状态快照其实是一个上下文,其信息包括拥塞窗口,慢启动阈值,对端通告窗口,RTT,当前最小RTT...其实,内核已经帮我们准备了这么一个“TCP上下文容器”,是为tcp_info结构体。获取了协议栈的上下文快照之后,除了能看到那个已经在你眼前的数据包之外,还能知道这个数据包为什么被发出来。
现在有两个问题:
问题1:如何获取这个tcp_info结构体?
问题2:如何将获取到的tcp_info其与事后的pcap数据包结合在一起?
单单回答其中任何一个问题都不是什么难事。
问题1的答案可能包括以下的:
1.在socket进程内部通过getsocketopt来获取tcp_info;
2.通过tcpprobe机制HOOK住任意TCP函数,这个需要加载甚至修改tcpprobe模块;
3.编写一个Netfilter模块,在任意上下文(而非socket所在进程)导出任意tcp_info;
...
问题2的答案可能包括以下的:
1.编写一个外部脚本通过时间来映射pcap文件里的包与info文件的条目之间的关系;
2.和1类似,但是使用序列号而不是时间来建立映射;
...
但是这些方法都显得太Low!我并不觉得完成上述任意一项可以“埋没掉什么技巧”,我只是觉得上面那些都太复杂了!
几个月前,我就有个想法,能不能把tcp_info信息“附着在每一个被抓取的包之后然后一起写入pcap文件”呢?
起初,我的想法来自于以下“伪装的”事实,一般而言,我们不太关注应用层的数据,我们只关注TCP数据,比如什么序列号啊,时间戳啊...所以说,我就想在内核抓包的时候,将应用层的数据替换成tcp_info...
我承认有段时间我被洗脑了,谁说人们不关注应用层数据了?谁说TCP的重要性是最高的了?全都是臆想!把应用层数据替换成TCP的信息,赤裸裸的武装侵略(而不仅仅是道德绑架)!在被TCP洗脑的那段时间,我同时也痴迷与研究pcap以及pcap-ng的格式,我懂得太多了,以至于当时不由分说我就知道,如果不替换应用数据,将tcp_info作为pcap-ng每个包的options来存储,也是不错的主意!这太TM帅了!
【关于pcap,pcap-ng的格式,请自行参考Wireshark的文档: https://wiki.wireshark.org/Development/LibpcapFileFormat】
-----------------------------------
不管哪种方案,我都没有落实。因为没有足够的冲动,再说当时没有人很在意这个,我也没有时间,便逐渐将其遗忘。我承认,如果这是一项工作任务,有经理逼着必须在规定时间弄好,否则加班的话,这玩意儿早就上线了,但是最终它沦落成了我的个人爱好,没人管,除了自己。这种沦落在2016年的最后一天升华,如果说我必须在2016年最后一天完成点什么的话,那就是,把这个pcap附带tcp_info的工具完成一个Demo出来。
在编码的过程中,我遇到了很多问题,同时也颠覆了之前的很多认知,事后看来,这都不错。本文中,我试图把这些过程记录下来。
-----------------------------------
1.修改内核抓包的逻辑
我要把获取tcp_info以及将tcp_info附着的逻辑加入,这就要修改PACKET抓包的数据包获取机制。需要修改的是af_packet.c的tpacket_rcv函数,不要再提packet_rcv,这是个老式的逻辑了。现在统统都归于tpacket_rcv了,要问这两者的区别,简单来讲就是,tpacket_rcv不是通过recvmsg来获取数据包到libpcap的,而是通过mmap,这样的机制避免了内存拷贝(但没有完全避免)和状态切换,让抓包的效率更高且不容易遗漏。
具体来讲,我这里有两个想法:
常规的想法:
我在sk_buff结构体中分配一个tcp_info结构体,只要有skb,就能直接指针到tcp_info(非TCP数据包为NULL)。然而tcp_info结构体要168bytes,如果设备的pps很高的话,内存将会吃不消,所以权衡的做法有3样:阉割tcp_info,使用vmalloc临时分配skb的tcp_info内存,或者说懒惰方案,到了tpacket_rcv调用的时候再临时取。
简便的做法:
这个方案中,我采用了到了tpacket_rcv临时调用tcp_get_info取出tcp_info的方法。但是这样做的问题在于,从数据包离开TCP层那一刻起,到它到达抓包这个位置为止,是有个时间差的,在这段时间内,谁也不能保证协议栈状态不会被新进的ACK影响到以至于发生改变。但既然这是一种权衡,必然要放弃点什么,为了快速编码以验证,我采用了这种简便的折中方案。
快速编码:
这里之所以说快速编码,我是想先倾倒一碗鸡汤的。
-----------------------------------
如果你想快速对一个想法进行验证,需要有个前提,那就是你要对该领域足够熟悉,否则就是纸上谈兵的学院派了!以为内核增加新机制为例,我再次感谢我自己在2006/7年的时候带我的那位导师,随后的几年,我几乎通读并调试了网络方面的所有代码(除了硬件相关的),建立在对内核协议栈足够熟悉的基础上,在我做VPN,Netfilter,TCP甚至今天的抓包时,我才可以很快定位到要改哪里。如果你对代码不熟悉,即便原理性的东西你已经十分精通,在动手的时候也能兴叹!
我并不是说要忽略原理,事实上,我的学习过程中,二者是同步进行的,在早期,在我看代码,写代码的间隙,旁边总是有一本本厚厚的计算机领域经典的黑皮书,那种没有一行代码但是却有数学公式的书...想当年我刚毕业的时候,跟我现在工位旁边的那些小鲜肉的状态是差不多的,一本本厚厚的书,不懂就问...目睹在我身边的刚毕业的人能有如此的动力,看书,看代码,我也深感欣慰,我希望他们可以坚持下去,坚持10年。至于能不能坚持10年以上,我不知道,因为我自己刚好坚持了10年...
再次重申,必须熟悉代码,这是一切的基础,否则,除了跟别人头脑风暴,真要落实想法的时候,将会举步维艰。
-----------------------------------
以下是我的tpacket_rcv,所有的内核层面的修改都是基于Linux 4.9内核(而不再是2.6.32了),后面我会将这些代码整理下发到github,但在本文,我将不会贴全部代码:
static int tpacket_rcv(struct sk_buff *skb, struct net_device *dev,
struct packet_type *pt, struct net_device *orig_dev)
{
...
// 这是我新加的局部变量,获取该skb的原始sock
struct sock *orig_sk = skb->sk;
...
if (po->has_vnet_hdr) {
if (__packet_rcv_vnet(skb, h.raw + macoff -
sizeof(struct virtio_net_hdr))) {
spin_lock(&sk->sk_receive_queue.lock);
goto drop_n_account;
}
}
new_len = skb->len;
// 以下的{}花括号内的逻辑是我新加的逻辑,用于获取tcp_info并附着到数据包的后面
{
struct tcp_info *info = NULL;
unsigned int len = snaplen;
if (cap_tcpinfo == 0 || // 没有开启抓取tcp_info选项
skb->pkt_type != PACKET_OUTGOING || // 不是本地发出的包
(!orig_sk || orig_sk->sk_type != SOCK_STREAM)) { // 不是TCP包
goto deliver; // 例行逻辑
}
info = kmalloc(sizeof(struct tcp_info), GFP_ATOMIC);
if (info) {
memset(info, 0, sizeof(*info));
tcp_get_info(orig_sk, info);
// 暂且不需要tcpi_total_retrans,将其替换成数据包发送的reason。
// 数据包可以在好多地方被发送,比如send逻辑,比如retrans逻辑,比如RTO逻辑,记录下发包位置,然后导出到一个info字段里。
// 注意,skb->tcp_info表明,skb里加了一个u32的字段,表明了发包的位置,或者说是原因。
info->tcpi_total_retrans = skb->tcp_info;//Reason for sending
snaplen += sizeof(struct tcp_info);
new_len += sizeof(struct tcp_info);
if (snaplen > 65535) {
snaplen = len;
len -= sizeof(struct tcp_info);
}
if (debug) { // 打印tcp_info信息
unsigned char *buf = (unsigned char *)info;
int i = 0;
for (i = 0; i < 10; i++) {
int j = 0;
char lb[256];
memset(lb, 0, 256);
for (j = 0; j < 16; j++) {
sprintf(lb+j*3, "%02x ", buf[i*16+j]);
}
printk("%s\n", lb);
}
}
// 将tcp_info拷贝到数据包的后面,长度就是sizeof(struct tcp_info)
memcpy(h.raw + macoff + len, (void *)info, sizeof(struct tcp_info));
}
deliver:
// 拷贝数据包
skb_copy_bits(skb, 0, h.raw + macoff, len);
// 如果你想让Wireshark帮你展示tcp_info信息,那么请打开info_proto参数选项,否则请关闭,自行通过类似Python脚本的技术来解析tcp_info
if (info_proto && info) {
// 为了将tcp_info作为应用层数据以便解析,必须修改IP头的总长字段。
struct iphdr *iph = (struct iphdr *)(h.raw + netoff);
iph->tot_len = htons(ntohs(iph->tot_len) + sizeof(struct tcp_info));
}
}
if (!(ts_status = tpacket_get_timestamp(skb, &ts, po->tp_tstamp)))
...
switch (po->tp_version) { // 注意,new_len在OUTGOING方向发生了变化,不再是skb->len了。
case TPACKET_V1:
h.h1->tp_len = new_len;
h.h1->tp_snaplen = snaplen;
h.h1->tp_mac = macoff;
h.h1->tp_net = netoff;
h.h1->tp_sec = ts.tv_sec;
h.h1->tp_usec = ts.tv_nsec / NSEC_PER_USEC;
hdrlen = sizeof(*h.h1);
break;
case TPACKET_V2:
h.h2->tp_len = new_len;
h.h2->tp_snaplen = snaplen;
h.h2->tp_mac = macoff;
h.h2->tp_net = netoff;
h.h2->tp_sec = ts.tv_sec;
h.h2->tp_nsec = ts.tv_nsec;
if (skb_vlan_tag_present(skb)) {
h.h2->tp_vlan_tci = skb_vlan_tag_get(skb);
h.h2->tp_vlan_tpid = ntohs(skb->vlan_proto);
status |= TP_STATUS_VLAN_VALID | TP_STATUS_VLAN_TPID_VALID;
} else {
h.h2->tp_vlan_tci = 0;
h.h2->tp_vlan_tpid = 0;
}
memset(h.h2->tp_padding, 0, sizeof(h.h2->tp_padding));
hdrlen = sizeof(*h.h2);
break;
case TPACKET_V3:
/* tp_nxt_offset,vlan are already populated above.
* So DONT clear those fields here
*/
h.h3->tp_status |= status;
h.h3->tp_len = new_len;
h.h3->tp_snaplen = snaplen;
h.h3->tp_mac = macoff;
h.h3->tp_net = netoff;
h.h3->tp_sec = ts.tv_sec;
h.h3->tp_nsec = ts.tv_nsec;
memset(h.h3->tp_padding, 0, sizeof(h.h3->tp_padding));
hdrlen = sizeof(*h.h3);
break;
default:
BUG();
}
}
在照上修改了tpacket_rcv之后,我发现orig_sk一直都是NULL,这个问题跟我之前遇到的关于NAT模块在复制skb的时候漏掉了tso相关的信息最终导致不应该的Fragment一样,我就知道在dev_queue_xmit_nit函数中出了点事故,于是Fix之:
/* need to clone skb, done only once */
skb2 = skb_clone(skb, GFP_ATOMIC);
if (!skb2)
goto out_unlock;
net_timestamp_set(skb2);
// 加入一句
skb2->sk = skb->sk;
现在暂且不管这么做的副作用,先保证抓包可以运行。
内核层面修改完毕,编译,先关闭info_proto参数加载,然后tcpdump抓包看看。挺好使。
2.无需修改libpcap以及tcpdump
紧接着上面一节的最后,我们看下抓包的结果。直接用Wireshark打开,数据包完全可读,跟没有修改内核一样,那么我添加的那些tcp_info到哪里去了呢?这个你得用UE或者hexdump来看:
我比较惧怕修改libpcap以及tcpdump,因为我对这些代码不熟,幸运的是,我完全没有必要修改它们,也无需去做什么将tcp_info作为options写入pcap-ng之类的事。一切都工作的非常不错!如果说仅仅为了分析数据包以及数据包被抓时的协议栈快照,我的工作基本就结束了,后面的工作就是编写一些简单的脚本,自行解析pcap文件,把需要的数据抽离出来,然后用GNUPlot类似的工具把图画出来即可,Python也有自带的Plot库,直接在Python里搞定也不是什么困难的事,再说了,pcap或者pcap-ng文件都不复杂,自己解析起来非常容易。
但是,如果我希望在Wireshark中以一种自然而然的方式展示这些tcp_info的字段的话,那就不得不去了解一点关于Wireshark的事了。
-----------------------------------
以下的内容,如果对Wireshark展示tcp_info不感兴趣,比如温州皮鞋厂老板这类,请忽略!
-----------------------------------
3.配置Wireshark
如果希望Wireshark展示tcp_info信息,那就自然而然要把tcp_info作为一种新的协议对待,为此我们必须将af_packet模块的info_proto参数打开,以便IP层会认为tcp_info是属于自己的载荷。然而,IP层搞定了,TCP层不一定认账!
我们知道,TCP协议头里并没有任何关于长度的信息,TCP段的数据长度完全由IP头的“数据总长”来推断,即数据总长减去IP头和TCP头,剩下的就是TCP数据的长度!我们假设发送了一个TCP段:
IP total length:160
Seq:100
除掉IP头20字节和TCP头40字节,TCP裸数据的长度应该是100字节,因此其终止序列号应该是200!然而,加入了tcp_info之后,数据段增加了168字节,该数据包的TCP/IP字段发生了变化:
IP total length:160+168
Seq:100
按照以上的计算方法,Wireshark会认为TCP数据的长度为,160+168-20-40=268(字节),因此TCP数据段的终止序列号为100+268=368(字节)!当协议栈再次发送TCP数据段的时候,初始序列号自然而然应该是201,然而在Wireshark看来,却应该是369!如果没有发出369,而是发出了201,那么Wireshark将会认为这是一个Out-of-order的数据段!
真难伺候!但可以不伺候!可以通过以下的方式禁用这种伺候:
4.使用Lua编写Wireshark插件
我承认我不懂编程,但是却稍微懂一点!这让我觉得很尴尬。显然,我是不懂Lua语言的,但是我会复制粘贴。以下是我为解析tcp_info而折腾出的一个十分垃圾但是却能用的Lua脚本:
do
local p_TcpInfo = Proto("tcpinfo2","TCP Information2")
local f_appinfo = ProtoField.bytes("tcpinfo2.appinfo","Raw Application Information")
local f_tcpi_state = ProtoField.uint8("tcpinfo2.tcpi_state","tcpi_state",base.DEC)
local f_tcpi_ca_state = ProtoField.uint8("tcpinfo2.tcpi_ca_state","tcpi_ca_state",base.DEC)
local f_tcpi_retransmits = ProtoField.uint8("tcpinfo2.tcpi_retransmits","tcpi_retransmits",base.DEC)
local f_tcpi_probes = ProtoField.uint8("tcpinfo2.tcpi_probes","tcpi_probes",base.DEC)
local f_tcpi_backoff = ProtoField.uint8("tcpinfo2.tcpi_backoff","tcpi_backoff",base.DEC)
local f_tcpi_options = ProtoField.uint8("tcpinfo2.tcpi_options","tcpi_options",base.DEC)
local f_tcpi_snd_wscale = ProtoField.uint8("tcpinfo2.tcpi_snd_wscale","tcpi_snd_wscale",base.DEC)
local f_tcpi_delivery_rate_app_limited = ProtoField.uint8("tcpinfo2.tcpi_delivery_rate_app_limited","tcpi_delivery_rate_app_limited",base.DEC)
local f_tcpi_rto = ProtoField.uint32("tcpinfo2.tcpi_rto","tcpi_rto",base.DEC)
local f_tcpi_ato = ProtoField.uint32("tcpinfo2.tcpi_ato","tcpi_ato",base.DEC)
local f_tcpi_snd_mss = ProtoField.uint32("tcpinfo2.tcpi_snd_mss","tcpi_snd_mss",base.DEC)
local f_tcpi_rcv_mss = ProtoField.uint32("tcpinfo2.tcpi_rcv_mss","tcpi_rcv_mss",base.DEC)
local f_tcpi_unacked = ProtoField.uint32("tcpinfo2.tcpi_unacked","tcpi_unacked",base.DEC)
local f_tcpi_sacked = ProtoField.uint32("tcpinfo2.tcpi_sacked","tcpi_sacked",base.DEC)
local f_tcpi_lost = ProtoField.uint32("tcpinfo2.tcpi_lost","tcpi_lost",base.DEC)
local f_tcpi_retrans = ProtoField.uint32("tcpinfo2.tcpi_retrans","tcpi_retrans",base.DEC)
local f_tcpi_fackets = ProtoField.uint32("tcpinfo2.tcpi_fackets","tcpi_fackets",base.DEC)
local f_tcpi_last_data_sent = ProtoField.uint32("tcpinfo2.tcpi_last_data_sent","tcpi_last_data_sent",base.DEC)
local f_tcpi_last_ack_sent = ProtoField.uint32("tcpinfo2.tcpi_last_ack_sent","tcpi_last_ack_sent",base.DEC)
local f_tcpi_last_data_recv = ProtoField.uint32("tcpinfo2.tcpi_last_data_recv","tcpi_last_data_recv",base.DEC)
local f_tcpi_last_ack_recv = ProtoField.uint32("tcpinfo2.tcpi_last_ack_recv","tcpi_last_ack_recv",base.DEC)
local f_tcpi_pmtu = ProtoField.uint32("tcpinfo2.tcpi_pmtu","tcpi_pmtu",base.DEC)
local f_tcpi_rcv_ssthresh = ProtoField.uint32("tcpinfo2.tcpi_rcv_ssthresh","tcpi_rcv_ssthresh",base.DEC)
local f_tcpi_rtt = ProtoField.uint32("tcpinfo2.tcpi_rtt","tcpi_rtt",base.DEC)
local f_tcpi_rttvar = ProtoField.uint32("tcpinfo2.tcpi_rttvar","tcpi_rttvar",base.DEC)
local f_tcpi_snd_ssthresh = ProtoField.uint32("tcpinfo2.tcpi_snd_ssthresh","tcpi_snd_ssthresh",base.DEC)
local f_tcpi_snd_cwnd = ProtoField.uint32("tcpinfo2.tcpi_snd_cwnd","tcpi_snd_cwnd",base.DEC)
local f_tcpi_advmss = ProtoField.uint32("tcpinfo2.tcpi_advmss","tcpi_advmss",base.DEC)
local f_tcpi_reordering = ProtoField.uint32("tcpinfo2.tcpi_reordering","tcpi_reordering",base.DEC)
local f_tcpi_rcv_rtt = ProtoField.uint32("tcpinfo2.tcpi_rcv_rtt","tcpi_rcv_rtt",base.DEC)
local f_tcpi_rcv_space = ProtoField.uint32("tcpinfo2.tcpi_rcv_space","tcpi_rcv_space",base.DEC)
local f_tcpi_send_reason = ProtoField.uint32("tcpinfo2.tcpi_send_reason","send reason",base.DEC)
local f_tcpi_pacing_rate = ProtoField.uint64("tcpinfo2.tcpi_pacing_rate","tcpi_pacing_rate",base.DEC)
local f_tcpi_max_pacing_rate = ProtoField.uint64("tcpinfo2.tcpi_max_pacing_rate","tcpi_max_pacing_rate",base.DEC)
local f_tcpi_bytes_acked = ProtoField.uint64("tcpinfo2.tcpi_bytes_acked","tcpi_bytes_acked",base.DEC)
local f_tcpi_bytes_received = ProtoField.uint64("tcpinfo2.tcpi_bytes_received","tcpi_bytes_received",base.DEC)
local f_tcpi_segs_out = ProtoField.uint32("tcpinfo2.tcpi_segs_out","tcpi_segs_out",base.DEC)
local f_tcpi_segs_in = ProtoField.uint32("tcpinfo2.tcpi_segs_in","tcpi_segs_in",base.DEC)
local f_tcpi_notsent_bytes = ProtoField.uint32("tcpinfo2.tcpi_notsent_bytes","tcpi_notsent_bytes",base.DEC)
local f_tcpi_min_rtt = ProtoField.uint32("tcpinfo2.tcpi_min_rtt","tcpi_min_rtt",base.DEC)
local f_tcpi_data_segs_in = ProtoField.uint32("tcpinfo2.tcpi_data_segs_in","tcpi_data_segs_in",base.DEC)
local f_tcpi_data_segs_out = ProtoField.uint32("tcpinfo2.tcpi_data_segs_out","tcpi_data_segs_out",base.DEC)
local f_tcpi_delivery_rate = ProtoField.uint64("tcpinfo2.tcpi_delivery_rate","tcpi_delivery_rate",base.DEC)
p_TcpInfo.fields = {f_appinfo, f_tcpi_state, f_tcpi_ca_state, f_tcpi_retransmits, f_tcpi_probes, f_tcpi_backoff, f_tcpi_options, f_tcpi_snd_wscale,
f_tcpi_delivery_rate_app_limited, f_tcpi_rto, f_tcpi_ato, f_tcpi_snd_mss, f_tcpi_rcv_mss, f_tcpi_unacked, f_tcpi_sacked, f_tcpi_lost,
f_tcpi_retrans, f_tcpi_fackets, f_tcpi_last_data_sent, f_tcpi_last_ack_sent, f_tcpi_last_data_recv, f_tcpi_last_ack_recv, f_tcpi_pmtu,
f_tcpi_rcv_ssthresh, f_tcpi_rtt, f_tcpi_rttvar, f_tcpi_snd_ssthresh, f_tcpi_snd_cwnd, f_tcpi_advmss, f_tcpi_reordering, f_tcpi_rcv_rtt,
f_tcpi_rcv_space, f_tcpi_send_reason, f_tcpi_pacing_rate, f_tcpi_max_pacing_rate, f_tcpi_bytes_acked, f_tcpi_bytes_received,
f_tcpi_segs_out, f_tcpi_segs_in, f_tcpi_notsent_bytes, f_tcpi_min_rtt, f_tcpi_data_segs_in, f_tcpi_data_segs_out, f_tcpi_delivery_rate}
local data_dis = Dissector.get("data")
local function ScoreBoard_dissector(buf,pkt,root)
local buf_len = buf:len();
if buf_len < 168 then return false end
local v_appinfo = buf(0,buf_len - 168)
local v_tcpi_state = buf(buf_len-168,1)
local v_tcpi_ca_state = buf(buf_len-167,1)
local v_tcpi_retransmits = buf(buf_len-166,1)
local v_tcpi_probes = buf(buf_len-165,1)
local v_tcpi_backoff = buf(buf_len-164,1)
local v_tcpi_options = buf(buf_len-163,1)
local v_tcpi_snd_wscale = buf(buf_len-162,1)
local v_tcpi_delivery_rate_app_limited = buf(buf_len-161,1)
local v_tcpi_rto = buf(buf_len-160,4)
local v_tcpi_ato = buf(buf_len-156,4)
local v_tcpi_snd_mss = buf(buf_len-152,4)
local v_tcpi_rcv_mss = buf(buf_len-148,4)
local v_tcpi_unacked = buf(buf_len-144,4)
local v_tcpi_sacked = buf(buf_len-140,4)
local v_tcpi_lost = buf(buf_len-136,4)
local v_tcpi_retrans = buf(buf_len-132,4)
local v_tcpi_fackets = buf(buf_len-128,4)
local v_tcpi_last_data_sent = buf(buf_len-124,4)
local v_tcpi_last_ack_sent = buf(buf_len-120,4)
local v_tcpi_last_data_recv = buf(buf_len-116,4)
local v_tcpi_last_ack_recv = buf(buf_len-112,4)
local v_tcpi_pmtu = buf(buf_len-108,4)
local v_tcpi_rcv_ssthresh = buf(buf_len-104,4)
local v_tcpi_rtt = buf(buf_len-100,4)
local v_tcpi_rttvar = buf(buf_len-96,4)
local v_tcpi_snd_ssthresh = buf(buf_len-92,4)
local v_tcpi_snd_cwnd = buf(buf_len-88,4)
local v_tcpi_advmss = buf(buf_len-84,4)
local v_tcpi_reordering = buf(buf_len-80,4)
local v_tcpi_rcv_rtt = buf(buf_len-76,4)
local v_tcpi_rcv_space = buf(buf_len-72,4)
local v_tcpi_send_reason = buf(buf_len-68,4)
local v_tcpi_pacing_rate = buf(buf_len-64,8)
local v_tcpi_max_pacing_rate = buf(buf_len-56,8)
local v_tcpi_bytes_acked = buf(buf_len-48,8)
local v_tcpi_bytes_received = buf(buf_len-40,8)
local v_tcpi_segs_out = buf(buf_len-32,4)
local v_tcpi_segs_in = buf(buf_len-28,4)
local v_tcpi_notsent_bytes = buf(buf_len-24,4)
local v_tcpi_min_rtt = buf(buf_len-20,4)
local v_tcpi_data_segs_in = buf(buf_len-16,4)
local v_tcpi_data_segs_out = buf(buf_len-12,4)
local v_tcpi_delivery_rate = buf(buf_len-8,8)
local t = root:add(p_TcpInfo,buf)
pkt.cols.protocol = "tcpinfo2"
t:add(f_appinfo,v_appinfo)
t:add(f_tcpi_state,v_tcpi_state)
t:add(f_tcpi_ca_state,v_tcpi_ca_state)
t:add(f_tcpi_retransmits,v_tcpi_retransmits)
t:add(f_tcpi_probes,v_tcpi_probes)
t:add(f_tcpi_backoff,v_tcpi_backoff)
t:add(f_tcpi_options,v_tcpi_options)
t:add(f_tcpi_snd_wscale,v_tcpi_snd_wscale)
t:add(f_tcpi_delivery_rate_app_limited,v_tcpi_delivery_rate_app_limited)
t:add(f_tcpi_rto,v_tcpi_rto)
t:add(f_tcpi_ato,v_tcpi_ato)
t:add(f_tcpi_snd_mss,v_tcpi_snd_mss)
t:add(f_tcpi_rcv_mss,v_tcpi_rcv_mss)
t:add(f_tcpi_unacked,v_tcpi_unacked)
t:add(f_tcpi_sacked,v_tcpi_sacked)
t:add(f_tcpi_lost,v_tcpi_lost)
t:add(f_tcpi_retrans,v_tcpi_retrans)
t:add(f_tcpi_fackets,v_tcpi_fackets)
t:add(f_tcpi_last_data_sent,v_tcpi_last_data_sent)
t:add(f_tcpi_last_ack_sent,v_tcpi_last_ack_sent)
t:add(f_tcpi_last_data_recv,v_tcpi_last_data_recv)
t:add(f_tcpi_last_ack_recv,v_tcpi_last_ack_recv)
t:add(f_tcpi_pmtu,v_tcpi_pmtu)
t:add(f_tcpi_rcv_ssthresh,v_tcpi_rcv_ssthresh)
t:add(f_tcpi_rtt,v_tcpi_rtt)
t:add(f_tcpi_rttvar,v_tcpi_rttvar)
t:add(f_tcpi_snd_ssthresh,v_tcpi_snd_ssthresh)
t:add(f_tcpi_snd_cwnd,v_tcpi_snd_cwnd)
t:add(f_tcpi_advmss,v_tcpi_advmss)
t:add(f_tcpi_reordering,v_tcpi_reordering)
t:add(f_tcpi_rcv_rtt,v_tcpi_rcv_rtt)
t:add(f_tcpi_rcv_space,v_tcpi_rcv_space)
t:add(f_tcpi_send_reason,v_tcpi_send_reason)
t:add(f_tcpi_pacing_rate,v_tcpi_pacing_rate)
t:add(f_tcpi_max_pacing_rate,v_tcpi_max_pacing_rate)
t:add(f_tcpi_bytes_acked,v_tcpi_bytes_acked)
t:add(f_tcpi_bytes_received,v_tcpi_bytes_received)
t:add(f_tcpi_segs_out,v_tcpi_segs_out)
t:add(f_tcpi_segs_in,v_tcpi_segs_in)
t:add(f_tcpi_notsent_bytes,v_tcpi_notsent_bytes)
t:add(f_tcpi_min_rtt,v_tcpi_min_rtt)
t:add(f_tcpi_data_segs_in,v_tcpi_data_segs_in)
t:add(f_tcpi_data_segs_out,v_tcpi_data_segs_out)
t:add(f_tcpi_delivery_rate,v_tcpi_delivery_rate)
return true
end
function p_TcpInfo.dissector(buf,pkt,root)
if ScoreBoard_dissector(buf,pkt,root) then
else
data_dis:call(buf,pkt,root)
end
end
local tcp_table = DissectorTable.get("tcp.port")
tcp_table:add(80, p_TcpInfo)
end
参考了这篇文章《 Lua编写Wireshark插件实战》。
我的展示效果如下所示:
5.求助
我现在需要求助!我可以发100块RMB的红包!我的问题在下面的已有求助中体现,随后我给出我的中文说明:1>.《 How do I determine a TCP segment's length》
2>.《 need help: the code location of Ethernet trailer》
我的需求和他们的差不多。
我不想在tpacket_rcv中修改iph的tot_len,修改它是万不得已的办法!我希望的是,TCP/IP的解析照常进行,但不要把我附着的tcp_info信息作为Ethernet的Trailer来看待。如果哪位大师有办法,请帮助。
我不得不抱怨的是,为什么每次都是我遇到这种奇葩问题,为什么每次我遇到的问题都没有人回答,以至于很多时候,都是我自问自答,但现在,令人遗憾的2016年最后一天,我不再自问自答了,我要求助!我答应会发50到100的红包就一定会给,而且不光这个,以后如果有需要我帮忙的,我也会以此为乐,红包是小事,我认为交流可以共同进步,那才是大事。
这种问题已经被我解答了不下N次了,比如VMWare虚拟机里的nmap,nat问题等...唉,我竟然无语凝噎。
马上就2017年了,再见,令人不那么舒服的2016年...