实验指导书

我的代码&&相关注释

置顶

关于对分片的支持 CS144似乎是将分片支持放在了TCP层,也即由TCP实现分片和传输。在本实验中,我需要思索一下udp怎么适配分片,发送是已经没问题的了,接收需要好好修改一下。

总体架构

发送过程:

  1. UDP发送包
  2. IP发送包
  3. ARP发送包
    1. 地址未知,调用arp_req发送ARP请求
    2. 地址已知,ETH协议
  4. ETH发送包

接收过程:

  1. ETH收到包

    1. ARP协议

      1. 收到了ARP请求报文

        调用arp_resp回应自身MAC地址

      2. 否则,则为收到了ARP响应报文

        调用eth_out发送驻留的IP报文

    2. IP协议

  2. IP收到包

    1. ICMP协议
      1. IP报文协议字段错误,调用icmp_unreachable
      2. 如果为ICMP回显请求,发送ICMP响应
    2. UDP协议
  3. UDP收到包

    1. 内部错误,调用icmp_unreachable
    2. 否则,调用main::handler()

实现原理

本质上只是类似于一种【抓包程序】:将网卡设置为混杂模式,同时增设一个BPF过滤器,通过接收网络上所有包并筛选出那些目标IP地址等于设置的协议栈地址的部分,然后发送也是借这个网卡发出去,从而达成收发数据包的目的。

具体可见driver.c文件中的代码和注释。

协议栈

一些问题

大小端

网络上传输的数据都是字节流,对于一个多字节数值,在进行网络传输的时候,先传递哪个字节?按照TCP/IP协议规定:网络字节序是大端字节序。但是,X86平台上是以小端字节序存储,也就是,在发送之前我们需要将小端存储的字节序转换成大端法存储的数值,而在接收时,也需要将大端序转成小端序存放的数值。

我们只需关注那些长度大于一个字节的数据类型即可,如protocol_type、opcode16等等等,buf数组以及长度等字段的类型单位只为一个字节大小,无需swap。

字节对齐

在大多数计算机体系结构中,数据访问通常按照特定的字长(如32位或64位)进行对齐。结构体(struct)作为复合数据类型,其成员可以是基本数据类型(如int、long、float等),也可以是其他复合数据类型(如数组、结构体、联合体等)。

在默认情况下,编译器会根据结构体的每个成员的自然对齐条件来分配内存空间,这是为了提高数据访问和运算的效率。自然对齐,也称为默认对齐方式,通常是根据结构体成员中最大尺寸的成员来确定对齐边界。这意味着,即使某些成员的实际大小小于对齐边界,编译器也可能在它们之间插入填充字节,以确保整个结构体满足对齐要求。

网络传输是字节流传输,但是当发生结构体对齐时,编译器会自动加入填充字节,这样发送出去的字节流某些字段会指向错误的地方,因此,在实验中,我们需要禁用结构体内部字段的对齐。

在实验代码中,可以使用伪指令pack(n),编译器将按照n个字节对齐。注意:如果指定的n大于结构体最大成员的size,则其不起作用,结构体仍然按照size最大的成员进行对齐。

1
2
3
4
5
6
7
8
9
#pragma pack(1) // 编译器将按照1个字节对齐

typedef struct ether_hdr
{
uint8_t dst[NET_MAC_LEN]; // 目标mac地址
uint8_t src[NET_MAC_LEN]; // 源mac地址
uint16_t protocol16; // 协议/长度
} ether_hdr_t;
#pragma pack() // 取消自定义字节对齐方式

Map的使用

本实验用C语言实现了一个标准的泛型容器map,非常值得一看。

ETH

../_images/%E4%BB%A5%E5%A4%AA%E7%BD%91%E6%95%B0%E6%8D%AE%E5%B8%A71.png

../_images/%E5%B8%A7%E6%A0%BC%E5%BC%8F1.png

ARP

