Gannicus Guo的DIY TCP/IP之旅,描述本人自己动手写的一个简化的TCP/IP协议。经测试该协议可以运行在Ubuntu 16.10 x86_64 操作系统用户空间。便于描述,将该TCP/IP协议称为DIY TCP/IP 。专栏内容包括DIY TCP/IP实现的可行性分析,依赖的编程接口,ARP数据帧的收发,ICMP Echo (PING)数据帧的收发,简单的基于ARP表的IP路由,局域网内IP数据帧的收发,IP数据帧的分片,IP分片的重组,TCP数据帧的收发,TCP连接状态机,以及TCP滑动窗口的实现等全部内容。方便感兴趣的朋友参考并自己动手实现。
自己动手实现DIY TCP/IP,硬件依赖:一台个人电脑(带网卡,无线有线均可),软件依赖:Ubuntu操作系统(虚拟机,硬盘安装均可)。DIY TCP/IP支持ARP,Ping,Large Packet Ping和局域网内的iperf tcp吞吐量测试。

  1. 可行性分析
    Linux Kernel 提供PF_PACKET域的socket,通过PF_PACKET socket可以收发链路层的原始数据帧。基于链路层的原始数据帧,可以在Linux Kernel的用户空间实现TCP/IP协议。区别于Linux kernel内核空间的TCP/IP协议栈,DIY TCP/IP虚拟一个局域网内不存在的IP地址,数据帧到达Linux kernel的网络设备层后,由DIY TCP/IP接收,不经过kernel的TCP/IP协议栈。假设局域网内一台测试机器上运行iperf,目的IP址是虚拟出来的IP,只要DIY TCP/IP 回复测试机发出的ARP Request,建立虚拟IP和本机MAC地址的映射,DIY TCP/IP即可收到测试机发出的IP和TCP数据帧。在此基础上实现DIY TCP/IP的IP模块,TCP模块,即可完成DIY TCP/IP和测试机器的TCP/IP通信, 不需要对Linux kernel做任何配置或和修改。
  2. DIY TCP/IP软件架构

    DIY TCP/IP实现依赖的编程接口:libpcap,POSIX Pthread,RAW Socket,Linux C文件操作,Linux C的时间操作等。
  3. 自己动手实现
    本人搜所过网络上现存的实现TCP/IP协议的内容,有的是通过libnet来实现组帧,有的没有完整的讲解实现过程。由于本人深受于渊老师“自己动手写操作系统”一书的影响,希望尽可能多的自己动手实现需要的功能,除去”DIY TCP/IP软件架构”一节中列出的对现成库函数的依赖,DIY TCP/IP 的实现:编译用到的Makefile,链表,队列,buffer 管理,组帧,各个协议模块的细节均是参考开源代码后自己动手通过C语言实现。以测试通过ARP,PING,Large Packet Ping和iperf TCP为目标,详细的描述DIY TCP/IP的全部实现内容。
  4. 从0开始
    在介绍从0开始的代码实现之前,先介绍用到的5个libpcap的库函数。从http://www.tcpdump.org/ 可以获取到lipcap库的源代码。Ubuntu上使用libpcap库,需要先通过”sudo apt-get install libpcap-dev”命令安装。获取libpcap库的源代码,有助于更深入的了解libpcap库API的实现,正确的设置库函数参数。如果读者已经安装过libpcap库可以通过如下命令查看libpcap库的版本:
 $ ldconfig -p | grep pcaplibpcap.so.0.8 (libc6,x86-64) => /usr/lib/x86_64-linux-gnu/libpcap.so.0.8libpcap.so.0.8 (libc6) => /usr/lib/i386-linux-gnu/libpcap.so.0.8libpcap.so (libc6,x86-64) => /usr/lib/x86_64-linux-gnu/libpcap.so$ ls -l /usr/lib/x86_64-linux-gnu/libpcap* -rw-r--r-- 1 root root 433728  2月  4  2014 libpcap.alrwxrwxrwx 1 root root     14  2月  4  2014 libpcap.so -> libpcap.so.0.8lrwxrwxrwx 1 root root     16 11月  8  2016 libpcap.so.0.8 -> libpcap.so.1.5.3

