首页 > 其他分享 >计算机网络实验 实验5 运输层和应用层协议解析

计算机网络实验 实验5 运输层和应用层协议解析

时间:2023-04-02 17:55:16浏览次数:53  
标签:HTTP 报文 SYN TCP 计算机网络 实验 服务器 观察 应用层

实验5 运输层和应用层协议解析

一、 实验目的

   本实验通过运用Wireshark对网络活动进行分析,观察TCP协议报文,分析通信时序,理解TCP的工作过程,掌握TCP工作原理与实现;学会运用Wireshark分析TCP连接管理、流量控制和拥塞控制的过程,发现TCP的性能问题。

二、 实验内容

任务1:TCP正常连接观察

实验准备:

 

 

 

用PC1 ping 一下PC2,看是否能ping通,观察到两主机能联通:

 

 

 

 

  1. 利用 python 自带的 SimpleHTTPServer 模块,在 PC2 上启动一个简易的 web 服务器。终端上运行 echo "TCP lab test" > index.html 创建 index.html 文件为测试站首页,运行 sudo python -m SimpleHTTPServer 80 启动一个简易 web 服务器;打开新终端,键入 ss -tln查看当前主机打开的 TCP 连接,确认 80 端口处于监听状态。

 

 

 

  1. 在 PC1 上打开一个终端,键入 sudo wireshark 启动抓包软件;再打开一个新终端,键入curl <PC2 的 IP> ;停止抓包,在 wireshark 过滤出 TCP 类型报文。观察首个 TCP 报文头,并分析各段值代表的意义。如果想要关闭相对序号/确认号,可以选择 Wireshark 菜单栏中的EditPreferenceprotocolsTCP,去掉 Relative sequence number 勾选项。使用 Wireshark 内置的绘制流功能,选择菜单栏中的 StatisticsFlow Graph, Flow Type 选择 TCP flows 可以直观地显示 TCP 序号和确认号是如何工作的。

 

 

 

实验要求:

  1. 利用Wireshark,抓包分析并截图,分析该报文TCP首部各字段的定义、值及其含义

(1)      利用Wireshark抓包并截图(保存在tcp1.pcapng中):

在Wireshark软件中设置display filter如下,进行报文过滤:

 

抓取到整个TCP报文流:

 

使用Statistics->Flow Graph工具,可以清楚地看到三次握手过程:

 

四次挥手过程:

 

可是,这里我观察到了一个“奇怪”的现象。不是说好了四次挥手吗?怎么只看到两次挥手。为了解决这个问题,我上网查找了很多资料。首先连接释放的时候不一定需要四次挥手,事实上,很多情况下都只有三次挥手。而满足三次挥手需要两个条件,当被动关闭方(上图的客户端)在TCP挥手过程中,[没有数据要发送]并且[开启了TCP延迟确认机制],那么第二次和第三次挥手就会合并传输,这样就出现了三次挥手。这里的合并传输也就是这个FIN包和ACK包合并传输了。

那么,分析完了四次挥手变成三次挥手的原因,可是我这里观察到的只有两次挥手啊,这是为什么呢?

这个问题困惑了我一整天,查阅资料发现,并不一定是客户端会主动断开连接,服务器端也有这个功能。于是我就去看了[FIN,ACK]之前的那个由服务器发给客户端的HTTP包,果然找到了答案:

 

我发现这个报文里的FIN标志位竟然是1,说明这是服务器向客户端发送的第一个FIN包,也就是第一次挥手。于是,四次挥手便再次显现出来。

(2)      分析报文TCP首部各字段的定义、值及其含义

这里,我选择了三次握手的第一个报文:SYN报文进行分析

 

下面依据TCP报文的首部格式分析该TCP报文首部各字段:

 

前两个字节是16位源端口号,该报文的源端口号为34318

 

接下来两个字节是16位目标端口号,该报文的目标端口号为80

 

接下来四个字节是32位客户端随机初始化的序列号,该报文的序列号为1551970934

 

