众所周知,计网被评为最困的计算机专业课,俗称计算机中的语文。👴看了《计算机网络-自顶向下方法》(后文简称CNTDA)之后,觉得翻译就像汤姆叔叔的烂苹果派一样糟糕,上帝啊,我发誓会狠狠踢他的屁股。建议带🔥去看英文原版。
但是👴最近接触的许多实验还是很好玩的,于是本文面向对计算机网络完全0基础的同学,试图通过全程动手实操学习计网。
主要工具:
- wireshark是坠nb的网络封包分析软件。就是用来抓包的。
下载:https://www.wireshark.org/download.html 教程:https://www.javatpoint.com/wireshark
- scapy库是python的网络编程库,可以让你细致入微的操纵网络流量。就是用来发包的。
- //不要和爬虫库scrapy混淆
scapy文档:https://scapy.readthedocs.io/en/latest/ 中文版:https://www.osgeo.cn/scapy/introduction.html //有些翻译错误
最后有一个敲代码顺手的python环境和一个能正常监听的wireshark就可以了。
计网基本概念
💣包(package)
等等,啥是“抓包”?啥是“发包”?我只知道A大下包。
当然,下包不仅仅是一个 CSGO 术语,在计算机网络中,包(package)有多个近义词,包括:报文/数据报(Datagram),分组/封包(Packet)……根据语境不同而区分,但大致指的是同一件事情:即网络中真正流动着的东西,我们希望网络来传递的东西。只不过“包”是最通俗的叫法,那么抓包和发包就不难理解了。
你还想问,包到底长什么样?众所周知,快递由包装和里面的东西组成,其实网络上的封包也差不多,也大致都有两部分:
- 包头,学名首部(Header)——快递包装上的标签,写着目的地址,联系电话,快递号等信息
- 包体,学名载荷(Payload)——快递要运输的货物本身。某些语境下也喜欢称为报文。
当然,网络封包归根结底还是线性的比特序列,于是我们需要包头来识别这个封包的相关信息,就像看快递先看标签一样。
另外,一个协议的封包也可以成为另一个协议的载荷,后面你会看到诸如pkt.payload.payload.payload.payload
的套娃用法,要理解这种套娃,还需要知道分层思想。
🍰分层(layering)
CNTDA 中用邮政系统类比计算机网络,这是最常用的例子,这里我们用快递物流网来举例。随便打开你的网购记录,你会发现快递物流大概经过以下过程:
- 客户发货:把货物和地址交给快递点
- 快递网点揽件:包装货物而变成包裹;包裹被送往最近的中转中心
- 中转中心运输:根据包裹目的地不同,分拣并装车运输给不同的中转中心;若收到本片区的包裹,卸车并分拣给不同的网点
- 快递网点派送:按包裹的地址,快递员送货上门
- 客户取件:拆箱,拿到货物,确认无误签收
你知道发一个快递要经历怎样的困难吗?你不知道,你只关心你自己。这里的重点是,客户不需要关心中转中心如何指挥重型货车或飞机,网点也只需要关心如何包装好客户的货物。快递网络明显的呈现出三层的分层架构,每一层之间只需要关心自己的工作,并和相邻的层交互。这就是应对复杂系统的组织方法——分层。
课本上会提到OSI七层模型或者TCP/IP五层模型,这里的模型全称是协议分层模型,又来新词了,别急,后面还有:
- 协议(Protocol):同一层级内的交互规则。//横向
- 服务(Service):不同层级间的交互规则。//纵向
每一层的工作,就是调用下层的接口,并为上层提供服务。接口(Interface)和服务的区别是,服务作为实体,由本层负责实现,暴露出接口供上层调用;而接口则是抽象的,本层并不知道下一层的可靠性。
由此你能否看出分层思想的优越性?每一层只关注自己的实现,于是大问题被分解成了小问题。好比一个总工作量100的问题,不了解分层思想的你只能10+10+10+……=100;而分层思想提供了乘法法则,于是你可以通过10*10=100,只需要完成20工作量。//个中思想也体现了OOP中的解耦。
上述类比中标注了一些对应关系:
- 封包(Packaging):包装,货物→包裹。信息在层次间传递的过程就是封包/解封的过程。
- 路由(Routing):分拣。根据包裹上的标签,决定包装的去向。
可以看到,每一层都有自己的“货物”,比如中转中心的载荷是满载包裹的长途货车而不是单个包裹。报文在每一层都被封装并交给下一层,要想得到原始报文只能一层一层解开,操作模式类似栈。由此协议分层模型也被简称为协议栈(Protocol stack)。
最后简单解释五层模型每一层的分工,自底向上顺序:
- 物理层:对接物理介质,运输比特
- 提供基于比特的通信路径
- 链路层:将路径串联成链
- 提供基于链路的接入、交付、和传输服务
- 网络层:将链路编织成网
- 提供任意两主机之间的通信
- 运输层:将主机的通信分解为进程的通信
- 提供进程间的逻辑通信
- 应用层:实现用户需求
- 向用户提供透明可靠的网络服务
偶剋!你已经了解了分层思想,下面来设计互联网吧!
⌚️开始实验
有关计网的学习顺序自古就有自顶向下还是自底向上的分歧,余以为只要理解了分层思想,顺序便不算很重要。本系列实验将遵从浅入深出的原则,从应用层逐步深入到链路层再返回应用层,同时难度不断加大。
实验来源:
- 👴自己:0x10, 0x20
- SEEDLab,雪城大学的信息安全课配套实验,网络安全部分。国内知名度不高所以值得一做。官方网站
- CNTDA 实验:GIthub上抄的作业
实验代码仓库:lonelyuan/ComputerNetwork-exp (github.com)
实验编号规则:0xabn
- a:层级:1 - 应用层;2 - 传输层;3 - 网络层;4 - 链路层;5 - 物理层
- b:难度:0 - ⭐;1 - ⭐⭐;2 - ⭐⭐⭐;3 - ⭐⭐⭐⭐;4 - ⭐⭐⭐⭐⭐;
- n:重复难度则再加一位编号
//【想看哪个没更新的可以催👴】
0x10 应用层: Server | ⭐
目标:用scapy/socket做一个静态服务器。
- 实际上,python3已经自带了一个简易http服务器:
|
|
此时浏览器访问:localhost:8000
,如果当前目录下有index.html
文件,浏览器即可显示该html文档。这条命令表明,调用python标准库http
的server方法,默认会在8000端口启动一个http服务器。该服务器也是基于另一个python标准库socket
编写的,本实验的目标即是自己实现类似的功能。
但首先我们需要了解一些前置知识:
-
C/S架构(client-server):互联网的基本模型。通信的双方通常分成两个角色:
- 发起的一方称为客户端(C),即前端。
- 接收的一方称为服务端(S),即后端。为了保证随时接收请求,服务端需要持久监听某通信端口
-
URL(统一资源标识符):俗称的网址,也就是互联网上的地址。
- 完整语法:
[协议名]://[用户名]:[密码]@[服务器地址]:[服务器端口号]/[路径]?[查询字符串]#[片段]
- 完整语法:
-
HTTP协议:应用层最普遍的文本协议之一。文本协议表示其所有内容都是可读的,其主要格式如下:
|
|
- HTML:标记语言,用<>组织起网页的骨架。浏览器会把HTML源码渲染成好看的网页。
- socket:逻辑通信的端点。
- socket是逻辑通信的接口。上文提到网络层为运输层和应用层提供了点到点的逻辑通信服务,该服务的基本接口就是socket。
- socket是通信端点的抽象。它将进程/应用和
(主机host,端口port)
二元组绑定,于是通过(host,port)
即可标记网络上的进程。- 一个主机有一个地址和多个端口。地址和端口的关系,就像房子和窗户的关系。
- socket由操作系统提供。本实验用到的是python对socket的封装,但不管换什么语言本质上都是系统调用。
- //其中文翻译“套接字”非常具有误导性,建议直接用英文单词。
最后,建议花5分钟通读《图解HTTP》前6章(或者《CNTDA》2.1-2.2节),你就能轻松理解上述术语。
Guidelines
-
Socket通信
要使用socket通信,通信双方都需要持有一个socket对象,其主要方法和生命周期如下:
|
|
于是我们可以建立起服务器代码的框架:
|
|
- It’s worth noting that,服务端socket并没有发送任何数据!
accept()
方法将返回一个客户端socket对象,由这个socket执行数据的收发。这样做的原因是为了实现多路复用,即让服务器支持多个连接同时通信。 - 于是我们可以看到,对每个TCP连接,都有一对socket存在于通信的两端。而服务端socket仅仅做了管理连接的工作,他们放在一个类里,是出于简化代码的考虑。(当然实现多路复用的方式不只有一种。
现在,你可以自己尝试编写socket客户端跟该服务器进行明文的通信。不过我们的目标是HTTP服务器,先复习一下HTTP协议格式,状态码,首部等知识吧。
-
HTTP解析
如果编程能力尚可,你可以自己写HTTP类来把报文解析成对象。这里还是用现成的,scapy库提供的HTTPRequest
和HTTPResponse
类。
|
|
-
do_dissect()
方法将字符串解析为对象 -
HTTP()/res/body
:scapy核心语法/
,表示协议栈的堆叠,可以链式调用。 -
这里的类型为:
HTTP / HTTPResponse / Raw
,之所以要这样三层表示,是因为HTTPResponse/HTTPRequest类仅仅是一个中间层,如果没有HTTP层,scapy会报warning。 -
raw()
方法返回封包的字节数组,可以看到在socket之上,我们先把报文转化为对象,解析之后再返回报文。 -
观察封包的常用方法还有:
|
|
现在运行服务器,用浏览器访问localhost:8000
,你可以看到大大的“Hello”了!
Task
下面的任务交给你,目标是尽量接近python自带http服务器的表现。
为了实现静态服务器,你需要根据访问的路径返回对应的内容。为此,请完善route()
函数:
- 访问根路径
/
将返回index.html
- 使用os模块读取文件,注意文本文件和二进制文件(如图片)的处理
- 用HTTP响应码进行错误处理,比如
404 NOT FOUND
,302 REDIRECT
最后,在根目录(你在哪里运行你的服务器脚本,那里就是你的根目录)下放入任意文件,浏览器都可以访问其内容,如果不存在则会返回404。
Expand
- 抓包观察访问你的网站和访问正常网站有什么区别。你会发现,本实验几乎没有讲解HTTP首部的细节,请自行了解诸如
Content-Type:
,Content-Length:
,Transfer-Encoding:
等首部,看看传输图片/压缩文件时的标准做法,以及在遇到大文件时如何实现分段运输。(尽管我们的实现很简陋,浏览器还是能正常工作,说明HTTP是相当健壮的协议) - 你的服务器是否有安全问题?你可以访问根目录之外的文件吗?如:
/../../../../etc/passwd
(linux下) - 服务器概念辨析:Web初学者容易对服务器概念感到迷惑。软件语境下,服务器指对外提供服务的程序,常用服务器如apache、nginx,tomcat等;硬件语境下则指运行着服务器软件的机器。
- 我们实现的是静态网站,你可能疑惑是不是还有动态网站。当然有,区分动态和静态并不是网页会不会自己动,而是服务器上的数据是否可以动态的改变,而我们的服务器只能被动的显示文件,客户端无法做出任何更改。现代web框架诸如Springboot,Django之类当然是动态网站框架。
实际上,传统计算机网络并不关心应用层以上的东西,让我们向下看,探究socket背后的原理吧。
0x20 传输层:Socket | ⭐
- 目标:用 scapy 实现TCP协议,以尽可能替换上一个实验使用的socket模块
- // 你可能猜到了,下一个实验是不是要自己实现IP协议呀?恭喜你猜错了。
- 前置:
- 术语
- TCP/UDP
- TCP报文格式
- 有限状态机
- 完成本实验仅涉及《CNTDA》3.4-3.5节,如果理解有困难,建议先完成Wireshark 实验:TCP观察
- 术语
- 在上一个实验中,我们了解到socket是操作系统提供的系统调用,例如 Linux 中,创建socket对象返回的
sock_fd
本质上就是一个文件描述符,即建立连接后可以直接像文件一样读写,绑定端口后操作系统会保护该端口不被其他进程占用。而本实验关注运输层原理,所以绕过了操作系统,使用更底层的接口实现TCP。当然,是最简陋的一种实现。
Guidelines
- Socket实现原理
那么,socket里面到底有什么?首先,要保存地址端口等信息;其次,要有收发的两个缓冲区,这里可以用队列;然后,为了实现可靠运输,需要用到计时器来触发重传,需要变量标记滑动窗口;我们还用自动机思想来管理连接状态。Socket底层模型如下:
|
|
这里就需要面向对象上场了
|
|
下面逐个实现socket接口。
【to be continued】
- 定制TCP报文
【to be continued】
- 连接管理
了解了三次握手,就可以实现connect函数了
【to be continued】
Task
- 多路复用
目前的实现只能支持一个TCP连接,请实现listen(num)
函数,调用时创建 num 对读写缓冲区,响应的为
- 完善TCP功能:
- RTT
- 可靠运输
- 流量控制
- 阻塞控制
- 拓展UDP到你的socket
Expand
【to be continued】
0x30 网络层: 路由追踪 | ⭐
Intro
术语:
- IP层:IP协议,ICMP协议,路由协议
- 路由追踪:请求某地址经过了那些路由器?
Guidelines
scapy实现了路由追踪函数,你可以钻研一下源码(很短),下面写一个自己的traceroute。
Task
下面用Ipython演示:
|
|
下面讲解核心代码:
ans,unans=sr(IP(dst=target,ttl=(1,30),id=RandShort())/TCP(flags=0x2))
- sr():send and receive,返回的两个参数分别是得到应答的数据包列表和未应答的包列表。
- ttl=(4,30):ttl参数在IP层表示ICMP包的转发次数(跳数)。此外,传入tuple表示一个范围,sr函数将会为这个范围内的每个值生成一个发包。(如果有多个tuple参数,则会按笛卡尔积规则生成发包列表)
- TCP(flags=0x2):在TCP头部设定flag字段的值,0x2对应ACK,即确认收到包。
- 综合起来,这条代码将发送30个包,其ttl从1到30。并筛选返回ACK的包。
- 这样根据IP层路由算法,到达ttl的包无论是否找到目标都会返回,直到找到目标,TCP层返回ACK。遍历ttl形成的列表即是经过的所有路由。
Expand
0x301 网络层: 欺骗ping | ⭐ | TODO
Intro
- 来源:https://seedsecuritylabs.org/Labs_20.04/Files/ICMP_Redirect/ICMP_Redirect.pdf
- 术语:
Guidelines
Task
Expand
0x41 链路层: ARP缓存投毒 | ⭐⭐ | TODO
https://seedsecuritylabs.org/Labs_20.04/Files/ARP_Attack/ARP_Attack.pdf
0x21 传输层: TCP攻击 | ⭐⭐ | TODO
https://seedsecuritylabs.org/Labs_20.04/Files/TCP_Attacks/TCP_Attacks.pdf
TCP协议 SYN泛洪 TCP reset TCP session hijacking反弹shell (重点)
0x31 网络层: NAT,DHCP和虚拟机 | ⭐⭐ | TODO
相信折腾过虚拟机的同学都绕不过这个问题:我的虚拟机怎么连不上网?本实验基于wmware虚拟机平台,讲解几种虚拟机网络模式及其原理。
0x13 应用层: DNS本地攻击 | ⭐⭐⭐ | TODO
https://seedsecuritylabs.org/Labs_20.04/Files/DNS_Local/DNS_Local.pdf
0x14 应用层: SSL协议和HTTPS | ⭐⭐⭐⭐ | TODO
0x15 应用层: 多线程Web代理服务器 | ⭐⭐⭐⭐⭐ | TODO
0x151 应用层: VPN | ⭐⭐⭐⭐⭐ | TODO
探究VPN原理
0x152 应用层: V2Ray协议学习 | ??? | TODO
有生之年研究一下Vmess等协议