本人Ubuntu机器上链接的libpcap库的版本是1.5.3。使用libpcap库函数,gcc编译时需指定链接参数-lpcap。本节介绍用到的libpcap库函数时引入的代码实现,也是基于libpcap1.5.3。更多libpcap库函数的使用手册,读者可以在ubuntu终端中输入man “库函数名称”,例如man pcap_open_live, 也可以直接访问”Linux man page”官方网址https://www.die.net/。下面介绍的libpcap库函数的函数原型,基本描述,返回值,以及出错处理,均参考Linux man page。
4.1 pcap_open_live库函数
从0开始的代码使用的第一个libpcap的库函数为pcap_open_live,该库函数的实现在pcap.c文件中。该函数初始化pcap_t结构,创建socket,domain为PF_PACKET,type为SOCK_RAW,protocol为ETH_P_ALL ,也就是从链路层接收所有协议的数据帧,初始化socket选项PACKET_RX_RING,通过内存映射的方式从RAW socket上接收原始数据帧。
在pcap_open_live的linux man page, 可以看到该库函数的函数原型和使用时需要引入的头文件。依照Linux man page 里的内容逐一介绍该库函数的基本描述,参数,返回值和出错处理。

#include <pcap/pcap.h>char errbuf[PCAP_ERRBUF_SIZE];pcap_t *pcap_open_live(const char *device, int snaplen,int promisc, int to_ms, char *errbuf);

第一个参数device,网络接口,例如”eth0”或”wlan0”等。如果指定device为NULL或”any”,则从所有网络接口接收数据帧。
第二个参数snaplen,指定抓取数据帧的长度,类似tcpdump的”-s”参数,如果不需要完整的数据帧,可以指定需要的长度。如果指定snaplen < 0或者大于MAXMUM_SNAPLEN,libpcap库会将其设置为MAXIMUM_SNAPLEN,MAXIMUM_SNAPLEN的值可以查看pcap-int.h。回到参数sanplen的介绍,库函数会将sanplen赋值给pcap_t结构中的snapshot成员。DIY TCP/IP需要的完整的数据包的长度为1514字节,IP层的最大传输单元为1500,MAC头的开销为 源地址 (6字节) + 目的地址 (6字节) + Type (2字节) ,共1514字节。使用过iperf测试的朋友应该发现过长度远于1518字节的TCP数据帧,并且IP层没有分片,此处先说明这种超过1514字节的TCP数据帧与网卡驱动的GRO有关。GRO相关内容在DIY TCP/IP的TCP模块的实现章节介绍。
第三个参数promisc,指定是否将device指定的网络接口设置为混杂模式,1为混杂模式。
第四个参数to_ms,以毫秒为单位,是libpcap通过 poll检查 raw socket是否有待接收的数据帧的超时时间。libpcap 将socket设置为non-block模式并设置超时时间,用于处理被打断的system call的poll event和检查结束条件是否满足,超时处理的实现见pcap_wait_for_frames_mmap函数。
第五个参数errbuf,用于返回pcap_open_live的出错信息,如果strlen(errbuf)不为0时,表明有出错信息,errbuf的最小长度为PCAP_ERRBUF_SIZE(256),定义见pcap.h头文件。
pcap_open_live出错返回NULL,出错信息存放在errbuf中,成功时返回pcap_t类型的指针,指向libpcap库创建的数据结构pcap_t。pcap_t结构体定义见pcap-int.h头文件。
4.2 pcap_compile & pcap_setfilter库函数

#include <pcap/pcap.h>int pcap_compile(pcap_t *p, struct bpf_program *fp,const char *str, int optimize, bpf_u_int32 netmask);int pcap_setfilter(pcap_t *p, struct bpf_program *fp);