接下来四个字节是32位的确认应答号,该报文是三次握手的第一个SYN报文,没有设置确认应答号,故该报文确认应答号为0

 

接下来4位bit是首部长度,该报文的首部长度为40Bytes

 

接下来6位标志位保留,报文中都为0

 

接下来6位标志位,该报文中只有SYN被置1

 

接下来两个字节是窗口大小,该报文的窗口大小为64240

 

接下来两个字节是校验和

 

接下来两个字节是紧急指针字段,该报文URG标志位为0,故这里的紧急指针字段也为0

 

最后的20字节是选项字段,这里不多分析

 

至此,该报文的首部各字段分析完毕。

  1. 画出该TCP流的流图

 

任务2:TCP异常传输观察分析

  1. 尝试连接未存活的主机或未监听端口

(1)      用 curl 访问一个不存在的主机 IP,抓包观察共发送了几次 SYN 报文。根据每次时间间隔变化,估算 RTO(重传超时)。

 

抓包观察到共发送了两次SYN报文:

 

 

估算RTO:

由上图可以大致估算出,RTO ≈ 9.514 – 6.443 = 3.071s

(2)      查看 Linux 主机的系统的 TCP 参数 SYN 重传设定:

`cat /proc/sys/net/ipv4/tcp_syn_retries`

 

(3)      更改 SYN 重传次数为 3:

`echo "3" > /proc/sys/net/ipv4/tcp_syn_retries`

注意,这里要切换到root用户下才能更改,否则会“Permission Denied”

 

(4)      再次 curl 访问,观察抓包内容。

 

 

(5)      关闭服务器端的 SimpleHTTPServer(ctrl+C 中断,或关闭所在终端),客户端 curl 访问服务器 80 端口,观察应答报文。

 

 

(6)      运行 nmap -sS <PC2 的 IP> 扫描服务器,并抓包。

 

 

(7)      在报告中总结以上观察结果,解释 SYN 扫描原理。

  1. 观察客户端发送了第一个SYN连接请求,服务器无响应的情景

(1)      服务器开启 telnet 或 ssh 服务,客户端先尝试连接服务器,连接成功后,在双方键入 ss -tan 查看所有 TCP 连接状态。我们看到的 TCP 连接建立过程同 1 中的 HTTP 访问类似。在客户端,利用 iptables 拦截服务器回应的 SYN ACK 包,命令如下:

` sudo iptables -I INPUT -s 192.168.13.128 -p tcp -m tcp --tcp-flags ALL SYN,ACK -j DROP `

为了让服务器能够开启ssh服务,需要先`sudo apt install openssh-server`

 

输入`sudo service ssh start`启动ssh服务

 

输入`sudo ps -e |grep ssh`查看ssh服务是否启动,观察到服务器已启动:

 

接下来尝试使用客户端连接服务器,服务器的ip为192.168.13.128

 

在客户机上使用`sudo ssh [email protected]`远程连接服务器(注意这里需要root权限,否则会Permission Denied):

 

看到如上界面说明成功远程连接到了服务器。

连接成功后,在双方键入`ss -tan`查看所有TCP连接状态:

 

 

在客户端输入命令` sudo iptables -I INPUT -s 192.168.13.128 -p tcp -m tcp --tcp-flags ALL SYN,ACK -j DROP `利用iptables拦截服务器回应的SYN ACK包:

 

(2)      再次尝试连接并启动 wireshark 抓包,并在双方多次用 ss -tan 观察 TCP 状态。

 

服务器:

 

客户机:

 

(3)      观察 TCP 的状态变化,分析 wireshark 捕获的 TCP 异常报文。

 

会产生这样的异常报文的原因是,由于我们设置了防火墙,阻塞了从服务器端发过来的SYN&ACK包,这样客户机发第一个SYN包后,就始终不会收到服务器端的SYN&ACK包,它就会以为是传输过程中丢包了,于是重新传输SYN包;而服务器那边也就不会收到客户机发来的三次挥手中的最后一个ACK包,超时后就会重传SYN&ACK包。这就是产生图中异常报文的原因。

