这篇文章原文内容是笔者大学时网络系统集成的笔记,由于最近开始复习计算机网络基础,为了体系化知识。
ifconfig
可以看到,笔者客户端所使用的ip为192.168.0.127
网口名称为ens
tcpdump
监听对应网口的ping 192.168.0.128
的数据包 ,这里笔者就简单介绍一下下面命令的意思 -i
即指定抓包的网口,由于上文笔者使用ifconfig看到网口名为ens33
,所以这里-i所指定的端口就是ens33
,后续跟着一个icmp就是icmp协议的不多赘述,使用and拼接后续的条件这里指明了笔者目标服务器的ip 192.168.0.128
,后续再加一个-nn即可表示我们抓到的包不解析为主机名,显示的是ip
和port
。最后一个-w ping.pcap意思为将抓到的数据包写到ping.pcap文件中
tcpdump -i ens33 icmp and host 192.168.0.128 -nn -w ping.pcap
tcpdump更多的命令可以参考下表
ping 192.168.0.128 -I ens33 -c 3
ping
结束后,将服务器中的ping.pcap文件导到windows
系统中用wireshark
打开,最终我们就可以看到下面这样一个文件echo request
,服务端就会回应一个echo reply
我们不妨双击一个请求查看他的数据包的详细内容,可以看到wireshark的强大,他将物理层到网络层的所有信息都捕获到了
首先看看数据链路层,他指明了上层数据的类型,以及源mac地址和服务器(192.168.0.128)的mac地址
再来看看网络层抓到的内容
最后看看icmp
协议,可以看到报文内容很简单,就是一个简单的echo request
总结一下,上文的数据分层结构如下图所示,每一层都会在数据包中添加一个头。
这里我们就编写了一个简单的http请求方法
这里笔者为了打出来的jar不带本号,遂在pom
配置中写死了fileName
<finalName>demo</finalName>
使用idea
右边的maven
点击install
一下生成一个jar包
可以看到打出来了一个名为demo的jar包,我们把它放到模拟服务器的虚拟机中
使用java命令后台运行
nohup java -jar demo.jar &
在服务器上测试使用可通
curl 127.0.0.1/shark/hello
可以看到响应了一个hello world的字符串,我们终于可以抓包干活了
tcpdump
监听本机关于192.168.0.128
的http
请求tcpdump -i any and host 192.168.0.128 and port 80 -w http.pcap
curl 192.168.0.128/shark/hello
CTRL+c
终止监听,并将抓到的数据包的pcap文件导入wireshark
中,于是我们就得到下图所示的文件我们可以看到正常的三次握手过程以外,还看到了http交互过程,看抓到的数据包,我们不难看出,在建立tcp连接后,客户端会向服务端发送一个http请求,即一个get的请求。
然后服务端发一个tcp的ack数据包表示确认收到,再将一个text类型的数据发给客户端,客户端会给服务端一个ack确认本次会话就结束了。
在4次挥手时,我们看到一个很奇怪的现象,看着我们抓包的情况来看,tcp断开连接好像3次交互就够了,难道书本上教的内容是错误的嘛?
并不是,就以本次连接为例,由于http请求的数据不大,在断开连接的时候,服务端并没有要再传给客户端的数据,所以将在服务端向客户端发送确认报文ack时,捎带了fin数据包。这就造成了断开连接只要3次的情况。
当然,要是觉得这样看不够直观,我们完完全全可以使用wireshark自带的统计流程图看看tcp交互过程
如下图所示,点击流量图
再将流类型改为tcp flows即可
这样3次握手、建立连接、4次挥手的过程就很直观了
将PSH(push)置为1,即将数据直接向上层传输,这一点我们可以在http请求的数据包的传输层看到这个标志位
syn-send :意味同步发送,syn为同步位,所以在建立连接时,都需要将syn位置为1
当URG = 1时表明紧急指针字段有效,他告诉系统此报文段中有紧急数据,应尽快传送,而不要按原来的排队顺序来传送,发送方的TCP就把紧急数据放到本报文段数据的最前面。URG标志位要与首部中的紧急指针字段配合使用,紧急指针指向数据段中的某个字节,(数据从第一个字节到指针所指的字节就是紧急数据)。值得注意的是即使窗口为0时也可以发送紧急数据,紧急数据不进入接收缓冲区直接交给上层进程。
当两个应用进程进行交互式通信时,有时客户发一个请求给服务器时希望立即能够收到对方的响应,这种情况下,客户应用程序通知TCP使用推送(push)操作,TCP就把PSH置为1,并立即创建一个报文段发送过去,类似的服务器的TCP收到一个设了PSH标志的报文段时就尽快将所有收到的数据立即提交给服务进程,而不在等到整个缓存都填满了再向上交付。
但URG为1时,表示从数据开始到紧急指针部分,不必进入缓冲区直接交付上层应用.
而PSH为1时,通知缓冲区,数据进入缓冲区,只要数据完整的接收完后立即交付给上层应用.(而不必等缓冲区满)
由于tcp要实现全双工通信,所以要求双方都具备收发功能,这就要求第一次建立请求之后,双方都要各自进行一次收发。
这些内容我们完完全全可以在上文的抓包中得到印证。
三次握手的协商参数
切分后的报文段的第一个字节字节号作为数据包的序列号。
为保证tcp可靠传输的一个方案,其特点就是累计确认,例如 接受方受到连续按序的五个报文时候发送确认消息6,就代表前五个数据包都收到了,期待第六个数据包。
哪个报文没收到就要求发送方重发哪个报文,但缺点也很明显,效率低。
每次批量发送数据包,由于与批量传输,所以适用于通信状况比较稳定的场所。但数据包不可达时,选用后退n或者选择重发进行数据重传。
接收方接收后回复ack时顺带一些需要发送给发送方的数据。
在tcp建立连接时,接收方会高速发送方他的接收窗口的大小,发送方的发送窗口就会根据接收方所能接受的窗口动态的调整窗口大小。
工作原理即接收方发送确认包时,发送方动态向右滑动窗口继续发送新数据,且该窗口始终保持接收方的当前所能接受的窗口大小。
拥塞窗口根据网络拥塞程度动态变化,一旦网络拥塞就减小拥塞窗口,以减少发送到网络中的报文数量。
这其中有个关键字 MSS(最大报文段大小),取值一般取1460(tcp头和ip头各占20字节,加起来1500刚好是以太网标准的mtu)
流量控制是收发两端的问题,即点对顶啊问题,通过抑制发送方发送数据的大小进行控制。这其中就涉及到一个重要的概念,缓存,如下图所示:通信两端在每道工序中货扮演生产者或扮演消费者的角色,先将数据放入缓存,一旦缓存一满就将数据交予下一工序,并提醒下部工序的角色做好准备,正是这样以套紧密联接的分工,才保证的流量控制。
而拥塞控制是全局性问题,无法做到完全解决,只能说在收发两端使用慢启动和拥塞避免的手段尽可能的优化。
如下所示,注意三次握手和数据传输时ack的叠加方式
三次握手时,数据的length为0,所以ack只能基于发送方的seq+1
数据传输时,length!=0,所以ack基于发送方的seq+length+1
因为通信是双向的,所以需要经过双方都确认才可,而结束是单方面要求,必须经由双方各自全双工通道都断开连接才可。
对方tcp服务关闭
Ddos攻击(分布式拒绝式服务攻击)
发送方连方 1000 2000 3000 4000 5000 6000的报文,接收方回复ack序列号为4000,若为gbn需发送那些报文?若为sr则又要发送那些报文?
答:gbn回4000则是要求发送4000代表前4000的都收到了,4000字节段的报文可能出现问题,要求对方发送4000之后的报文。
而sr则是代表3000段收到了要求发4000,其他报文段没有回复确认同样需要发送。即sr回复4000需要发送1000 2000 4000 5000 6000
a向b发送数据,序列号分别是70 110。
拥塞问题为何超时重传无法解决?
答:重传会导致更多的丢失包,进而导致网络崩坏。
tcpdump -i any tcp and host 192.168.0.128 and port 80 -w synClose.pcap
这步无需解释,笔者直接在虚拟机中将网络关掉,或者直接关机
如下所示,为了更直观的看到时间,笔者在命令前后加了date
date;curl 192.168.0.128/shark/hello;date
从抓出来的数据来看,由于我们将服务器的网络断了,导致客户端的syn请求没有收到服务端的ack,所以客户端以为服务端没有收到,就重试了5次。
为什么是5
次呢?我们可以通过操作系统内核的配置中一探究竟
键入以下命令
cat /proc/sys/net/ipv4/tcp_syn_retries
输出
5
这就是为什么一旦syn
没有收到对端的ack
,就会超时重试的次数
如果读者不相信这个结果可以改变结果,不妨修改这个值
echo 2 > /proc/sys/net/ipv4/tcp_syn_retries
可以看到经过笔者的修改后,TCP建立连接丢包,就会重试两次。
还有一点读者需要留心,就是重试等待时间RTO,如下图所示,可以看出每次重试的时间都会基于上一次的等待时间乘2
这里只是做个补充,由于笔者上一个实验将服务器网络断开了,这里要再次将其连接回来
tcpdump -i any tcp and host 192.168.0.128 and port 80 -w rejectServer.pcap
iptables -I INPUT -s 192.168.0.128 -j DROP
date;curl 192.168.0.128/shark/hello;date
可以看到下面这样一张图,是不是很不直观,无妨,我们使用tcp flows查看一下结果
首先我们看看客户端,除去第一次syn的数据包以外,由于我们把服务端的确认包拒绝了,导致客户端以为他发的syn数据包没有被收到,遂重试了5次,可以看到RTO也是基于上次等待时间乘2,符合我们上个实验所得结论
可以看出,服务端在第一次发syn+ack(同步+确认收到)
包之后,每次客户端发syn包都会回复一个syn+ack(同步+确认收到)
包,在下图用红色箭头所指。
一旦超时没有被收到就会再发一次,下图用蓝色箭头所指。如此往复5次,最终确认无法和对方建立连接。直接放弃建立连接。
需要补充一点,可能会有读者疑问,为什么iptables明明drop这个数据包,为什么tcpdump还能看到,如下所示,数据包流向iptabls之前会经过tcpdump,所以对于input的数据tcpdump可以看到,而出去的就看不到了
进来的顺序 Wire -> NIC -> tcpdump -> netfilter/iptables
出去的顺序 iptables -> tcpdump -> NIC -> Wire
键入以下命令,我们可以得到数字5,由此得出上文中syn+ack包重试次数为5,同样的,如果读者不信,可以将这个值修改一下抓包看看结果,这里就不做演示了
cat /proc/sys/net/ipv4/tcp_synack_retries
由于上一个实验将规则drop了,这里需要改为accept
iptables -I INPUT -s 192.168.0.128 -j ACCEPT
命令如下所示意为将客户端的确认包丢掉
iptables -I INPUT -s 192.168.0.127 -p tcp --tcp-flag ACK ACK -j DROP
date;curl 192.168.0.128/shark/hello;date
同样看到密密麻麻的一排,我们将其使用统计图的形式来分析结果
首先看看服务端的数据包,可以看到由于ack包被drop了,服务端认为自己的syn+ack没有被收到,遂按照上文tcp_synack_retries
重试了5次,发现失败,遂不了了之
所以我们在服务端键入如下指令
netstat -napt |grep 192.168.0.127
输出
tcp 0 0 192.168.0.128:80 192.168.0.127:58696 SYN_RECV
我们都知道客户端向服务端发送syn包之后,客户端会进入SYN_SEND
,等待服务器确认包
当服务端收到syn包后,立刻回一个syn+ack,此时服务端的状态就是SYN_RECV
意为收到客户端的syn包,发送一个ack等待客户端确认,也就是上文的状态。
再来客户端的数据包可以看到笔者矩形所圈的部分就是客户端发送ack后,认为已经和服务端建立连接了此时状态为established。这一点我们可以按照如下指令即可看到结果
netstat -napt |grep 192.168.0.128
遂发送了一个http请求,结果没收到结果,发送了一个psh+ack再找服务端要一次,还是没要到,这时候客户端就认为这条连接有问题了。
所以后面15箭头所示的,超时重传请求15次去确认这条tcp连接是否可用。
这个15我们可以键入以下命令查看
cat /proc/sys/net/ipv4/tcp_retries2
需要补充的是tcp_retries2意思为在丢弃激活(已建立通讯状况)的TCP连接之前﹐需要进行多少次重试。默认值为15
碍于本文篇幅原因,很多读者可能对文章某些内容云里雾里,以下便列出笔者认为阅读本文所需要补充的知识
tcp_syn_retries等参数详解
Linux 防火墙教程:IPTables 表、链、规则基础
Linux下如何通过一行命令查找并杀掉进程
ICMP属于哪层协议
SYN_RECV的意思
一文读懂OSI七层模型和TCP/IP五层模型
TCP 实战抓包分析