pcap_compile生成bpf_grogram数据结构,可以理解为数据帧的过滤器,pap_setfilter设置pcap_compile生成的过滤器。DIY TCP/IP需要接收以太网类型为0x0800 (IP) 和0x0806 (ARP)类型的数据帧。
pcap_compile的第一个参数p是4.1节介绍的pcap_open_live返回的pcap_t类型的指针;第二个参数fp是指向bfp_program数据结构的指针;第三个参数参数str指向模式字符串,str的语法见pcap-filter的man page。过滤IP和ARP类型的数据帧,str为“ether proto 0x0800 or ether proto 0x0806”。pcap_compile根据str,填充bfp_program数据结构的成员。第四个参数optimize,控制pcap_compile是否执行bfp_optimize,将其设置为1,第五个参数netmask是网络接口的子网掩码,只有在过滤广播包时有用,将其设置为0xffffff00 (255.255.255.0) 即可。
pcap_setfitler的第一个参数是pcap_open_live返回的pcap_t类型的指针,第二个参数是pcap_compile生成的过滤器。
pcap_compile和pcap_setfilter库函数函数执行成功返回0,不成功返回-1。返回-1时, 可以通过pcap_geterr获取出错信息。
4.3 pcap_loop库函数

#include <pcap/pcap.h>typedef void (*pcap_handler)(u_char *user,const struct pcap_pkthdr *h, const u_char *bytes);int pcap_loop(pcap_t *p, int cnt,pcap_handler callback, u_char *user);

pcap_loop从raw socket上读取数据帧,通过callback回调函数将数据帧传给调用者,pcap_loop的实现代码见pcap.c。
先介绍pcap_loop的第三个参数callback,pcap_handler类型的函数指针。callback函数在pcap_loop接收到数据帧时调用,由调用者实现,完成对捕获的数据帧的处理。pcap_loop函数的第一个参数user是调用者传递给callback函数的参数,第二个参数h是libpcap对接收到的网络数据帧的描述,包括长度,时间戳等。第三个参数bytes指向数据帧的有效载荷。
pcap_loop的第一个参数是pcap_open_live返回的pcap_t指针,第二个参数cnt指定需要获取的网络数据帧的数量,pcap_loop在接收到cnt个数据帧时返回。如果cnt为0或-1,pcap_loop会一直执行,这种情况要用pcap_breakloop库函数打断pcap_loop。第四个参数user是传给callback回调函数的参数。

4.4 pcap_breakloop库函数

#include <pcap/pcap.h>
void pcap_breakloop(pcap_t *);

pcap_breakloop设置pcap_loop的返回条件为真,pcap_loop每次循环,接收到数据帧,或超时,或被中断时检查该返回条件。
4.5 接收链路层数据帧
从0开始的代码一共91行,先介绍主函数,然后是头文件和数据结构的定义。本节使用4.1-4.4节介绍的libpcap库函数,从eth0网络接口链路层接收数据帧,实现pcap_loop回调函数,打印出数据帧以太网头部的类型,main函数如下:

pcap_t *pcap_dev = NULL;void signal_handler(int sig_num)
{pcap_breakloop(pcap_dev);
}int main(int argc, char *argv[])
{char pcap_packet_filter[FILTER_BUFFER_SIZE];char err_buf[PCAP_ERRBUF_SIZE];struct bpf_program filter_code;/* init signal handler */signal(SIGINT, signal_handler);/* obtain packet capture handle */pcap_dev = pcap_open_live("eth0", MAX_NETWORK_SEGMENT_SIZE, 0,TIMEOUT_MS, err_buf);if (pcap_dev == NULL) {printf("pcap_open_live failed, %s (%d)\n",strerror(errno), errno);return -1;}/* set pcap filter */memset(pcap_packet_filter, 0, FILTER_BUFFER_SIZE);init_packet_filter(pcap_packet_filter, FILTER_BUFFER_SIZE);if (pcap_compile(pcap_dev, &filter_code,pcap_packet_filter, 1, IPV4_NETWORK_MASK) < 0) {pcap_perror(pcap_dev, "pcap_compile");return -1;}if (pcap_setfilter(pcap_dev, &filter_code) < 0) {pcap_perror(pcap_dev, "pcap_setfilter");return -1;}pcap_loop(pcap_dev, -1, pcap_callback, NULL);printf("pcap_loop ended\n");
}