(4)      服务端的 SYN-RECV 状态何时释放?

当终端显示”Connection timed out”时释放

(5)     SYN ACK 重传了几次,时间间隔有何变化?

 

可以观察到SYN ACK重传了11次

 

可以观察到时间间隔一开始呈指数级别增大,最后稳定在16s左右

(6)      参考 1 中的操作,在服务端修改 SYN ACK 重传次数 (tcp_synack_retries),再次观察,此任务结束后清空防火墙规则 (iptables -F)。

在服务端改重传次数为3次:

 

再次观察(注意这里我连续尝试连接了两次):

 

清空防火墙规则:

 

之后就可以顺利连接了:

 

任务3:拥塞控制

  1. 配置虚拟机设置:

 

  1. 使用ftp传输大文件:

首先,我自己编写了一个程序gen_ran.c,使用命令`./gen_ran <filename> [size(MB)]`

 

该程序可生成一个大小为size的文件,名称为filename,size可选,默认为100MB

使用`./gen_ran scpfile`生成一个大小为50MB的大文件scpfile:

 

 

使用ftp传输该文件,同时使用wireshark抓包:

首先在终端输入命令`ftp 192.168.13.128`与服务端连接:

 

连接成功后显示如上。

然后使用命令`put ftpfile`传输大文件,同时wireshark抓包:

 

 

  1. 传输完毕,进行结果分析(该结果存放在ftp.pcapng中):

 

 

刚开始时,执行慢开始算法,说是慢开始,其实它并不慢,因为它是呈指数增长的。

超时,网络发生拥塞,这时可能连续收到三个重复确认,执行快恢复算法,拥塞窗口变为原来的一半。

然后拥塞避免算法,加法增大拥塞窗口。

任务4:HTTP协议分析

  1. 搭建HTTP1.0服务器

任务1搭建的即为HTTP1.0服务器。使用命令` sudo python -m SimpleHTTPServer 80`在虚拟机UbuntuV2上搭建服务器,在虚拟机Ubuntu 64bit上使用命令`curl 192.168.13.128`向服务器请求数据,

即可抓到HTTP1.0报文:

 

 

 

data的内容即为网页中的内容。

  1. 搭建HTTP1.1服务器

在Ubuntu 64bit中编写python脚本(源代码文本见附件),将协议的版本类型修改为HTTP/1.1,即可搭建HTTP1.1服务:

 

终端输入命令运行服务器,并打开wireshark进行抓包:

 

输入后,终端会自动打开一个网页,该网页是我在该目录下写的一个html文件(源码见附件):

 

可以使用wireshark捕捉到HTTP1.1的报文:

 

  1. 搭建HTTP2.0服务器

在搭建HTTP1.1的基础上,将协议的类型修改为HTTP2.0即可:

 

与上一步类似,使用wireshark抓包,可以抓到TLS1.2的报文,该报文是HTTP2.0报文加密后的结果:

 

若想要分析HTTP2.0报文,需要对wireshark进行解密,步骤如下:

(1)     配置系统环境变量:

在~/.profile文件下加入`export SSLKEYLOFFILE=/home/rongrong/Desktop/sslkey.log`

终端输入命令:`source ~/.profile`

重启Ubuntu。

然后打开一个浏览器,会看到密钥已经写入到sslkey.log文件中了:

 

(2)     配置Wireshark:

Wireshark->Edit->Protocols->TLS下,将(Pre)-Master-Secret log filename设置为刚刚设置的环境变量值:

 

然后就可以看到HTTP2.0报文

 

点开该报文可以看到,内容与index.html中的一致:

 

 

  1. 接下来对三种HTTP版本的报文进行分析

(1)     HTTP1.0

 

右键点击该报文,选择Follow->HTTP Stream可以追踪HTTP流:

 

 