在TCP/IP的网络构造和网络通信中无需事先知道MAC地址究竟是什么,只要确定了IP地址,就可向这个目标地址发送IP数据报了。然而,在数据链路层使用的是硬件地址(MAC)进行报文传输,IP地址不能被物理网络所识别,因此必须建立IP地址和MAC地址的映射关系,这一过程称为ARP(Address Resolution Protocol)地址解析协议。

ARP协议以目标IP地址为线索,用来定位下一个应该接收数据包的网络设备对应的MAC地址。如果目标主机不在同一个链路上,可以通过ARP查找下一跳网关的MAC地址。注意,ARP只适用于IPv4,不能用于IPv6。IPv6可以用ICMPv6替代ARP发送邻居探索消息。

那么ARP又是如何知道MAC地址的呢?简单地说,ARP是借助ARP请求与ARP响应两种类型的包确定MAC地址的。此外,在每台使用ARP的主机中,都保留了一个专用的内存区(称为缓存),存放最近的IP地址与硬件地址的对应关系。一旦收到ARP应答,主机将获得的IP地址和硬件地址的对应关系存到缓存中。当发送报文时,首先去缓存中查找相应的项,如果找到相应项,便将报文直接发送出去;如果找不到,再利用ARP进行解析。ARP 缓存信息在一定时间内有效,过期不更新就会被删除。

ARP是一个独立的三层协议,所以ARP报文在向数据链路层传输时不需要经过IP协议的封装,而是直接生成自己的报文,其中包括ARP报头,到数据链路层后再对应的数据链路层(如以太网协议)进行封装。ARP报文分为ARP请求和应答报文两种,报文格式如下图所示。

../_images/ARP.png

IP

数据链路层提供两个直连设备之间的通信功能,而一旦跨越不同的数据链路,就需要借助网络层。IP(Internet Protocol,网际协议) 是TCP/IP协议网络层的协议,其主要作用是“实现终端节点之间的通信”,也叫“点对点(point-to-point)通信”。IP可大致分为三大作用模块,分别是IP寻址、IP分包与组包以及路由转发,其中,路由转发功能涉及到路由协议,在本节实验中不做重点介绍。

../_images/IP%E5%9C%B0%E5%9D%80.png

IP提供了一种尽力而为、无连接的数据报交付服务。“尽力而为”的含义是不保证IP数据报能成功到达目的地。“无连接”意味着IP不维护网络单元(即路由器)中数据报相关的任何链接状态信息,每个数据报独立于其他数据报来处理,这也意味着IP数据报可不按顺序交付。

本实验是基于IPv4协议上完成的,以下主要介绍IPv4版本的IP协议。IPv4的头部结构长度为20字节,若含有可变长的选项部分,最多60字节。

../_images/IP%E9%A6%96%E9%83%A8.PNG

这3个字段(标识、标志、位偏移)则描述如何实现分片重组:

  1. 标识 :唯一地标识主机发送的每一个数据报,其初始值是随机的,每发送一个数据报其值就加1。同一个数据报的所有分片都具有相同的标识值。本实验中,标识字段可从0开始计数,然后往上自增。
  2. 标志 : 位1保留,位2表禁止分片(DF),若设置了此位,IP模块将不对数据报进行分片,在此情况下若IP数据报超过MTU,IP模块将丢弃数据报并返回一个ICMP差错报文;位3标识更多分片(MF),除了数据报的最后一个分片,其他分片都要把它设置为1。
  3. 位偏移 :分片相对原始IP数据报数据部分的偏移。实际的偏移值为该值左移3位后得到的,所以除了最后一个IP数据报分片外,每个IP分片的数据部分的长度都必须是8的整数倍