line1-6:定义全局变量pcap_dev,方便在信号处理函数中使用,信号处理函数调用pcap_breakloop终止pcap_loop循环。
line8-24: 注册信号处理函数,捕获SIGINT信号,终端键入ctrl+c时,前台进程组会收到SIGINT信号,终止进程。pcap_open_live指定从eth0网络接口的链路层接收数据帧。长度65536 > 1518(字节),0为不打开混杂模式,由于虚拟IP也是与本机MAC地址建立映射,所以不必打开混杂模式。TIMEOUT_MS为512,pcap_loop在没有数据帧接收时,poll也没有被信号打断的情况下,内部循环512ms一次,err_buf返回pcap_open_live出错时的信息。
line26-37: init_packet_filter初始化帧过滤字符串,将清零之后的pcap_packet_filter初始化为”ether proto 0x0800 or ether proto 0x0806”。pcap_complie生成帧过滤器filter_code,optimize值为1,子网掩码为255.255.255.0,再调用pcap_setfilter设置帧过滤器生效。
Line39: 开始接收链路层数据帧,个数为无限 (-1) ,回调函数pcap_callback处理接收到的数据帧,pcap_callback的参数为NULL。
接下来给出main函数中的头文件,宏定义:

  #include <stdio.h>#include <string.h>#include <errno.h>#include <signal.h>#include <pcap/pcap.h>#define MAX_NETWORK_SEGMENT_SIZE        65535#define PROMISC_ENABLE                  1#define TIMEOUT_MS                      512#define FILTER_BUFFER_SIZE              256#define IPV4_NETWORK_MASK               0xffffff00#define ETHERNET_IP                     0x0800#define ETHERNET_ARP                    0x0806#define ETHERNET_ADDR_LEN               6#define MACSTR "%02x:%02x:%02x:%02x:%02x:%02x"#define MAC2STR(x) (x)[0], (x)[1], (x)[2], (x)[3], (x)[4], (x)[5]typedef struct _ethhdr {unsigned char dst[ETHERNET_ADDR_LEN];unsigned char src[ETHERNET_ADDR_LEN];unsigned short type;} __attribute__((packed)) ethhdr_t;

line1-14: 加入引用的头文件,最大数据帧长度65535,大于1500 + sizeof(Ethernet Header)1514字节即可。PROMISC_ENABLE为1,对于从0开始的代码实现,为丰富测试数据,接收目的MAC地址不是本机MAC地址的数据帧,使用混杂模式。对于DIY TCP/IP的实现,映射虚拟IP到本机MAC地址,不需要打开混杂模式。
ETHERNET_IP,ETHERNET_ARP为以太网类型,分别带表IPv4网际协议和ARP地址解析协议类型。通过wireshark捕获数据帧,验证定义的正确性:

上图为wireshark 抓到的TCP数据帧,以太网类型字段为0x0800。

有的朋友会注意到,还有以太网类型为0x8100的ARP数据帧,用于802.1Q VLAN。对于DIY TCP/IP,过滤接收IPv4和ARP数据帧即可。
line16-17:MACSTR为MAC地址的格式化输出字符串,MAC2STR和MACSTR配合打印输出MAC地址。
line19-23: 以太网头部结构体定义,从图1或图2中可以看到,以太网头部分为三个部分,目的MAC地址:6 bytes, 源MAC地址:6 bytes,类型:2 bytes。
最后给出main函数中用到的init_packet_filter和pcap_callback的实现:

 static void init_packet_filter(char *pcap_packet_filter, unsigned int size){if (pcap_packet_filter == NULL || size == 0) {printf("Null packet filter: %p or Size: %u\n",pcap_packet_filter, size);return;}snprintf(pcap_packet_filter, size,"ether proto 0x%04x or ether proto 0x%04x",ETHERNET_IP, ETHERNET_ARP);printf("filter: %s\n", pcap_packet_filter);}