其中,红色的部分是请求格式,蓝色的部分是响应格式,可以很清楚地看到该HTTP流的请求响应流程。

可以分析一下HTTP1.0的报文结构:

起始行是状态行:HTTP/1.0 200 OK

可以看到版本为HTTP1.0;状态码为200,表示成功;短语为OK。

(2)     HTTP1.1

 

与上一步的(1)相同,可以肯定看到HTTP stream:

 

可以对HTTP1.1的请求报文结构和响应报文结构进行分析:

起始行除了版本,其他与HTTP1.0相同

可以看到Server与HTTP/1.0不同,HTTP/1.1的Server为nginx;

HTTP/1.1报文还会将使用的是什么浏览器显示出来:Via: 1.1 google

HTTP/1.1相比于HTTP/1.0做了如下几点优化:

  • 使用长连接的方式改善了HTTP/1.0短连接造成的性能开销
  • 支持管道(pipeline)网络传输,只要第一个请求发出去了,不必等其回来,就可以发第二个请求出去,可以减少整体的响应时间。

(3)     HTTP2.0

 

与前面两步相同,我们同样可以在这里看到HTTP2.0的HTTP stream:

 

HTTP/2.0协议与HTTP/1.1协议有很大的不同,前者把后者存在的性能问题全部一一攻破了。

HTTP/2.0相比于HTTP/1.1在性能上的改进有如下几点:

l  头部压缩

HTTP/2 会压缩头(Header)如果你同时发出多个请求,他们的头是一样的或是相似的,那么,协议会帮你消除重复的部分。

这就是所谓的 HPACK 算法:在客户端和服务器同时维护一张头信息表,所有字段都会存入这个表,生成一个索引号,以后就不发送同样字段了,只发送索引号,这样就提高速度了。

l  二进制格式

HTTP/2 不再像 HTTP/1.1 里的纯文本形式的报文,而是全面采用了二进制格式,头信息和数据体都是二进制,并且统称为帧(frame):头信息帧(Headers Frame)和数据帧(Data Frame)。

这一点观察报文也能稍微看出一些区别。

 

这样虽然对人不友好,但是对计算机非常友好,因为计算机只懂二进制,那么收到报文后,无需再将明文的报文转成二进制,而是直接解析二进制报文,这增加了数据传输的效率

比如状态码 200 ,在 HTTP/1.1 是用 '2''0''0' 三个字符来表示(二进制:00110010 00110000 00110000),共用了 3 个字节,如下图:

 

在 HTTP/2 对于状态码 200 的二进制编码是 10001000,只用了 1 字节就能表示,相比于 HTTP/1.1 节省了 2 个字节,如下图:

 

l  并发传输

l  服务器主动推送资源

三、 实验结果分析

本次实验遇到了很多奇怪的现象,比如对于任务1,始终无法观察到连接释放时候的四次挥手,只能观察到两次挥手:由客户机向服务器发送一个FIN包,然后服务器再向客户机发送一个ACK包,然后就结束了,并没有观察到服务器向客户机发送的FIN包,如下图:

 

可以看到只有两次。

多次尝试好不容易抓到了一个四次挥手的报文,可是奇怪的事情又发生了。按理来说第一次的FIN包应该由客户机发给服务器才对,但是我观察到的却是服务器先发送非客户机:

 

发生这个现象的原因可能是HTTP/1.0是短连接的,每发起一个请求都要新建一次TCP连接,这样服务端就会先发FIN包;若是使用HTTP/1.1则不会是这样的现象。

 

经过我的不懈努力,搜索了大量的有关TCP抓包的资料,终于解决了上述两个问题。

以及在做观察快恢复现象的时候无法观察到与教材上相同的现象,只能根据包的数量大致分析这个阶段是在执行哪个算法。

除此以外,本次实验都完成地比较顺利。

四、 实验小结与感想

据老师所说,本次实验是最后一个需要写实验报告的实验。太好啦!终于不用写实验报告啦!!!

