让我们代码TCP / IP堆栈(2016)

2021-06-29 22:45:15

写自己的TCP / IP堆栈可能看起来像是一个令人生畏的任务。实际上,TCP积累了超过三十年的寿命的许多规格。然而,核心规范看似紧凑1 - TCP标题解析的重要部分,状态机,拥塞控制和重传超时计算。

分别是最常见的第2层和第3层协议,以太网和IP,与TCP的复杂性相比,苍白。在此博客系列中,我们将为Linux实施最小的用户空间TCP / IP堆栈。

这些帖子的目的和生成的软件纯粹是教育 - 以更深层次的级别学习网络和系统编程。

要从Linux内核拦截低级网络流量,我们将使用Linux Tap设备。简而言之,网络用户空间应用程序通常使用TUN / TAP设备分别用于操纵L3 / L2流量。一个流行的例子是隧道,其中数据包在另一个数据包的有效载荷内包装。

TUN / TAP设备的优势在于它们易于在用户空间程序中设置,并且它们已在多个程序中使用,例如OpenVPN。

正如我们想要从第2层构建网络堆栈,我们需要一个点击设备。我们将其实例化:

/ * *从内核文档/ networking / tuntap.txt * / int tun_alloc(char * dev){struct ifreq ifr; int fd,err; if((fd =打开(" / dev / net / tap / tap",o_rdwr))< 0){print_error("无法打开tun / tap dev");出口(1); }清除(IFR); / *标志:IFF_TUN - TUN设备(无以太网标题)* IFF_TAP - 点击设备* * IFF_NO_PI - 不提供数据包信息* / IFR。 ifr_flags = iff_tap | iff_no_pi; if(* dev){strncpy(ifr。ifr_name,dev,ifnamsiz);如果((eroctl(fd,tunsetiff,(void *)& ifr))< 0){print_error(" err:不能ioctl tun:%s \ n",sterror( errno));关闭(FD);返回错误; strcpy(dev,ifr。ifr_name);返回FD; }

在此之后,返回的文件描述符FD可用于读取和写入虚拟设备的以太网缓冲区。

FLAG IFF_NO_PI在此处至关重要,否则我们最终会有不必要的数据包信息向以太网帧。您实际上可以查看核心设备驱动程序的内核源代码,并验证此操作。

众多不同的以太网网络技术是在局域网(LAN)中连接计算机的骨干。与所有物理技术一样,以太网标准从1980年由数字设备公司,英特尔和施乐发布的第一个版本2。

以太网的第一个版本在当今标准中缓慢 - 约为10MB / s,它使用了半双工通信,这意味着您要么发送或接收数据,但不同时。这就是为什么必须合并媒体访问控制(MAC)协议以组织数据流。即使到了这一天,如果在半双工模式下运行以太网接口,则需要使用碰撞检测(CSMA / CD)的多次访问。

100Base-T以太网标准使用双绞线接线的发明,实现全双工通信和较高的吞吐量速度。此外,以太网交换机的普及同时增加,使CSMA / CD很大陈旧。

接下来,我们会查看以太网帧头。它可以跟踪C结构声明:

#include< linux / if_ether.h> struct eth_hdr {unsigned char dmac [6]; unsigned char smac [6]; uint16_t以太网; unsigned char payload []; } __attribute__((包装));

DMAC和SMAC是非常自我解释的领域。它们包含传送方的MAC地址(分别为目的地和来源)。

重载的字段以太网是一个2个八位字节字段,即根据其值,指示有效载荷的长度或类型。具体而言,如果字段的值更大或等于1536,则该字段包含有效载荷的类型(例如IPv4,ARP)。如果值小于该值,则它包含有效载荷的长度。

在类型字段之后,以太网帧有几种不同的标签。这些标签可用于描述虚拟LAN(VLAN)或帧的服务质量(QoS)类型。以太网帧标记被排除在我们的实现之外,因此相应的字段也不会显示在我们的协议声明中。

现场有效负载包含指向以太网帧的有效载荷的指针。在我们的情况下,这将包含ARP或IPv4数据包。如果有效载荷长度小于所需的48字节(没有标签),则填充字节将附加到有效载荷的末尾以满足要求。

我们还包括IF_ETHER.H Linux标题,以提供以太网类型和其十六进制值之间的映射。

最后,以太网帧格式还包括结尾的帧检查序列字段,其与循环冗余校验(CRC)一起使用,以检查帧的完整性。我们将在我们的实施中省略处理该领域。

在STRACH的声明中包装的属性是实现详细信息 - 它用于指示GNU C编译器不优化与填充字节的数据对齐的结构存储器布局4.使用此属性纯粹符合我们的方式“解析“协议缓冲区,该协议缓冲区只是使用正确的协议结构在数据缓冲区上传递的类型:

一个便携式,尽管稍微更加费力的方法,是手动序列化协议数据。这样,编译器可以自由地添加填充字节以更好地符合不同的处理器的数据对齐要求。

if(tun_read(buf,buflen)< 0){print_error(" err:从tun_fd读取:%s \ n",sterror(errno)); struct eth_hdr * hdr = init_eth_hdr(buf); handle_frame(& netdev,hdr);

handle_frame函数只查看以太网标题的EtherType字段,并根据值决定其下一个操作。

地址解析协议(ARP)用于将48位以太网地址(MAC地址)动态映射到协议地址(例如IPv4地址)。这里的关键是通过ARP,可以使用多个不同的L3协议:不仅仅是IPv4,而是混沌等其他协议,它声明了16位协议地址。

通常的情况是您知道LAN中某些服务的IP地址,但要建立实际通信,还需要知道硬件地址(MAC)。因此,ARP用于广播和查询网络,询问IP地址的所有者报告其硬件地址。

struct arp_hdr {uint16_t hwtype; UINT16_T型焊接; unsigned char hwsize;未签名的炭辱; UINT16_T OPCODE; unsigned char数据[]; } __attribute__((包装));

ARP标题(ARP_HDR)包含2-OCTET HWTYPE,该HWTYPE确定使用的链路层类型。在我们的情况下,这是以太网,实际值为0x0001。

2-octet Prolype字段表示协议类型。在我们的情况下,这是IPv4,它与值0x0800通信。

HWSIZE和PRESIZE FIALES的大小均为1八位字节,它们分别包含硬件和协议字段的大小。在我们的情况下,MAC地址为6个字节,IP地址为4个字节。

2-octet字段操作码声明ARP消息的类型。它可以是ARP请求(1),ARP回复(2),RARP请求(3)或RARP回复(4)。

数据字段包含ARP消息的实际有效载荷,并且在我们的情况下,这将包含IPv4特定信息:

这些田地非常自我解释。 SMAC和DMAC分别包含发件人和接收者的6字节MAC地址。 SIP和DIP分别包含发件人和接收器的IP地址。

?我是否在AR $ HRD中有硬件类型?是的:(几乎肯定)[可选地检查硬件长度AR $ HLN]?我是否在AR $ PRO中讲协议?是:[可选地检查协议长度AR $ PLN] merge_flag:= false如果该对<协议类型,发件人协议地址>已经在我的翻译表中,使用数据包中的新信息更新条目的发件人硬件地址字段,并将merge_flag设置为true。我是目标协议地址吗?是:如果merge_flag为false,则添加三态组<协议类型,发件人协议地址,发件人硬件地址>到翻译表。 ?操作码ares_op $申请吗? (现在看看Opcode !!)是:交换硬件和协议字段,将本地硬件和协议地址放在发件人字段中。将AR $ OP字段设置为ARES_OP $ REPLY将数据包发送到收到请求的同一硬件上的(新)目标硬件地址。

即,翻译表用于存储ARP的结果,以便主机可以查看它们是否已在其缓存中有条目。这避免了垃圾签发冗余ARP请求的网络。

最后,ARP实现的最终测试是看它是否正确地返回ARP请求:

[saminiir @ localhost lvl-ip] $ arping -i tap0 10.0.0.4arping 10.0.4起从192.168.1.32 Tap 0unicast回复从10.0.0.4从10.0.0.4 [00:0C:29:6D:50:25] 3.170msunicast回复从10.0.0.4 [00:0C:29:6D:50:25] 13.309MS [Saminiir @ localhost lvl-IP] $ Arpaddress HWTYPE HWAddress Flags Mask iFace10.0.0.4 ether00:0C:29:6D:50:25 C Tap0

内核的网络堆栈识别来自自定义网络堆栈的ARP回复,因此使用我们的虚拟网络设备的条目填充其ARP缓存。成功!

以太网帧处理和ARP的最小实现相对容易,并且可以在几行代码中完成。相反,奖励因素非常高,因为您可以使用自己的制作信念以太网设备填充Linux主机的ARP缓存!

在下一个帖子中,我们将继续执行ICMP Echo&回复(ping)和IPv4数据包解析。

如果您喜欢这篇文章,您可以与您的追随者分享并跟随我的推特!

Kudos到Xiaochen Wang,其类似的实现对我来说,在加快C网络编程和协议处理时,对我来说是非常宝贵的。我发现他的源代码5易于理解,我的一些设计选择从他的实施中直接复制。