line1-14: 通过sprintf将pcap_packet_filter格式化为” ether proto 0x0800 or ether proto 0x0806”,同时打印出格式化后的字符串验证。

 static void pcap_callback(unsigned char *arg,const struct pcap_pkthdr *pkthdr, const unsigned char *packet){ethhdr_t *ethpkt = NULL;if (packet == NULL)return;ethpkt = (ethhdr_t *)packet;printf("%ld.%06u: capture length: %u, pkt length: %u, ethernet type: %04x, "MACSTR " --> " MACSTR"\n",pkthdr->ts.tv_sec, pkthdr->ts.tv_usec, pkthdr->caplen, pkthdr->len,ethpkt->type, MAC2STR(ethpkt->src), MAC2STR(ethpkt->dst));}

pcap_callback用于打印输出收到数据帧的以太网头。
line4-8: 回调函数中pack是pcap_loop回传的有效载荷,pkthdr描述接收到的数据帧的长度。
line9-11: 打印输出数据帧的时间戳,有效载荷的长度,以太网类型字段,源MAC地址和目的MAC地址。
编译,运行:

  $ gcc -o device device.c -lpcap$ sudo ./device filter: ether proto 0x0800 or ether proto 0x08061528450824.298054: capture length: 243, pkt length: 243, ethernet type: 0008, fc:4d:d4:39:c4:5e --> ff:ff:ff:ff:ff:ff1528450824.298331: capture length: 235, pkt length: 235, ethernet type: 0008, bc:30:5b:a2:ee:24 --> ff:ff:ff:ff:ff:ff1528450824.677061: capture length: 60, pkt length: 60, ethernet type: 0608, c8:5b:76:04:fd:94 --> ff:ff:ff:ff:ff:ff1528450825.030632: capture length: 92, pkt length: 92, ethernet type: 0008, 28:d2:44:7e:62:32 --> ff:ff:ff:ff:ff:ff1528450825.168767: capture length: 64, pkt length: 64, ethernet type: 0081, 8c:b6:4f:57:9e:bc --> ff:ff:ff:ff:ff:ff1528450825.387166: capture length: 64, pkt length: 64, ethernet type: 0081, 00:19:2f:91:cd:ff --> ff:ff:ff:ff:ff:ff^Cpcap_loop ended

从0开始的代码保存为device.c,后续章节将基于device.c实现DIY TCP/IP的网络设备模块。
line1: gcc编译device.c指定动态链接库-lpcap
line2: 超级权限运行device, 使用PF_PACK域的socket需要超级权限才能从链路层接收数据帧。
line3: 格式化输出的帧过滤字符串。
line4-9: 接收到的链路层数据帧,以太网类型字段是大端格式,后面章节介绍大小端转换的函数的实现。
line10: 在终端键入ctrl+c,终止pcap_loop函数。打印输出”pcap_loop ended”,此处的打印输出,在后续章节扩展实现成DIY TCP/IP在pcap_loop运行结束后,销毁网络设备模块,IP模块,TCP模块以及其他模块的代码。
4.6 Makfile
现在已经可以从链路层接收数据帧了,并简单实现了以太网头的解析。本节来动手写Makefile,后续章节会不断的扩充本节实现的Makefile,加入后续代码的编译。
目前只有一个device.c文件,把device.c用到的宏定义,结构体的定义,可以暴露给别的模块使用的函数接口等,写到头文件中。只有device.c文件用到的,例如PROMISC_ENABLE宏,单独放在device.h文件中,以太网头结构体的定义,不仅仅只有device.c用到,ARP模块的实现也会用到,将其放在common.h头文件中。按照使用场景分类,将代码模块化如下:
device.h

 #ifndef _DEVICE_H_#define _DEVICE_H_#define MAX_NETWORK_SEGMENT_SIZE       65535#define PROMISC_ENABLE                   1#define PROMISC_DISABLE                  0#define TIMEOUT_MS                        512#define FILTER_BUFFER_SIZE              256#endif