不过,和wireshark打了快一个学期的交道了,我深深感受到了wireshark这个抓包工具的强大。它除了可以抓包外,还提供了可视化分析网络包的图形界面,还内置了一系列的汇总分析工具。就拿本次实验来说,我就用到了许多除了抓包以外的工具,比如Flow Graph以及IO Graph里面的HTTP Stream等工具。通过这些工具分析流,可比光看报文方便多了。

五、 思考题

  1. 在 TCP 状态机中,有些状态停留时间较长,易观察到,有些状态很短暂不易观察到。试列出不易观察到的状态,并考虑观察到它们的可能方法。

不易观察到的状态有: FIN_WAIT_1、FIN_WAIT_2、CLOSE_WAIT、LAST_ACK。可以阻断中间的某个报文,以观察到接下来预计达到的状态。

  1. TCP 在不可靠的 IP 层上建立了可靠的端对端连接,如果要在不可靠的 UDP 上建立可靠的端对端传输系统,需要考虑哪些方面?

现在市面上已经有基于 UDP 协议实现的可靠传输协议的成熟方案了,那就是 QUIC 协议,已经应用在了 HTTP/3。

要基于 UDP 实现的可靠传输协议,那么就要在应用层下功夫,也就是要设计好协议的头部字段。

需要把TCP 可靠传输的特性(序列号、确认应答、超时重传、流量控制、拥塞控制)在应用层全部实现一遍。

六、 附件

gen_ran.c

#include<stdio.h>

#include<stdlib.h>

#include<time.h>

#include<stdint.h>

 

#define ONE_MB_SIZE 262144

int32_tout[ONE_MB_SIZE];

 

intmain(intargc, char* argv[]) {

    if(argc < 2 || argc > 3) {

        fprintf(stderr, "Usage: ./gen_ran <filename> [size(MB)]\n");

        exit(1);

    }

    int size;

    if(argc == 2) size = 100; // 如果只有两个参数即只有一个文件名称默认生成100MB的文件

    else size = atoi(argv[2]);

    FILE* outfile;

    if((outfile = fopen(argv[1], "wb")) == NULL) {

        fprintf(stderr, "open error\n");

        exit(1);

    }

    srand((unsignedint)time(0));

    int i, j;

    for(i = 1; i <= size; i++) {

        for(j = 0; j < ONE_MB_SIZE; j++) {

            out[j] = rand();

        }

        fwrite(out, sizeof(int32_t), ONE_MB_SIZE, outfile);

    }

    fprintf(stdout, "random input file %s was generated successfully\n", argv[1]);

    exit(0);

}

 

HTTPserver.py

   import json

from http.server import HTTPServer, SimpleHTTPRequestHandler

import webbrowser

 

ip = "localhost"  # 监听IP,配置项

port = 8800  # 监听端口,配置项

index_url = "http://%s:%d/index.html" % (ip, port)  # 监听主页url,配置项

 

# 创建http server

classGetHttpServer(SimpleHTTPRequestHandler):

    protocol_version = "HTTP/1.0"

    server_version = "PSHS/0.1"

    sys_version = "Python/3.9.x"

    target = "./"  # 监听目录,配置项

 

    defdo_get(self):

        ifself.path.find("/json/") > 0:

            print(self.path)

            self.send_response(200)

            self.send_header("Content-type", "json")

            self.end_headers()

            req = {"success": "ok"}

            self.wfile.write(req.encode("utf-8"))

        else:

            SimpleHTTPRequestHandler.do_GET(self)

 

    defdo_post(self):

        ifself.path == "/signin":

            print("postmsg recv, path right")

        else:

            print("postmsg recv, path error")

            data = ""

            data = json.loads(data)

            self.send_response(200)

            self.send_header("Content-type", "text/html")

            self.end_headers()

            rspstr = "recv ok, data = "

            rspstr += json.dumps(data, ensure_ascii=False)

            self.wfile.write(rspstr.encode("utf-8"))

 

