自己动手写一个小型的TCPIP协议
⾃⼰动⼿写⼀个⼩型的TCPIP协议
TCP/IP协议⼤家都知道,但真正理解的⼈不多,不如动⼿写⼀个⼩型的看看。
我知道看书很枯燥,看不懂,还打击⼤家的信⼼,不是我们的脑袋不如⼈,是我们的⽅法错了。
⼀切的技术都从应⽤中发展⽽来,所以要从下往上⾛,先动⼿完成⼀个任务吧。
需要准备的前提知识
linux驱动程序知识:原本理解⽹络协议是不⼀定⾮要懂linux驱动程序的,但由于这个例⼦是使⽤linux虚拟⽹卡作为基础,为了看懂源代码,需要简单了解。⽬前没有⼜短⼩⼜清楚的好⽂章推荐,以后可以补充。
太⽹报头格式(L2层),ARP报头格式,ARP协议功能和实现流程。下⾯这个英⽂⽹址的⽂章我认为讲解还算⽐较好。
⾸先明⽩所谓7层协议栈各层的报头。这⾥不准备7层全部讲,就讲三层,包括以太⽹报头(L1层),IP报头(L2
层),TCP/UDP(L3层)报头。讲多了,反⽽不容易理解。从⼀个个具体应⽤总结出整个协议栈的结构。
然后再来看⼀个实际截获的数据包。
最后看⼀下整个数据包,包括数据和三个报头,是如何⽣成的。分别由哪些协议⽣成和⽣成次序。包包⽣成之后,交给以太⽹驱动程序发⾛。
图1:IP报头结构
除掉 IP Option这个字段, IP报头⼀共20个字节,各字段的含义如上图所⽰。
下⾯再看⼀个实际截获的UDP数据包:
图2:⼀个实际截获的UDP数据包
以太⽹报头
有⼀个“类型”字段(上⾯的例⼦中是0800,代表是从IP协议层传送过来的数据包),其中各类型值的含义分别如下:
0x0600 XNS
0x0800 IP
0x0806 ARP
0x6003 DECnet
怎么查自己的ip
IP报头
红⾊框框⾥⾯就是20个字节的IP报头,各字段的含义要仔细看。
UDP报头
IP报头
紧接着后⾯8字节的UDP报头。它是被上层的UDP协议加上去的。
⾸先数据(有效载荷)当然是应⽤层(打⽐⽅EPSON打印机应⽤软件)⽣成的。(上⾯这个图是我的EPSON打印机软件和打印机的通信包)。EPSON打印机应⽤软件⽣成⼀个数据包,就丢给UDP层(L3),这个UDP协议就会在数据前⾯加个⼀个UDP的报头(上图中的蓝⾊框框)。然后UDP协议接着往下传递,传到了IP层(L2层),这个IP协议呢,⼜在前⾯加了⼀个IP报头。⼜接着往下传递,传递到了以太⽹卡层(L1层),⼜在前⾯加了⼀个以太⽹报头,然后整个数据包交给以太⽹驱动程序,这个数据包包括三个报头和要发送的数据(有效载荷),最后以太⽹驱动程序把整个数据包通过⽹线发送出去。
看完⼀个具体的例⼦,再来看整个协议栈。
好了,到此为⽌,我认为该准备的基础知识已经准备好了。接下来动⼿实验吧。
让我们写⼀个TCP / IP协议栈,1:以太⽹、ARP
⾃⼰写TCP / IP协议栈可能看起来像⼀个艰巨的任务。事实上,TCP已经积累了超过三⼗年的寿命的规范。
然⽽核⼼规范,看起来是紧凑的,重要组成部分是:TCP报头解析、状态机、拥塞控制和重传超时计算。
最常⽤的2层和3层的协议,分别在以太⽹和IP层,相对TCP的复杂性⽽⾔显得简单很多。
在这个博客系列,我们将在Linux上实现⼀个最⼩的⽤户空间TCP / IP协议栈。
这些是纯粹的学习⽤,要深⼊学习,请更深层的学习⽹络和系统编程。
内容
TUN/TAP设备
以太⽹帧格式
以太⽹帧分析
地址解析协议
地址解析算法
结论
来源
TUN/TAP设备
为了拦截来⾃于内核的底层的⽹络数据,我们将使⽤⼀个TUN/TAP设备。
[Connie Note]
tun/tap 驱动程序实现了虚拟⽹卡的功能,tun表⽰虚拟的是点对点设备,tap表⽰虚拟的是以太⽹设备,这两种设备针对⽹络包实施不同的封装。
利⽤tun/tap 驱动,可以将tcp/ip协议栈处理好的⽹络分包传给任何⼀个使⽤tun/tap驱动的进程,由进程重新处理后再发到物理链路中。
简单说,⼀个TUN/TAP设备通常是由⽹络⽤户空间的应⽤程序⽤来分别操纵L3或L2层的数据。
⼀个流⾏的例⼦是,其中⼀个包被包裹在另⼀个数据包的有效载荷中。
TUN/TAP设备的优势是,它们很容易在⽤户空间的程序建⽴,它们已经被⽤于许多程序,如OpenVPN。
由于我们要从L2层建⽴⾃⼰的⽹络协议堆栈,我们需要⼀个TAP设备。我们那样实例化的那样:
/ *
*从内核⽂件/⽹络/ 取
*/
int tun_alloc(char *dev)
{
struct ifreq ifr;
int fd, err;
if( (fd = open("/dev/net/tap", O_RDWR)) < 0 ) {
print_error("Cannot open TUN/TAP dev");
exit(1);
}
CLEAR(ifr);
/* Flags: IFF_TUN  - TUN device (no Ethernet headers)
*        IFF_TAP  - TAP device
*
*        IFF_NO_PI - Do not provide packet information
*/
ifr.ifr_flags = IFF_TAP | IFF_NO_PI;
if( *dev ) {
strncpy(ifr.ifr_name, dev, IFNAMSIZ);
}
if( (err = ioctl(fd, TUNSETIFF, (void *) &ifr)) < 0 ){
print_error("ERR: Could not ioctl tun: %s\n", strerror(errno));
close(fd);
return err;
}
strcpy(dev, ifr.ifr_name);
return fd;
}
接下来,返回的⽂件描述符fd可以⽤来读写数据到虚拟设备的以太⽹缓冲。
标志IFF_NO_PI是⾄关重要的,否则,我们会添加到以太⽹帧不必要的数据包信息。你可以看看内核的设备驱动程序的,并⾃⼰验证。
以太⽹帧格式
众多不同的以太⽹⽹络技术连接计算机到局域⽹(LANs)的主⼲⽹。与所有的物理技术⼀样,以太⽹标准已经发⽣巨⼤变化,从1980年由Digital Equipment公司和英特尔和施乐出版的第⼀个⽅案。
以太⽹的第⼀个版本是在今天的标准看来是很慢的,⼤概10Mb/s,采⽤半双⼯通信,这意味着你可以发送或接收数据,但不能在同⼀时间。这就是为什么⼀个媒体访问控制(Media Access Control )(MAC)协议必须被纳⼊组织的数据流。即使到今天,如果在半双⼯模式下运⾏⼀个以太⽹接⼝,载波侦听多路访问冲突检测(CSMA/CD)依然是必须的,因为MAC。
采⽤双绞线布线的100BASE-T以太⽹标准使全双⼯通信成为可能,也使通讯有了更⾼的吞吐速度。此外,同时增加了以太⽹交换机的⼈⽓,让CSMA/CD过时。
IEEE 802.33⼯作组维护着不同的以太⽹标准。
下⼀步,我们将看看以太⽹帧头。它可以被声明为⼀个C的结构体:
#include <linux/if_ether.h>
struct eth_hdr
{
unsigned char dmac[6];
unsigned char smac[6];
uint16_t ethertype;        //以太⽹类型
unsigned char payload[];    //有效载荷
} __attribute__((packed));
DMAC和Smac是相当不⾔⾃明的项⽬。它们分别包含通信⽅(⽬的和源)的地址。
重载项⽬ethertype,是⼀种2-octet项⽬,它的含义取决于它的值,可以是有效载荷的长度,也可以使有效载荷的类型。具体来说,如果该字段的值⼤于或等于1536,该字段表⽰有效载荷的类型(如IPv4,ARP)。如果值⼩于该值,则字段表⽰有效负载的长度。
ethertype字段后,以太⽹帧中可以有⼏种不同的标签。这些标签可以⽤来描述虚拟局域⽹(VLAN)、服务质量(QoS)的帧类型。以太⽹帧标签被排除在我们这次的代码之外,所以在我们的协议声明中,相应的字段也没有出现。
有效载荷字段包含指向以太⽹帧有效负载的指针。在我们的例⼦中,这将包含⼀个ARP或IPv4数据包。如果有效负载长度⼩于所需的最⼩48字节(不带标签),则将字节数追加到有效负载的最末端,以满⾜需求。
我们还需要包括if_ether.h 这个Linux头⽂件,它提供ethertypes和⼗六进制值之间的映射。
最后,以太⽹帧格式还包括帧校验字段,它是⽤循环冗余校验(CRC)来检查帧的完整性。在我们的例⼦中,我们将省略这⼀处理。
以太⽹帧的解析
结构声明的packed属性是⼀个细节,它告诉编译器不要优化数据结构的4字节对齐的内存布局。使⽤这个属性是由于我们“解析”协议缓冲区的⽅式,这是⼀种适当的协议结构数据缓冲区:
struct eth_hdr hdr = (struct eth_hdr ) buf;
⼀种便携式,稍微费⼒的⽅法,将⼿动序列化协议数据。这样,编译器可以⾃由地添加填充字节,以更好地适应不同处理器的数据对齐要求。
分析和处理以太⽹帧缓存的总体⽅案是简单的:
if (tun_read(buf, BUFLEN) < 0) {
print_error("ERR: Read from tun_fd: %s\n", strerror(errno));
}
struct eth_hdr *hdr = init_eth_hdr(buf);
handle_frame(&netdev, hdr);
handle_frame功能使根据以太帧的ethertype 字段的值,决定其下⼀步的动作。
地址解析协议
地址解析协议(ARP)是⽤于动态映射⼀个48位的以太⽹地址(MAC地址)到协议地址(例如IPv4地址)。这⾥的关键是,ARP需要对应很多不同的L3协议,不仅仅是IPv4,还有像CHAOS这样的协议。
这些被声明成16⽐特的协议地址。
通常的情况是,你知道在你的局域⽹中的⼀些服务的IP地址,但要建⽴实际的通信,也需要知道硬件地址(MAC)。因此,ARP是⽤来⼴播查询⽹络,要求IP地址的所有者报告其硬件地址。
ARP数据包的格式相对⽐较简单:

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。