把调用libpcap库函数时用到的参数,定义成对应的宏,放在device.h头文件中,包括snaplen, promisc, timeout的数值, 生成帧过滤器的buffer size。
init.h

 #ifndef _INIT_H_#define _INIT_H_//#define DEFAULT_IFNAME "eth0"#define DEFAULT_IFNAME "ens33"#endif

init.h存放DIY TCP/IP的初始化信息,目前只有默认网络接口的名称eth0。ens33是在更新的ubuntu版本上出现的由udev命名规则确定的网络接口名称。
common.h

  #ifndef _COMMON_H_#define _COMMON_H_#define IPV4_NETWORK_MASK    0xffffff00#define ETHERNET_IP           0x0800#define ETHERNET_ARP          0x0806#define ETHERNET_ADDR_LEN    6#define MACSTR "%02x:%02x:%02x:%02x:%02x:%02x"#define MAC2STR(x) (x)[0], (x)[1], (x)[2], (x)[3], (x)[4], (x)[5]typedef struct _ethhdr {unsigned char dst[ETHERNET_ADDR_LEN];unsigned char src[ETHERNET_ADDR_LEN];unsigned short type;} __attribute__((packed)) ethhdr_t;#endif 

common.h存放子网掩码,以太网类型:IP,ARP,MAC地址格式化输出宏,以太网头部结构体定义,packed限定ethhdr_t数据结构按字节对齐。解析从链路层接收到的数据帧,直接把payload的指针强制转换为以太网头部数据结构,以太网头是14字节,不是4字节对齐的,所以结构体定义要加上对齐属性。
代码已经划分完成,现在有4个文件device.c,device.h,init.h,common.h,开始写Makefile,之后的代码实现会不断扩充本节划分出来的模块。
Makefile语法规则如下
target : prerequisites
command
target是目标名称,prerequesties是生成目标文件的依赖,依赖可以是其他目标文件或头文件,command是生成target需要执行的命令行。先不去了解Makefile其他复杂的语法规则,就以能够编译生成tcp_ip_stack目标文件为目的,在prerequesties中加入对头文件的依赖,command写成gcc编译的命令行。

 tcp_ip_stack:device.ogcc -o tcp_ip_stack device.o -lpcapdevice.o:device.c device.h init.h common.hgcc -c device.cclean:rm -rf *.orm -rf tcp_ip_stack

line1-2: target是tcp_ip_stack elf文件,依赖的目标文件为device.o,命令行gcc –o 生成可执行文件,动态库为lpcap。
line3-4: target是device.o目标文件,依赖为device.c和编译需要的头文件,命令行是gcc –c生成device.o目标文件。
line5-7: target是clean,没有依赖,命令行是shell的命令,清除当前目录的所有目标文件和生成的elf tcp_ip_stack可执行文件。
下面是在终端键入make和make clean时的输出结果

 $ makegcc -c device.cgcc -o tcp_ip_stack device.o -lpcap$ make cleanrm -rf *.orm -rf tcp_ip_stack

本章小结之前先来扩充一下common.h头文件,实现NTOHS,NTOHL完成16位无符号整型值unsigned short和32位无符号整型值unsigned int的大小端转换,以小端格式输出以太网头部的类型字段。
将NTOHS,NTOHL,HSTON和HLTON放在common.h头文件中,便于各个模块调用。

  #define NTOHS(x) ({\unsigned short val = (x);\unsigned char *b = (unsigned char *)&(val);\b[0] << 8 | b[1]; })#define NTOHL(x) ({\unsigned int val = (x);\unsigned char *b = (unsigned char *)&(val); \b[0] << 24 | b[1] << 16 | b[2] << 8 | b[3];  })#define HSTON    NTOHS                                   #define HLTON    NTOHL                                   