defhttp_server():

    server = HTTPServer((ip, port), GetHttpServer)

    try:

        # 弹出窗口

        webbrowser.open(index_url)

        # 输出信息

        print("服务器监听地址: ", index_url)

        server.serve_forever()

    exceptKeyboardInterrupt:

        server.socket.close()

 

# 执行服务器脚本

http_server()

 

index.html

<!DOCTYPEhtml>

<htmllang="en">

<head>

    <metacharset="UTF-8">

    <metahttp-equiv="X-UA-Compatible"content="IE=edge">

    <metaname="viewport"content="width=device-width, initial-scale=1.0">

    <title>Document</title>

</head>

<body>

    hello world

</body>

</html>

标签:HTTP,报文,SYN,TCP,计算机网络,实验,服务器,观察,应用层
From: https://www.cnblogs.com/i-rong/p/17280903.html

相关文章

  • 软件测试-白盒测试实验
      语句覆盖   输入   YearMonthDay测试路径测试结果e11301-2-3-17输入日期无效20223281-2-3-4-5-6-7-8-9-10-11-12-13-14-15-16-17星期一    判定覆盖、条件覆盖、判定/条件覆盖、组合覆盖   输入   ......
  • 实验3
    实验1#include<stdio.h>#include<stdlib.h>#include<time.h>#include<windows.h>#defineN80voidprint_text(intline,intcol,chartext[]);//函数声明voidprint_spaces(intn);//函数声明voidprint_blank_lines(intn);//函数声明......
  • 实验3
    task1.c代码#include<stdio.h>#include<stdlib.h>#include<time.h>#include<windows.h>#defineN80voidprint_text(intline,intcol,chartext[]);voidprint_spaces(intn);voidprint_blank_lines(intn);intmain(){intline,......
  • 202031607129-杨炜 实验一 软件工程准备—博客园技巧与博客首秀
    项目内容班级博客链接2023年春软件工程(2020级计算机科学与技术本次作业要求链接实验一软件工程准备我的课程学习目标注册博客园和Github账号,学习使用博客园,了解Github的基本操作。本次作业在哪些方面帮我实现学习目标按照实验内容,借助各种链接的例子,一步步......
  • 实验3
    task1程序源码:#include<stdio.h>#include<stdlib.h>#include<time.h>#include<windows.h>#defineN80voidprint_text(intline,intcol,chartext[]);voidprint_spaces(intn);voidprint_blank_lines(intn);intmain(){intline......
  • 实验三
    task1源代码#include<stdio.h>#include<stdlib.h>#include<time.h>#include<windows.h>#defineN80voidprint_text(intline,intcol,chartext[]);voidprint_spaces(intn);voidprint_blank_line(intn);intmain(){intl......
  • 实验3
    TEST1源码:#include<stdio.h>#include<stdlib.h>#include<time.h>#include<Windows.h>#defineN80voidprint_text(intline,intcol,chartext[]);voidprint_spaces(intn);voidprint_blank_lines(intn);intmain(){  intline,col,i;   ......
  • 实验三
    实验任务1源代码#include<stdio.h>#include<stdlib.h>#include<time.h>#include<windows.h>#defineN80voidprint_text(intline,intcol,chartext[]);voidprint_spaces(intn);voidprint_blank_lines(intn);intmain(){i......
  • 实验三
    task.1#include<stdio.h>#include<stdlib.h>#include<time.h>#include<windows.h>#defineN80voidprint_text(intline,intcol,chartext[]);voidprint_spaces(intn);voidprint_blank_lines(intn);intmain(){intline,co......
  • 实验一-密码引擎-3-加密API研究
    密码引擎API的主要标准和规范包括:1微软的CryptoAPI2RAS公司的PKCS#11标准3中国商用密码标准:GMT0016-2012智能密码钥匙密码应用接口规范,GMT0018-2012密码设备应用接口规范等研究以上API接口,总结他们的异同,并以龙脉GM3000Key为例,写出调用不同接口的代码,提交博客链接和代......