其它字段:

  1. 生存时间 :数据报到达目的地之前允许经过的路由器跳数。TTL值被发送端设置,常设置为64。数据报在转发过程中每经过一个路由该值就被路由器减1.当TTL值为0时,路由器就将该数据包丢弃,并向源端发送一个ICMP差错报文。TTL可以防止数据报陷入路由循环。
  2. 协议 : 区分IP协议上的上层协议。在Linux系统的/etc/protocols文件中定义了所有上层协议对应的协议字段,ICMP为1,TCP为6,UDP为17
  3. 头部校验和 : 由发送端填充接收端对其使用CRC算法校验,检查IP数据报头部在传输过程中是否损坏。IP头部校验和只针对IP首部做校验,对其负载数据的校验是上层协议负责的,它不需要关心负载数据在传输过程中出错与否。IP数据报在转发的过程中,其头部字段就会被改变一次(比如修改TTL等),但IP数据报中的负载数据字段是不会改变的,因此,通过只校验IP头部的方式,也可以提高路由器处理分组的效率。
  4. 源IP地址和目的IP地址 : 表示数据报的发送端和接收端。
  5. 区分服务(Type Of Service,TOS) :3位优先权字段(现已被忽略) + 4位TOS字段 + 1位保留字段(须为0)。4位TOS字段分别表示最小延时、最大吞吐量、最高可靠性、最小费用,其中最多有一个能置为1。应用程序根据实际需要来设置TOS值。本实验中,可将TOS设置为0。
    • 最小延时:确保数据包尽快到达目的地,适用于需要低延迟的应用,如 VoIP 或在线游戏、ssh和telnet这样的登录程序。
    • 最大吞吐量:允许数据包以最大速率传输,适用于需要高带宽的应用,如视频会议、文件传输ftp。
    • 最高可靠性:减少数据包丢失和错误,适用于需要高可靠性的应用,如金融交易。
    • 最小费用:选择成本最低的路径传输数据包,适用于成本敏感的应用。

../_images/IP%E5%88%86%E7%89%87.PNG

ICMP

将Internet控制报文协议(Internet Control Message Protocol,ICMP)与IP结合使用,以便提供与IP协议层配置和IP数据包处理相关的诊断和控制信息。ICMP主要功能包括:确认IP包是否成功送达目标地址、通知发送过程当中IP包被废弃的具体原因、改善网络设置等。ICMP协议应用在许多网络管理命令中。例如,ping命令使用ICMP回送请求和应答报文,路由分析诊断程序tracert使用ICMP时间超过报文等。

报文是在IP数据报内被封装传输的。ICMP头部8个字节,所有数据都在ICMP头部后面。ICMP报文格式具体见RFC777,RFC792规范。

../_images/ICMP%E6%A0%BC%E5%BC%8F.png

ICMP的消息大致可以分为两类:一类是通知出错原因的错误消息(又称差错报文,error message),另一类是用于诊断的查询消息(又称询问或信息报文,information message)。

../_images/ICMP%E7%B1%BB%E5%9E%8B.png

  • 差错报文

    差错报文保留IP数据包中的前8个字节意义:TCP、UDP前八字节都含有源端口号,可以反馈给源主机。接收ICMP差错报文的模块就会把它与某个协议和用户进程联系起来。其中协议:根据IP数据报首部中的协议字段来判断,用户进程:根据包含在IP数据报前8字节中的TCP、UDP报文首部中的TCP、UDP端口号判断。

    ../_images/ICMP%E5%B7%AE%E9%94%99%E6%8A%A5%E6%96%87.png

  • 查询报文

    其中16位校验和(Checksum)涵盖了整个报文,其校验和的计算方法和IP协议校验和算法是一样的;

    标识符(Identifier):如果是ICMP请求报文,该字段在Linux/macOS中用的是进程ID。如果是ICMP应答报文,则只需拷贝来自ICMP请求报文的标识符字段;

    序列号(Sequence number):如果是ICMP请求报文,该字段在Linux/macOS中是从0递增的,每个进程独立。如果是ICMP应答报文,则只需拷贝来自ICMP请求报文的序列号字段;

    数据(Optional data):如果是ICMP请求报文,该字段包括时间戳以及一串填充数据。如果是ICMP应答报文,则只需拷贝来自ICMP请求报文的数据字段。

    ../_images/ICMP%E4%BF%A1%E6%81%AF%E6%8A%A5%E6%96%87.png

    image-20240526200525596

附加题

TCP

首先实现server逻辑吧