line1-4: NTOHS宏,N代表网络端(大端),H代表主机端(小端),S代表16无符号整型值,该宏用于16位无符号整型值大端转换成小端,宏定义返回最后一条语句的结果。
line5-8: 同NTOHS,该宏用于32位无符号整型值大端转换成小端。
line10-11: 定义主机端16位和32位无符号整型值,小端向大端转换。
再来修改一下,pcap_callback回调函数中,对以太网头部类型的打印,以太网头部类型是unsigned short,用NTOHS将其转换成小端格式。

  static void pcap_callback(unsigned char *arg,const struct pcap_pkthdr *pkthdr,
const unsigned char *packet){ethhdr_t *ethpkt = NULL;if (packet == NULL)return;ethpkt = (ethhdr_t *)packet;printf("%ld.%06ld: capture length: %u, pkt length: %u
ethernet type: %04x, "MACSTR " --> " MACSTR"\n",pkthdr->ts.tv_sec, pkthdr->ts.tv_usec, pkthdr->caplen, pkthdr->len,NTOHS(ethpkt->type), MAC2STR(ethpkt->src), MAC2STR(ethpkt->dst));}

NTOHS高亮显示改动的部分,编译运行,打印输出如下:

  $ makegcc -c device.cgcc -o tcp_ip_stack device.o -lpcap$ sudo ./tcp_ip_stack [sudo] password for gannicus: filter: ether proto 0x0800 or ether proto 0x08061529750254.336010: capture length: 131, pkt length: 131, ethernet type: 0800, dc:33:0d:2a:95:33 --> ff:ff:ff:ff:ff:ff1529750255.394534: capture length: 421, pkt length: 421, ethernet type: 0800, 20:6b:e7:4a:a8:2b --> 01:00:5e:7f:ff:fa1529750255.398825: capture length: 430, pkt length: 430, ethernet type: 0800, 20:6b:e7:4a:a8:2b --> 01:00:5e:7f:ff:fa1529750255.403594: capture length: 493, pkt length: 493, ethernet type: 0800, 20:6b:e7:4a:a8:2b --> 01:00:5e:7f:ff:fa1529750255.408557: capture length: 489, pkt length: 489, ethernet type: 0800, 20:6b:e7:4a:a8:2b --> 01:00:5e:7f:ff:fa1529750255.412843: capture length: 469, pkt length: 469, ethernet type: 0800, 20:6b:e7:4a:a8:2b --> 01:00:5e:7f:ff:fa

可以看到ethernet type已经变成小端值输出。
4.7 小结
本章讲述了从零开始的实现代码,现在已经可从链路层接收到数据帧了, 完成了代码模块化,并且已经自己动手写了Makefile。DIY TCP/IP的每个章节结束时,会列出对应代码实现的目录结构:

下一篇: DIY TCP/IP网络设备模块1

Gannicus Guo的DIY TCP/IP之旅相关推荐

  1. Gannicus Guo的DIY TCP/IP 连载目录

    Gannicus Guo的DIY TCP/IP 连载目录 该专栏描述本人自己动手写的一个简化的TCP/IP协议.经测试该协议可以运行在Ubuntu 16.10 x86_64 操作系统用户空间.便于描述 ...

  2. DIY TCP/IP IP模块和ICMP模块的实现2

    上一篇:DIY TCP/IP IP模块和ICMP模块的实现1 本节在8.2节的基础上扩展icmp_recv函数,检验接收到的ICMP数据帧的校验和,解析ICMP数据帧头部的type字段,根据ICMP数 ...

  3. DIY TCP/IP IP模块和ICMP模块的实现1

    上一篇:DIY TCP/IP IP模块和ICMP模块的实现0 8.2 IP数据帧的接收 本节实现DIY TCP/IP的IP数据帧的接收,6.1节介绍pdbuf模块时已经引入了IP头部结构体的定义,ip ...

  4. DIY TCP/IP TCP模块的实现9

    上一篇:DIY TCP/IP TCP模块的实现8 9.11 TCP滑动窗口的实现3 9.10节的DIY TCP/IP已经可以正确接收TCP数据帧了.9.10节只是解析一下收到的TCP数据帧携带的数据长 ...

  5. TCP/IP详解卷1中文版勘误表前言

    相信仔细阅读过TCP/IP这一经典著作中文版的读者们最痛苦的就是其中一些语句或者词汇感觉无法理解,其后果要么是无法理解,要么理解错误,如果错误的概念在脑中根深蒂固了,对于以后的学习和工作将是十分令人苦 ...

  6. linux端口加密,通过OpenSSH的端口转发功能加密和解密tcp/ip数据

    现在的一些服务例如telnet,FTP需要通过tcp/ip协议来进行数据传输,由于受到协议本身的限制,这些服务的的传输往往都是明文的,造成了很大的安全隐患,而我们的OpwnSSH传输的数据却是经过加密 ...

  7. 老吴的 Xmind / 网络是怎样连接的 / 传输 TCP/IP 数据-探索协议栈和网卡

    一.简介 无惊无险,又到了周五,最近在看一些关于计算机网络的书,积累了一些笔记,分享给大家,助力嵌入式人才涨薪计划. 最近看的书比较多,我发现看书也是分好坏2个方面的. 好的地方是知识点比较全面,成体 ...

  8. 探索TCP状态机之旅:发现网络连接的生命周期与神秘魅力

    目录标题 前言 TCP状态简介 TCP状态机的目的与功能 TCP状态在连接建立.数据传输和连接关闭过程中的作用 TCP状态详解 LISTEN:服务器监听来自客户端的连接请求. SYN\_SENT:客户 ...

  9. HTTP 协议入门 — (TCP/IP协议族、通信传输流、URI 与 URL 的区别、Cookie 状态管理、HTTP 支持的方法、状态码类别、HTTP 首部字段)

    TCP/IP协议族 在介绍 HTTP 协议之前,我们先对 TCP/IP 协议族有个大概的了解,TCP/IP 协议从上到下主要分为应用层.传输层.网络层和数据链路层,各层的主要功能如下表所示: 协议层 ...

最新文章

  1. netscaler密码恢复
  2. html滚动条样式自定义,CSS3自定义滚动条样式
  3. Spring-AOP 引介切面
  4. web.py开发web 第一章 Hello World
  5. 矩阵的Cholesky分解
  6. Python学习相关资料
  7. win10电脑插耳机没声音_Win10如何录制电脑内部声音
  8. VB2010(24)窗体用户控件
  9. Windows 环境搭建Redis集群之无脑教程
  10. 文书录入登记软件的其它模块源码
  11. 缓存算法篇其一-----FIFO(先入先出)
  12. 用Python来实现2~7阶行列式的计算
  13. 金典《歌手》2019即将首播 创作季上演神仙打架
  14. leetcode-954. 二倍数对数组
  15. 【饭谈】面试官:速斩此子,切不可引狼入室
  16. 警惕,老外也诈骗!!
  17. 端到端训练 联合训练_中巴空军“雄鹰-Ⅷ”联合训练:首次实现全过程体系对抗...
  18. 鼠标事件,显示悬浮窗
  19. python写百行代码可运行_56 岁潘石屹学俩月 Python ,写下百行代码
  20. mybatis collection column 传常量

热门文章

  1. 网页设计中有意思的404页面赏析
  2. 正则表达式-替换网址
  3. 产品经理知识体系:6.如何做好产品运营?
  4. 永中Office—公文打印
  5. AdminLTE 拖拽,AdminLTE卡片移动,AdminLTE 拖拽移动
  6. Android测试接口时超时
  7. 软件测试质量体系管控
  8. ChatGPT全球爆火,究竟有何特别之处?
  9. Python中实现文本分类(附代码、数据集)
  10. 云台控制html模板,使用vue视频播放器上增加云台控制面板