├── doc ├── images │ ├── README.md │ ├── TCPIP.png │ ├── TELNET.png │ ├── icmp-packet.png │ ├── iptables-chains.png │ └── iptables-tables.png ├── README.md ├── tcpipinit.md ├── systemcall.md ├── arp.md ├── socket.md ├── setupMenuOS.md ├── ip.md ├── tcpip.md ├── socketSourceCode.md ├── netutilities.md ├── tcp.md └── dataflow.md ├── lab1 ├── 互联网概述.pptx └── README.md ├── lab3 ├── TCP.pptx ├── TCP协议栈源代码分析.pptx ├── BuildLinuxSystem.pptx ├── Makefile ├── README.md ├── menu.h ├── linktable.h ├── menu.c ├── syswrapper.h ├── linktable.c └── main.c ├── lab5 └── ARP.pptx ├── lab7 └── DNS.pptx ├── lab8 ├── 网络安全等级保护.pptx └── 互联网架构设计背后的渊源.pptx ├── lab6 └── L2Switching.pptx ├── lab2 ├── ConcurrentServer.pptx ├── SocketProgramming.pptx ├── socket_workspace │ ├── README.md │ ├── dbtime.h │ ├── Makefile │ ├── server.c │ ├── client.c │ ├── socketwrapper.h │ └── dbtime.c ├── README.md ├── Makefile ├── udp_client.c ├── udp_server.c ├── tcp_client.c └── tcp_server.c ├── lab4 ├── How IP Networking.pptx └── README.md ├── README.md └── np2019.md /doc/images/README.md: -------------------------------------------------------------------------------- 1 | ## 图片 2 | -------------------------------------------------------------------------------- /lab1/互联网概述.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mengning/net/HEAD/lab1/互联网概述.pptx -------------------------------------------------------------------------------- /lab3/TCP.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mengning/net/HEAD/lab3/TCP.pptx -------------------------------------------------------------------------------- /lab5/ARP.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mengning/net/HEAD/lab5/ARP.pptx -------------------------------------------------------------------------------- /lab7/DNS.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mengning/net/HEAD/lab7/DNS.pptx -------------------------------------------------------------------------------- /doc/images/TCPIP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mengning/net/HEAD/doc/images/TCPIP.png -------------------------------------------------------------------------------- /lab8/网络安全等级保护.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mengning/net/HEAD/lab8/网络安全等级保护.pptx -------------------------------------------------------------------------------- /doc/images/TELNET.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mengning/net/HEAD/doc/images/TELNET.png -------------------------------------------------------------------------------- /lab3/TCP协议栈源代码分析.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mengning/net/HEAD/lab3/TCP协议栈源代码分析.pptx -------------------------------------------------------------------------------- /lab6/L2Switching.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mengning/net/HEAD/lab6/L2Switching.pptx -------------------------------------------------------------------------------- /lab8/互联网架构设计背后的渊源.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mengning/net/HEAD/lab8/互联网架构设计背后的渊源.pptx -------------------------------------------------------------------------------- /doc/images/icmp-packet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mengning/net/HEAD/doc/images/icmp-packet.png -------------------------------------------------------------------------------- /lab2/ConcurrentServer.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mengning/net/HEAD/lab2/ConcurrentServer.pptx -------------------------------------------------------------------------------- /lab2/SocketProgramming.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mengning/net/HEAD/lab2/SocketProgramming.pptx -------------------------------------------------------------------------------- /lab3/BuildLinuxSystem.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mengning/net/HEAD/lab3/BuildLinuxSystem.pptx -------------------------------------------------------------------------------- /lab4/How IP Networking.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mengning/net/HEAD/lab4/How IP Networking.pptx -------------------------------------------------------------------------------- /doc/images/iptables-chains.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mengning/net/HEAD/doc/images/iptables-chains.png -------------------------------------------------------------------------------- /doc/images/iptables-tables.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mengning/net/HEAD/doc/images/iptables-tables.png -------------------------------------------------------------------------------- /lab2/socket_workspace/README.md: -------------------------------------------------------------------------------- 1 | # Socket Workspace 2 | 3 | 只是实现客户端连接服务器并从服务器接收hello world字串 4 | 5 | * 直接make编译 6 | * 服务器端直接执行./server 7 | * 客户端执行:./client 127.0.0.1 8 | 9 | 连接服务器并从服务器接收hello world字串所用的时间会记录在dbtime.time文件中 10 | -------------------------------------------------------------------------------- /lab2/README.md: -------------------------------------------------------------------------------- 1 | # Socket网络编程 2 | 3 | ## 实验 4 | 5 | * 在默认情况下ubuntu没有提供c/c++编译环境, ubuntu提供了build-essential包让一次把相关软件安装好 sudo apt install build-essential 6 | * 熟悉Socket API接口的用法 7 | 8 | # 作业:编写一个简单的网络聊天程序 9 | 10 | * 请参考本章节的范例代码完成一个hello/hi的简单的网络聊天程序,并完成一篇博客文章解释Socket API接口的用法及程序代码的实现 11 | * 博客中请注明您的用户Id(推荐另外加上真实署名),并列出参考资料来源https://github.com/mengning/net 12 | * 要求思路清晰,便于理解Socket API工作机制。 13 | -------------------------------------------------------------------------------- /lab4/README.md: -------------------------------------------------------------------------------- 1 | ## 编译构建调试Linux系统 2 | 3 | ### 实验:编译构建调试Linux系统 4 | 自定搭建环境Based on Ubuntu 18.04 & linux-5.0.1,或使用在线环境:[实验楼虚拟机](https://www.shiyanlou.com/courses/1198)&[linux-src](https://github.com/torvalds/linux/blob/v5.4/) 5 | 6 | 7 | ### 作业:编译构建调试Linux系统并实际跟踪Linux内核IP协议栈 8 | 9 | * 请根据本章节的内容及自己的实验过程记录下编译构建调试Linux系统并实际跟踪Linux内核IP协议栈,然后完成一篇博客文章供他人实验或学习IP时参考,具体要求如下: 10 | * 博客中请注明您的用户Id(推荐另外加上真实署名),并列出参考资料来源https://github.com/mengning/net 11 | * 重点分析路由表及路由选择的过程 12 | * 要求思路清晰,便于查阅参考。 13 | -------------------------------------------------------------------------------- /doc/README.md: -------------------------------------------------------------------------------- 1 | # 做中学计算机网络 2 | 3 | ## [通过网络命令学习计算机网络](netutilities.md) 4 | 5 | ### [配置Linux系统连接Internet](netutilities.md#配置linux系统连接internet) 6 | ### 7 | 8 | ## [通过Socket编程接口学习计算机网络](socket.md) 9 | 10 | 11 | 12 | ## 庖丁解牛Linux网络协议栈 13 | 14 | * [构建调试Linux内核网络代码的环境MenuOS系统](setupMenuOS.md) 15 | * [系统调用相关代码分析](systemcall.md) 16 | * [Socket接口对应的Linux内核系统调用处理代码](socketSourceCode.md) 17 | * [Linux内核初始化过程中加载TCP/IP协议栈](tcpip.md) 18 | * [TCP/IP协议栈的初始化](tcpipinit.md) 19 | * [TCP协议](tcp.md) 20 | * [IP协议](ip.md) 21 | * [ARP协议](arp.md) 22 | -------------------------------------------------------------------------------- /lab3/Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # Makefile for menu program 3 | # 4 | 5 | CC_PTHREAD_FLAGS = -lpthread 6 | CC_FLAGS = -c 7 | CC_OUTPUT_FLAGS = -o 8 | CC = gcc 9 | RM = rm 10 | RM_FLAGS = -f 11 | 12 | TARGET = init 13 | OBJS = linktable.o menu.o main.o 14 | 15 | all: $(OBJS) 16 | $(CC) $(CC_OUTPUT_FLAGS) $(TARGET) $(OBJS) -static -lpthread 17 | 18 | .c.o: 19 | $(CC) $(CC_FLAGS) $< 20 | 21 | clean: 22 | $(RM) $(RM_FLAGS) $(OBJS) $(TARGET) *.bak init 23 | -------------------------------------------------------------------------------- /lab2/Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # Makefile for Socket Program 3 | # 4 | 5 | CC_FLAGS = -c 6 | CC_OUTPUT_FLAGS = -o 7 | CC = gcc 8 | RM = rm 9 | RM_FLAGS = -f 10 | 11 | 12 | all: 13 | $(CC) $(CC_OUTPUT_FLAGS) tcp_server tcp_server.c 14 | $(CC) $(CC_OUTPUT_FLAGS) tcp_client tcp_client.c 15 | $(CC) $(CC_OUTPUT_FLAGS) udp_server udp_server.c 16 | $(CC) $(CC_OUTPUT_FLAGS) udp_client udp_client.c 17 | 18 | clean: 19 | $(RM) $(RM_FLAGS) *.o tcp_server tcp_client udp_server udp_client 20 | -------------------------------------------------------------------------------- /lab1/README.md: -------------------------------------------------------------------------------- 1 | # 实验环境安装配置 2 | 3 | * 安装Linux系统, 以Ubuntu虚拟机为例 4 | * VirtualBox https://www.virtualbox.org/wiki/Downloads 或 VMware Workstation Player https://my.vmware.com/en/web/vmware/free#desktop_end_user_computing/vmware_workstation_player 5 | * Ubuntu Desktop https://www.ubuntu.com/download/desktop 6 | * 配置上网 7 | * 配置共享文件夹 8 | * Linux目录结构 9 | * Linux常用命令ls、cd、pwd、cat、mkdir 10 | 11 | ## 实验 12 | 13 | * 熟悉本章节涉及的网络相关命令 14 | 15 | 16 | # 作业:电脑无法上网该怎么办? 17 | 18 | * 请根据本章节的内容思考“电脑无法上网该怎么办?”,并完成一篇博客文章供他人在遇到网络问题时参考,具体要求如下: 19 | * 博客中请注明您的用户Id(推荐另外加上真实署名),并列出参考资料来源https://github.com/mengning/net 20 | * 分析电脑无法上网的可能原因,并通过检查网络配置排除和聚焦,最终给出每种可能原因的解决方法 21 | * 要求思路清晰,有便于查阅参考。 22 | 23 | 24 | -------------------------------------------------------------------------------- /lab2/socket_workspace/dbtime.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2012, by the NoSQL project team. 2 | // Hacked by Baojian Hua. 3 | 4 | #ifndef DBTIME_H 5 | #define DBTIME_H 6 | 7 | // May also print to "outfile". 8 | extern char *dbtime_filename; 9 | 10 | // start timing 11 | void dbtime_start (void); 12 | // start a test with name "testname". 13 | void dbtime_startTest (const char *testname); 14 | // end timing, but does nothing else. 15 | void dbtime_end (void); 16 | // end timing, and meanwhile, write out time status 17 | void dbtime_endAndShow (void); 18 | // Show the different between the last time sequence. 19 | // For now, only supports unnested timing. Do we need 20 | // nested timing? 21 | void dbtime_show (void); 22 | // may also prints to file. 23 | void dbtime_show2 (FILE*); 24 | 25 | // 26 | void dbtime_finalize (void); 27 | 28 | #endif 29 | -------------------------------------------------------------------------------- /lab2/socket_workspace/Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # Makefile for Workspace 3 | # 4 | 5 | CC_PTHREAD_FLAGS = -lpthread 6 | CC_TIME_FLAGS = -lrt 7 | CC_FLAGS = -c 8 | CC_OUTPUT_FLAGS = -o 9 | CC = gcc 10 | RM = rm 11 | RM_FLAGS = -f 12 | 13 | SERVER_TARGET = server 14 | CLIENT_TARGET = client 15 | 16 | TARGETS = $(SERVER_TARGET) $(CLIENT_TARGET) 17 | 18 | COMMON_OBJS = 19 | SERVER_OBJS = server.o $(COMMON_OBJS) 20 | CLIENT_OBJS = dbtime.o client.o $(COMMON_OBJS) 21 | 22 | OBJS = $(COMMON_OBJS) $(SERVER_OBJS) $(CLIENT_OBJS) 23 | 24 | all: $(OBJS) $(TARGETS) 25 | 26 | $(SERVER_TARGET):$(SERVER_OBJS) 27 | $(CC) $(CC_OUTPUT_FLAGS) $(SERVER_TARGET) $(SERVER_OBJS) $(CC_PTHREAD_FLAGS) 28 | 29 | $(CLIENT_TARGET):$(CLIENT_OBJS) 30 | $(CC) $(CC_OUTPUT_FLAGS) $(CLIENT_TARGET) $(CLIENT_OBJS) $(CC_PTHREAD_FLAGS) $(CC_TIME_FLAGS) 31 | 32 | .c.o: 33 | $(CC) $(CC_FLAGS) $< 34 | 35 | clean: 36 | $(RM) $(RM_FLAGS) $(OBJS) $(TARGETS) *.bak dbtime.time 37 | -------------------------------------------------------------------------------- /lab2/udp_client.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | int main(int argc, char *argv[]) 10 | { 11 | int client_sockfd; 12 | int len; 13 | struct sockaddr_in remote_addr; //服务器端网络地址结构体 14 | int sin_size; 15 | char buf[BUFSIZ]; //数据传送的缓冲区 16 | memset(&remote_addr,0,sizeof(remote_addr)); //数据初始化--清零 17 | remote_addr.sin_family=AF_INET; //设置为IP通信 18 | remote_addr.sin_addr.s_addr=inet_addr("127.0.0.1");//服务器IP地址 19 | remote_addr.sin_port=htons(8000); //服务器端口号 20 | 21 | /*创建客户端套接字--IPv4协议,面向无连接通信,UDP协议*/ 22 | if((client_sockfd=socket(PF_INET,SOCK_DGRAM,0))<0) 23 | { 24 | perror("socket"); 25 | return 1; 26 | } 27 | strcpy(buf,"This is a test message"); 28 | printf("sending: '%s'\n",buf); 29 | sin_size=sizeof(struct sockaddr_in); 30 | 31 | /*向服务器发送数据包*/ 32 | if((len=sendto(client_sockfd,buf,strlen(buf),0,(struct sockaddr *)&remote_addr,sizeof(struct sockaddr)))<0) 33 | { 34 | perror("recvfrom"); 35 | return 1; 36 | } 37 | close(client_sockfd); 38 | return 0; 39 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 互联网体系结构/庖丁解牛Linux网络协议栈 2 | 3 | 本课程从实践入手循序渐进,以Linux系统环境和Linux内核源代码为例,将Linux网络相关命令用法、Socket网络编程、TCP协议、IP协议及路由表、ARP协议及ARP缓存、二层交换网络的学习转发和过滤数据库等互联网架构的关键环节一一解析,并通过MenuOS实验系统进行代码跟踪分析。最终理解分析打开一个网页背后互联网的工作过程,其中重点分为三个抽象层次:一是便于人类理解的记忆的编址方式DNS Naming;二是便于全球定位编址和路由的IP Networking;三是便于局域网中实际完成数据交换传输的Layer 2 Switching;同时在理解互联网体系结构的基础上探寻它的历史演化渊源,乃至发现它背后的设计哲学,解读未来网络的演进方向。 4 | 5 | ## 互联网概述 6 | * 1.1 课程内容简介 7 | * 1.2 网络协议基础 8 | * 1.3 上网浏览网页背后的网络通信过程 9 | * 1.4 实验环境安装配置 10 | * 1.5 网络相关命令 11 | 12 | ## Socket网络编程 13 | 14 | * 2.1 编译、构建和调试 15 | * 2.2 Socket接口 16 | * 2.3 UDP范例代码 17 | * 2.4 TCP范例代码 18 | 19 | ## TCP协议 20 | 21 | * 3.1 TCP协议概述 22 | * 3.2 Linux网络协议栈源代码简介 23 | * 3.3 Linux系统的编译、构建和调试 24 | * 3.4 TCP协议源代码分析 25 | 26 | ## IP协议及路由表 27 | 28 | * IP协议基础 29 | * 路由表 30 | * 路由转发举例 31 | * IP协议栈源代码解析 32 | * 路由协议简介 33 | * 网络层数据传输路径解析 34 | 35 | ## ARP协议及ARP缓存 36 | 37 | * ARP协议基础 38 | * ARP解析的过程 39 | * ARP解析在网络传输过程中的作用 40 | * ARP协议栈源代码解析 41 | 42 | ## 二层交换网络及转发过滤数据库 43 | 44 | * 以太网基础 45 | * 交换机的学习、转发和过滤数据库 46 | * 常见二层协议 47 | * 数据链路层在Linux网络协议栈中的一些关键代码分析 48 | 49 | ## DNS协议及域名存储与解析 50 | 51 | * DNS协议基础 52 | * DNS域名的存储 53 | * DNS域名解析过程分析 54 | 55 | ## 互联网架构设计背后的渊源 56 | 57 | * 互联网架构设计的最初动机与核心目标 58 | * 互联网架构设计的具体目标及背后重要权衡 59 | 60 | -------------------------------------------------------------------------------- /lab2/udp_server.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | int main(int argc, char *argv[]) 10 | { 11 | int server_sockfd; 12 | int len; 13 | struct sockaddr_in my_addr; //服务器网络地址结构体 14 | struct sockaddr_in remote_addr; //客户端网络地址结构体 15 | int sin_size; 16 | char buf[BUFSIZ]; //数据传送的缓冲区 17 | memset(&my_addr,0,sizeof(my_addr)); //数据初始化--清零 18 | my_addr.sin_family=AF_INET; //设置为IP通信 19 | my_addr.sin_addr.s_addr=INADDR_ANY;//服务器IP地址--允许连接到所有本地地址上 20 | my_addr.sin_port=htons(8000); //服务器端口号 21 | 22 | /*创建服务器端套接字--IPv4协议,面向无连接通信,UDP协议*/ 23 | if((server_sockfd=socket(PF_INET,SOCK_DGRAM,0))<0) 24 | { 25 | perror("socket"); 26 | return 1; 27 | } 28 | 29 | /*将套接字绑定到服务器的网络地址上*/ 30 | if (bind(server_sockfd,(struct sockaddr *)&my_addr,sizeof(struct sockaddr))<0) 31 | { 32 | perror("bind"); 33 | return 1; 34 | } 35 | sin_size=sizeof(struct sockaddr_in); 36 | printf("waiting for a packet...\n"); 37 | 38 | /*接收客户端的数据并将其发送给客户端--recvfrom是无连接的*/ 39 | if((len=recvfrom(server_sockfd,buf,BUFSIZ,0,(struct sockaddr *)&remote_addr,&sin_size))<0) 40 | { 41 | perror("recvfrom"); 42 | return 1; 43 | } 44 | printf("received packet from %s:\n",inet_ntoa(remote_addr.sin_addr)); 45 | buf[len]='\0'; 46 | printf("contents: %s\n",buf); 47 | close(server_sockfd); 48 | return 0; 49 | } -------------------------------------------------------------------------------- /lab2/tcp_client.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | int main(int argc, char *argv[]) 10 | { 11 | int client_sockfd; 12 | int len; 13 | struct sockaddr_in remote_addr; //服务器端网络地址结构体 14 | char buf[BUFSIZ]; //数据传送的缓冲区 15 | memset(&remote_addr,0,sizeof(remote_addr)); //数据初始化--清零 16 | remote_addr.sin_family=AF_INET; //设置为IP通信 17 | remote_addr.sin_addr.s_addr=inet_addr("127.0.0.1");//服务器IP地址 18 | remote_addr.sin_port=htons(8000); //服务器端口号 19 | 20 | /*创建客户端套接字--IPv4协议,面向连接通信,TCP协议*/ 21 | if((client_sockfd=socket(PF_INET,SOCK_STREAM,0))<0) 22 | { 23 | perror("socket"); 24 | return 1; 25 | } 26 | 27 | /*将套接字绑定到服务器的网络地址上*/ 28 | if(connect(client_sockfd,(struct sockaddr *)&remote_addr,sizeof(struct sockaddr))<0) 29 | { 30 | perror("connect"); 31 | return 1; 32 | } 33 | printf("connected to server\n"); 34 | len=recv(client_sockfd,buf,BUFSIZ,0);//接收服务器端信息 35 | buf[len]='\0'; 36 | printf("%s",buf); //打印服务器端信息 37 | 38 | /*循环的发送接收信息并打印接收信息--recv返回接收到的字节数,send返回发送的字节数*/ 39 | while(1) 40 | { 41 | printf("Enter string to send:"); 42 | scanf("%s",buf); 43 | if(!strcmp(buf,"quit")) 44 | break; 45 | len=send(client_sockfd,buf,strlen(buf),0); 46 | len=recv(client_sockfd,buf,BUFSIZ,0); 47 | buf[len]='\0'; 48 | printf("received:%s\n",buf); 49 | } 50 | close(client_sockfd);//关闭套接字 51 | return 0; 52 | } -------------------------------------------------------------------------------- /lab3/README.md: -------------------------------------------------------------------------------- 1 | ## 编译构建调试Linux系统 2 | 3 | ### 实验:编译构建调试Linux系统 4 | 自定搭建环境Based on Ubuntu 18.04 & linux-5.0.1,或使用在线环境:[实验楼虚拟机](https://www.shiyanlou.com/courses/1198)&[linux-src](https://github.com/torvalds/linux/blob/v5.4/) 5 | 6 | 7 | ``` 8 | wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.0.1.tar.xz 9 | xz -d linux-5.0.1.tar.xz 10 | tar -xvf linux-5.0.1.tar 11 | cd linux-5.0.1 12 | sudo apt install build-essential flex bison libssl-dev libelf-dev libncurses-dev 13 | make defconfig 14 | make menuconfig # Kernel hacking—>Compile-time checks and compiler options ---> [*] Compile the kernel with debug info 15 | make 或 make -j*       # *为cpu核心数 16 | sudo apt install qemu 17 | qemu-system-x86_64 -kernel linux-5.0.1/arch/x86/boot/bzImage 18 | dd if=/dev/zero of=rootfs.img bs=1M count=128 19 | mkfs.ext4 rootfs.img 20 | mkdir rootfs 21 | sudo mount -o loop rootfs.img rootfs 22 | cd lab3 23 | make 24 | cp init rootfs/ 25 | sudo umount rootfs 26 | # KASLR是kernel address space layout randomization的缩写 27 | qemu-system-x86_64 -kernel linux-5.0.1/arch/x86_64/boot/bzImage -hda rootfs.img -append "root=/dev/sda init=/init nokaslr" -s -S 28 | # 另一个shell窗口 29 | gdb 30 | file linux-5.0.1/vmlinux 31 | target remote:1234 #则可以建立gdb和gdbserver之间的连接 32 | break start_kernel 33 | 按c 让qemu上的Linux继续运行 34 | ``` 35 | 36 | ### 作业:编译构建调试Linux系统并实际跟踪Linux内核TCP协议栈 37 | 38 | * 请根据本章节的内容及自己的实验过程记录下编译构建调试Linux系统并实际跟踪Linux内核TCP协议栈,然后完成一篇博客文章供他人实验或学习TCP时参考,具体要求如下: 39 | * 博客中请注明您的用户Id(推荐另外加上真实署名),并列出参考资料来源https://github.com/mengning/net 40 | * 要求思路清晰,有便于查阅参考。 41 | -------------------------------------------------------------------------------- /lab3/menu.h: -------------------------------------------------------------------------------- 1 | 2 | /**************************************************************************************************/ 3 | /* Copyright (C) mc2lab.com, SSE@USTC, 2014-2015 */ 4 | /* */ 5 | /* FILE NAME : menu.c */ 6 | /* PRINCIPAL AUTHOR : Mengning */ 7 | /* SUBSYSTEM NAME : menu */ 8 | /* MODULE NAME : menu */ 9 | /* LANGUAGE : C */ 10 | /* TARGET ENVIRONMENT : ANY */ 11 | /* DATE OF FIRST RELEASE : 2014/08/31 */ 12 | /* DESCRIPTION : This is a menu program */ 13 | /**************************************************************************************************/ 14 | 15 | /* 16 | * Revision log: 17 | * 18 | * Created by Mengning, 2014/08/31 19 | * 20 | */ 21 | 22 | /* Set Input Prompt */ 23 | int SetPrompt(char * p); 24 | /* add cmd to menu */ 25 | int MenuConfig(char * cmd, char * desc, int (*handler)()); 26 | 27 | /* Menu Engine Execute */ 28 | int ExecuteMenu(); 29 | 30 | -------------------------------------------------------------------------------- /lab2/tcp_server.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | int main(int argc, char *argv[]) 10 | { 11 | int server_sockfd;//服务器端套接字 12 | int client_sockfd;//客户端套接字 13 | int len; 14 | struct sockaddr_in my_addr; //服务器网络地址结构体 15 | struct sockaddr_in remote_addr; //客户端网络地址结构体 16 | int sin_size; 17 | char buf[BUFSIZ]; //数据传送的缓冲区 18 | memset(&my_addr,0,sizeof(my_addr)); //数据初始化--清零 19 | my_addr.sin_family=AF_INET; //设置为IP通信 20 | my_addr.sin_addr.s_addr=INADDR_ANY;//服务器IP地址--允许连接到所有本地地址上 21 | my_addr.sin_port=htons(8000); //服务器端口号 22 | 23 | /*创建服务器端套接字--IPv4协议,面向连接通信,TCP协议*/ 24 | if((server_sockfd=socket(PF_INET,SOCK_STREAM,0))<0) 25 | { 26 | perror("socket"); 27 | return 1; 28 | } 29 | 30 | /*将套接字绑定到服务器的网络地址上*/ 31 | if (bind(server_sockfd,(struct sockaddr *)&my_addr,sizeof(struct sockaddr))<0) 32 | { 33 | perror("bind"); 34 | return 1; 35 | } 36 | 37 | /*监听连接请求--监听队列长度为5*/ 38 | listen(server_sockfd,5); 39 | 40 | sin_size=sizeof(struct sockaddr_in); 41 | 42 | /*等待客户端连接请求到达*/ 43 | if((client_sockfd=accept(server_sockfd,(struct sockaddr *)&remote_addr,&sin_size))<0) 44 | { 45 | perror("accept"); 46 | return 1; 47 | } 48 | printf("accept client %s\n",inet_ntoa(remote_addr.sin_addr)); 49 | len=send(client_sockfd,"Welcome to my server\n",21,0);//发送欢迎信息 50 | 51 | /*接收客户端的数据并将其发送给客户端--recv返回接收到的字节数,send返回发送的字节数*/ 52 | while((len=recv(client_sockfd,buf,BUFSIZ,0))>0) 53 | { 54 | buf[len]='\0'; 55 | printf("%s\n",buf); 56 | if(send(client_sockfd,buf,len,0)<0) 57 | { 58 | perror("write"); 59 | return 1; 60 | } 61 | } 62 | close(client_sockfd); 63 | close(server_sockfd); 64 | return 0; 65 | } 66 | -------------------------------------------------------------------------------- /lab2/socket_workspace/server.c: -------------------------------------------------------------------------------- 1 | 2 | #include /* perror */ 3 | #include /* exit */ 4 | #include /* WNOHANG */ 5 | #include /* waitpid */ 6 | #include /* memset */ 7 | 8 | #include "socketwrapper.h" /* socket layer wrapper */ 9 | 10 | #define true 1 11 | #define false 0 12 | 13 | #define MYPORT 3490 /* 监听的端口 */ 14 | #define BACKLOG 10 /* listen的请求接收队列长度 */ 15 | 16 | int main() 17 | { 18 | int sockfd, new_fd; /* 监听端口,数据端口 */ 19 | struct sockaddr_in sa; /* 自身的地址信息 */ 20 | struct sockaddr_in their_addr; /* 连接对方的地址信息 */ 21 | int sin_size; 22 | 23 | if ((sockfd = Socket(PF_INET, SOCK_STREAM, 0)) == -1) 24 | { 25 | perror("socket"); 26 | exit(1); 27 | } 28 | 29 | sa.sin_family = AF_INET; 30 | sa.sin_port = Htons(MYPORT); /* 网络字节顺序 */ 31 | sa.sin_addr.s_addr = INADDR_ANY; /* 自动填本机IP */ 32 | memset(&(sa.sin_zero),0, 8); /* 其余部分置0 */ 33 | 34 | if ( Bind(sockfd, (struct sockaddr *)&sa, sizeof(sa)) == -1) 35 | { 36 | perror("bind"); 37 | exit(1); 38 | } 39 | 40 | if (Listen(sockfd, BACKLOG) == -1) 41 | { 42 | perror("listen"); 43 | exit(1); 44 | } 45 | 46 | /* 主循环 */ 47 | while(1) 48 | { 49 | sin_size = sizeof(struct sockaddr_in); 50 | new_fd = Accept(sockfd, (struct sockaddr *)&their_addr, &sin_size); 51 | if (new_fd == -1) 52 | { 53 | perror("accept"); 54 | continue; 55 | } 56 | 57 | printf("Got connection from %s\n", Inet_ntoa(their_addr.sin_addr)); 58 | if (fork() == 0) 59 | { 60 | /* 子进程 */ 61 | if (Send(new_fd, "Hello, world!\n", 14, 0) == -1) 62 | perror("send"); 63 | Close(new_fd); 64 | exit(0); 65 | } 66 | 67 | Close(new_fd); 68 | 69 | /*清除所有子进程 */ 70 | while(waitpid(-1,NULL,WNOHANG) > 0); 71 | 72 | } 73 | return true; 74 | } 75 | 76 | -------------------------------------------------------------------------------- /lab2/socket_workspace/client.c: -------------------------------------------------------------------------------- 1 | 2 | #include /* perror */ 3 | #include /* exit */ 4 | #include /* WNOHANG */ 5 | #include /* waitpid */ 6 | #include /* memset */ 7 | #include "dbtime.h" 8 | #include "socketwrapper.h" /* socket layer wrapper */ 9 | 10 | #define true 1 11 | #define false 0 12 | 13 | #define PORT 3490 /* Server的端口 */ 14 | #define MAXDATASIZE 100 /*一次可以读的最大字节数 */ 15 | 16 | 17 | int main(int argc, char *argv[]) 18 | { 19 | int sockfd, numbytes; 20 | char buf[MAXDATASIZE]; 21 | struct hostent *he; /* 主机信息 */ 22 | struct sockaddr_in their_addr; /* 对方地址信息 */ 23 | if (argc != 2) 24 | { 25 | fprintf(stderr,"usage: client hostname\n"); 26 | exit(1); 27 | } 28 | 29 | /* get the host info */ 30 | if ((he=Gethostbyname(argv[1])) == NULL) 31 | { 32 | /* 注意:获取DNS信息时,显示出错需要用herror而不是perror */ 33 | /* herror 在新的版本中会出现警告,已经建议不要使用了 */ 34 | perror("gethostbyname"); 35 | exit(1); 36 | } 37 | 38 | if ((sockfd=Socket(PF_INET,SOCK_STREAM,0))==-1) 39 | { 40 | perror("socket"); 41 | exit(1); 42 | } 43 | 44 | their_addr.sin_family = AF_INET; 45 | their_addr.sin_port = Htons(PORT); /* short, NBO */ 46 | their_addr.sin_addr = *((struct in_addr *)he->h_addr_list[0]); 47 | memset(&(their_addr.sin_zero),0, 8); /* 其余部分设成0 */ 48 | 49 | dbtime_startTest ("Connect & Recv"); 50 | 51 | if (Connect(sockfd, (struct sockaddr *)&their_addr, sizeof(struct sockaddr)) == -1) 52 | { 53 | perror("connect"); 54 | exit(1); 55 | } 56 | 57 | if ((numbytes=Recv(sockfd,buf,MAXDATASIZE,0))==-1) 58 | { 59 | perror("recv"); 60 | exit(1); 61 | } 62 | 63 | dbtime_endAndShow (); 64 | 65 | buf[numbytes] = '\0'; 66 | printf("Received: %s",buf); 67 | Close(sockfd); 68 | 69 | dbtime_startTest ("Sleep 5s"); 70 | sleep(5); 71 | dbtime_endAndShow (); 72 | dbtime_finalize (); 73 | return true; 74 | 75 | } 76 | 77 | -------------------------------------------------------------------------------- /lab3/linktable.h: -------------------------------------------------------------------------------- 1 | 2 | /********************************************************************/ 3 | /* Copyright (C) SSE-USTC, 2012-2013 */ 4 | /* */ 5 | /* FILE NAME : linktabe.h */ 6 | /* PRINCIPAL AUTHOR : Mengning */ 7 | /* SUBSYSTEM NAME : LinkTable */ 8 | /* MODULE NAME : LinkTable */ 9 | /* LANGUAGE : C */ 10 | /* TARGET ENVIRONMENT : ANY */ 11 | /* DATE OF FIRST RELEASE : 2012/12/30 */ 12 | /* DESCRIPTION : interface of Link Table */ 13 | /********************************************************************/ 14 | 15 | /* 16 | * Revision log: 17 | * 18 | * Created by Mengning,2012/12/30 19 | * 20 | */ 21 | 22 | #ifndef _LINK_TABLE_H_ 23 | #define _LINK_TABLE_H_ 24 | 25 | #include 26 | 27 | #define SUCCESS 0 28 | #define FAILURE (-1) 29 | 30 | /* 31 | * LinkTable Node Type 32 | */ 33 | typedef struct LinkTableNode 34 | { 35 | struct LinkTableNode * pNext; 36 | }tLinkTableNode; 37 | 38 | /* 39 | * LinkTable Type 40 | */ 41 | typedef struct LinkTable tLinkTable; 42 | 43 | /* 44 | * Create a LinkTable 45 | */ 46 | tLinkTable * CreateLinkTable(); 47 | /* 48 | * Delete a LinkTable 49 | */ 50 | int DeleteLinkTable(tLinkTable *pLinkTable); 51 | /* 52 | * Add a LinkTableNode to LinkTable 53 | */ 54 | int AddLinkTableNode(tLinkTable *pLinkTable,tLinkTableNode * pNode); 55 | /* 56 | * Delete a LinkTableNode from LinkTable 57 | */ 58 | int DelLinkTableNode(tLinkTable *pLinkTable,tLinkTableNode * pNode); 59 | /* 60 | * Search a LinkTableNode from LinkTable 61 | * int Conditon(tLinkTableNode * pNode,void * args); 62 | */ 63 | tLinkTableNode * SearchLinkTableNode(tLinkTable *pLinkTable, int Conditon(tLinkTableNode * pNode, void * args), void * args); 64 | /* 65 | * get LinkTableHead 66 | */ 67 | tLinkTableNode * GetLinkTableHead(tLinkTable *pLinkTable); 68 | /* 69 | * get next LinkTableNode 70 | */ 71 | tLinkTableNode * GetNextLinkTableNode(tLinkTable *pLinkTable,tLinkTableNode * pNode); 72 | 73 | #endif /* _LINK_TABLE_H_ */ 74 | 75 | 76 | -------------------------------------------------------------------------------- /lab2/socket_workspace/socketwrapper.h: -------------------------------------------------------------------------------- 1 | 2 | /********************************************************************/ 3 | /* Copyright (C) SSE-USTC, 2010 */ 4 | /* */ 5 | /* FILE NAME : socketwraper.h */ 6 | /* PRINCIPAL AUTHOR : Mengning */ 7 | /* LANGUAGE : C */ 8 | /* TARGET ENVIRONMENT : ANY */ 9 | /* DATE OF FIRST RELEASE : 2010/10/18 */ 10 | /* DESCRIPTION : the interface to socket layer. */ 11 | /********************************************************************/ 12 | 13 | /* 14 | * Revision log: 15 | * 16 | * Created by Mengning,2010/10/18 17 | * 18 | */ 19 | 20 | #ifndef _SOCKET_WRAPER_H_ 21 | #define _SOCKET_WRAPER_H_ 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include /* gethostbyname */ 31 | 32 | /* Standard Socket Call Mapping Definition */ 33 | #define Socket(x,y,z) socket(x,y,z) 34 | #define Bind(x,y,z) bind(x,y,z) 35 | #define Connect(x,y,z) connect(x,y,z) 36 | #define Listen(x,y) listen(x,y) 37 | #define Read(x,y,z ) read(x,y,z) 38 | #define Accept(x,y,z ) accept(x,y,(socklen_t *)z) 39 | #define Recv(w,x,y,z) recv(w,x,y,z) 40 | #define Recvfrom(a,b,c,d,e,f) recvfrom(a,b,c,d,e,f ) 41 | #define Recvmsg(a,b,c) recvmsg(a,b,c) 42 | #define Write(a,b,c) write(a,b,c) 43 | #define Send(a,b,c,d) send(a,b,c,d) 44 | #define Sendto(a,b,c,d,e,f) sendto(a,b,c,d,e,f) 45 | #define Sendmsg(a,b,c) sendmsg(a,b,c) 46 | #define Close(a) close(a) 47 | 48 | /* byte order trans */ 49 | #define Htons(a) htons(a) 50 | #define Inet_ntoa(a) inet_ntoa(a) 51 | 52 | /* Name */ 53 | #define Gethostbyname(a) gethostbyname(a) 54 | 55 | #endif /* _SOCKET_WRAPER_H_ */ 56 | 57 | 58 | -------------------------------------------------------------------------------- /doc/tcpipinit.md: -------------------------------------------------------------------------------- 1 | # TCP/IP协议栈的初始化 2 | 3 | * TCP/IP协议栈的初始化的函数入口[inet_init](https://github.com/mengning/linux/blob/master/net/ipv4/af_inet.c#L1899) 4 | ``` 5 | static int __init inet_init(void) 6 | { 7 | ... 8 | rc = proto_register(&tcp_prot, 1); 9 | if (rc) 10 | goto out; 11 | 12 | ... 13 | 14 | /* 15 | * Tell SOCKET that we are alive... 16 | */ 17 | 18 | (void)sock_register(&inet_family_ops); 19 | 20 | ... 21 | /* 22 | * Add all the base protocols. 23 | */ 24 | 25 | ... 26 | if (inet_add_protocol(&tcp_protocol, IPPROTO_TCP) < 0) 27 | pr_crit("%s: Cannot add TCP protocol\n", __func__); 28 | ... 29 | /* Register the socket-side information for inet_create. */ 30 | for (r = &inetsw[0]; r < &inetsw[SOCK_MAX]; ++r) 31 | INIT_LIST_HEAD(r); 32 | 33 | for (q = inetsw_array; q < &inetsw_array[INETSW_ARRAY_LEN]; ++q) 34 | inet_register_protosw(q); 35 | 36 | ... 37 | 38 | /* Setup TCP slab cache for open requests. */ 39 | tcp_init(); 40 | 41 | ... 42 | 43 | } 44 | 45 | fs_initcall(inet_init); 46 | ``` 47 | 接下来我们以TCP协议为例来看TCP/IP协议栈的初始化过程。 48 | 49 | ### TCP协议的初始化 50 | * [tcp_prot](https://github.com/mengning/linux/blob/master/net/ipv4/tcp_ipv4.c#L2536) 51 | ``` 52 | struct proto tcp_prot = { 53 | .name = "TCP", 54 | .owner = THIS_MODULE, 55 | .close = tcp_close, 56 | .pre_connect = tcp_v4_pre_connect, 57 | .connect = tcp_v4_connect, 58 | .disconnect = tcp_disconnect, 59 | .accept = inet_csk_accept, 60 | .ioctl = tcp_ioctl, 61 | .init = tcp_v4_init_sock, 62 | .destroy = tcp_v4_destroy_sock, 63 | .shutdown = tcp_shutdown, 64 | .setsockopt = tcp_setsockopt, 65 | .getsockopt = tcp_getsockopt, 66 | .keepalive = tcp_set_keepalive, 67 | .recvmsg = tcp_recvmsg, 68 | .sendmsg = tcp_sendmsg, 69 | .sendpage = tcp_sendpage, 70 | .backlog_rcv = tcp_v4_do_rcv, 71 | .release_cb = tcp_release_cb, 72 | ... 73 | }; 74 | EXPORT_SYMBOL(tcp_prot); 75 | ``` 76 | * [tcp_protocol](https://github.com/mengning/linux/blob/master/net/ipv4/tcp_ipv4.c#L2536) 77 | ``` 78 | /* thinking of making this const? Don't. 79 | * early_demux can change based on sysctl. 80 | */ 81 | static struct net_protocol tcp_protocol = { 82 | .early_demux = tcp_v4_early_demux, 83 | .early_demux_handler = tcp_v4_early_demux, 84 | .handler = tcp_v4_rcv, 85 | .err_handler = tcp_v4_err, 86 | .no_policy = 1, 87 | .netns_ok = 1, 88 | .icmp_strict_tag_validation = 1, 89 | }; 90 | ``` 91 | * [tcp_init](https://github.com/mengning/linux/blob/master/net/ipv4/tcp.c#L3837) 92 | ``` 93 | void __init tcp_init(void) 94 | { 95 | ... 96 | tcp_v4_init(); 97 | tcp_metrics_init(); 98 | BUG_ON(tcp_register_congestion_control(&tcp_reno) != 0); 99 | tcp_tasklet_init(); 100 | } 101 | ``` 102 | -------------------------------------------------------------------------------- /doc/systemcall.md: -------------------------------------------------------------------------------- 1 | # 系统调用相关代码分析 2 | 3 | ## 系统调用的初始化 4 | 5 | * x86-32位系统:start_kernel --> trap_init --> idt_setup_traps --> 0x80--entry_INT80_32,在5.0内核int0x80对应的中断服务例程是entry_INT80_32,而不是原来的名称system_call了。 6 | * x86-64位系统:start_kernel --> trap_init --> cpu_init --> syscall_init 7 | * [64位的系统调用中断向量与服务例程绑定](https://github.com/torvalds/linux/blob/c3bfc5dd73c6f519ff0636d4e709515f06edef78/arch/x86/kernel/cpu/common.c#L1670) 8 | ``` 9 | void syscall_init(void) 10 | { 11 | wrmsr(MSR_STAR, 0, (__USER32_CS << 16) | __KERNEL_CS); 12 | wrmsrl(MSR_LSTAR, (unsigned long)entry_SYSCALL_64); 13 | ... 14 | ``` 15 | ## 系统调用的正常执行 16 | 17 | 用户态程序发起系统调用,对于x86-64位程序应该是直接跳到entry_SYSCALL_64 18 | * [32位的系统调用服务程序](https://github.com/mengning/linux/blob/master/arch/x86/entry/entry_32.S#L989) 19 | ``` 20 | ENTRY(entry_INT80_32) 21 | ASM_CLAC 22 | pushl %eax /* pt_regs->orig_ax */ 23 | 24 | SAVE_ALL pt_regs_ax=$-ENOSYS switch_stacks=1 /* save rest */ 25 | 26 | /* 27 | * User mode is traced as though IRQs are on, and the interrupt gate 28 | * turned them off. 29 | */ 30 | TRACE_IRQS_OFF 31 | 32 | movl %esp, %eax 33 | call do_int80_syscall_32 34 | ... 35 | ``` 36 | * [do_int80_syscall_32](https://github.com/torvalds/linux/blob/ab851d49f6bfc781edd8bd44c72ec1e49211670b/arch/x86/entry/common.c#L345) 37 | ``` 38 | static __always_inline void do_syscall_32_irqs_on(struct pt_regs *regs) 39 | { 40 | ... 41 | #ifdef CONFIG_IA32_EMULATION 42 | regs->ax = ia32_sys_call_table[nr](regs); 43 | #else 44 | /* 45 | * It's possible that a 32-bit syscall implementation 46 | * takes a 64-bit parameter but nonetheless assumes that 47 | * the high bits are zero. Make sure we zero-extend all 48 | * of the args. 49 | */ 50 | regs->ax = ia32_sys_call_table[nr]( 51 | (unsigned int)regs->bx, (unsigned int)regs->cx, 52 | (unsigned int)regs->dx, (unsigned int)regs->si, 53 | (unsigned int)regs->di, (unsigned int)regs->bp); 54 | #endif /* CONFIG_IA32_EMULATION */ 55 | } 56 | 57 | syscall_return_slowpath(regs); 58 | } 59 | 60 | /* Handles int $0x80 */ 61 | __visible void do_int80_syscall_32(struct pt_regs *regs) 62 | { 63 | enter_from_user_mode(); 64 | local_irq_enable(); 65 | do_syscall_32_irqs_on(regs); 66 | } 67 | ``` 68 | * [64位的系统调用服务例程](https://github.com/torvalds/linux/blob/ab851d49f6bfc781edd8bd44c72ec1e49211670b/arch/x86/entry/entry_64.S#L175) 69 | ``` 70 | SYM_CODE_START(entry_SYSCALL_64) 71 | ... 72 | /* IRQs are off. */ 73 | movq %rax, %rdi 74 | movq %rsp, %rsi 75 | call do_syscall_64 /* returns with IRQs disabled */ 76 | ``` 77 | * [do_syscall_64](https://github.com/torvalds/linux/blob/ab851d49f6bfc781edd8bd44c72ec1e49211670b/arch/x86/entry/common.c#L282) 78 | ``` 79 | #ifdef CONFIG_X86_64 80 | __visible void do_syscall_64(unsigned long nr, struct pt_regs *regs) 81 | { 82 | ... 83 | if (likely(nr < NR_syscalls)) { 84 | nr = array_index_nospec(nr, NR_syscalls); 85 | regs->ax = sys_call_table[nr](regs); 86 | ... 87 | } 88 | #endif 89 | ``` 90 | ## 系统调用表的初始化 91 | 92 | ia32_sys_call_table 和 sys_call_table 数组都是由如下目录下的代码初始化的。 93 | https://github.com/mengning/linux/tree/master/arch/x86/entry/syscalls 94 | -------------------------------------------------------------------------------- /lab2/socket_workspace/dbtime.c: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2012, by the NoSQL project team. 2 | // Hacked by Baojian Hua. 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "dbtime.h" 9 | 10 | #define NANO (1000000000) 11 | #define MS (1000000) 12 | 13 | char *dbtime_filename = 0; 14 | static FILE *dbtime_outfile = 0; 15 | 16 | static char *defaultName = "dbtime.time"; 17 | 18 | // Initialize along the way to catch bugs. 19 | static int flag = 0;; 20 | static struct timespec start = {0, 0L}; 21 | static struct timespec end = {0, 0L}; 22 | 23 | // Linux kernel-style. 24 | void 25 | dbtime_start () 26 | { 27 | if (flag){ 28 | fprintf (stderr, "error\n"); 29 | exit (0); 30 | } 31 | // outfile has not been set, then 32 | // set a default. 33 | if (0==dbtime_filename){ 34 | dbtime_filename = defaultName; 35 | dbtime_outfile = stdout;//fopen (defaultName, "w+"); 36 | } 37 | else if (!dbtime_outfile){ 38 | char a[100]; 39 | 40 | strcpy (a, dbtime_filename); 41 | strcat (a, ".time"); 42 | dbtime_outfile = fopen (a, "w+"); 43 | if (!dbtime_outfile){ 44 | fprintf (stderr, "error open file\n"); 45 | exit (1); 46 | } 47 | } 48 | flag = 1; 49 | clock_gettime (CLOCK_PROCESS_CPUTIME_ID, &start); 50 | return; 51 | } 52 | 53 | void 54 | dbtime_startTest (const char *testname) 55 | { 56 | if (flag){ 57 | printf ("error\n"); 58 | exit (0); 59 | } 60 | // outfile has not been set, then 61 | // set a default. 62 | if (0==dbtime_filename){ 63 | dbtime_filename = defaultName; 64 | dbtime_outfile = fopen (defaultName, "w+"); 65 | } 66 | else if (!dbtime_outfile){ 67 | char a[100]; 68 | 69 | strcpy (a, dbtime_filename); 70 | strcat (a, ".time"); 71 | dbtime_outfile = fopen (a, "w+"); 72 | if (!dbtime_outfile){ 73 | fprintf (stderr, "error open file\n"); 74 | exit (1); 75 | } 76 | } 77 | flag = 1; 78 | fprintf (dbtime_outfile, "\n%s:\n", testname); 79 | clock_gettime (CLOCK_PROCESS_CPUTIME_ID, &start); 80 | return; 81 | } 82 | 83 | void 84 | dbtime_end () 85 | { 86 | if (!flag){ 87 | fprintf (stderr, "error\n"); 88 | exit (0); 89 | } 90 | flag = 0; 91 | clock_gettime (CLOCK_PROCESS_CPUTIME_ID, &end); 92 | return; 93 | } 94 | 95 | void 96 | dbtime_endAndShow () 97 | { 98 | if (!flag){ 99 | fprintf (stderr, "error\n"); 100 | exit (0); 101 | } 102 | flag = 0; 103 | clock_gettime (CLOCK_PROCESS_CPUTIME_ID, &end); 104 | dbtime_show (); 105 | return; 106 | } 107 | 108 | void 109 | dbtime_show () 110 | { 111 | struct timespec ltime; 112 | double us, ms, s; 113 | 114 | if (flag){ 115 | fprintf (stderr, "error\n"); 116 | exit (0); 117 | } 118 | if (end.tv_nsec <= start.tv_nsec){ 119 | ltime.tv_nsec = NANO + end.tv_nsec - start.tv_nsec; 120 | ltime.tv_sec = end.tv_sec - start.tv_sec - 1; 121 | } 122 | else { 123 | ltime.tv_nsec = end.tv_nsec - start.tv_nsec; 124 | ltime.tv_sec = end.tv_sec - start.tv_sec; 125 | } 126 | // pretty-printing it 127 | us = ltime.tv_nsec * 1.0 / 1000; 128 | ms = ltime.tv_nsec * 1.0 / 1000000; 129 | s = ltime.tv_nsec * 1.0 / NANO; 130 | 131 | // to shut up the compiler 132 | us = us; 133 | ms = ms; 134 | 135 | // For the purpose of time bookkeeping, I modify this 136 | // to use a much cleaner representation. 137 | /* 138 | fprintf (dbtime_outfile 139 | , "%ds + (%luns, %lfus, %lfms, %lfs)\n" 140 | , (int)ltime.tv_sec 141 | , ltime.tv_nsec 142 | , us 143 | , ms 144 | , s); 145 | */ 146 | fprintf (dbtime_outfile 147 | , "%lf\n" 148 | , (int)ltime.tv_sec + s); 149 | return; 150 | } 151 | 152 | void 153 | dbtime_finalize () 154 | { 155 | if (dbtime_outfile) 156 | fclose (dbtime_outfile); 157 | return; 158 | } 159 | -------------------------------------------------------------------------------- /lab3/menu.c: -------------------------------------------------------------------------------- 1 | 2 | /**************************************************************************************************/ 3 | /* Copyright (C) mc2lab.com, SSE@USTC, 2014-2015 */ 4 | /* */ 5 | /* FILE NAME : menu.c */ 6 | /* PRINCIPAL AUTHOR : Mengning */ 7 | /* SUBSYSTEM NAME : menu */ 8 | /* MODULE NAME : menu */ 9 | /* LANGUAGE : C */ 10 | /* TARGET ENVIRONMENT : ANY */ 11 | /* DATE OF FIRST RELEASE : 2014/08/31 */ 12 | /* DESCRIPTION : This is a menu program */ 13 | /**************************************************************************************************/ 14 | 15 | /* 16 | * Revision log: 17 | * 18 | * Created by Mengning, 2014/08/31 19 | * 20 | */ 21 | 22 | 23 | #include 24 | #include 25 | #include 26 | #include "linktable.h" 27 | #include "menu.h" 28 | 29 | tLinkTable * head = NULL; 30 | int Help(); 31 | int Quit(); 32 | 33 | #define CMD_MAX_LEN 1024 34 | #define CMD_MAX_ARGV_NUM 32 35 | #define DESC_LEN 1024 36 | #define CMD_NUM 10 37 | 38 | char prompt[CMD_MAX_LEN] = "Input Cmd >"; 39 | 40 | /* data struct and its operations */ 41 | 42 | typedef struct DataNode 43 | { 44 | tLinkTableNode * pNext; 45 | char* cmd; 46 | char* desc; 47 | int (*handler)(int argc, char *argv[]); 48 | } tDataNode; 49 | 50 | int SearchConditon(tLinkTableNode * pLinkTableNode,void * arg) 51 | { 52 | char * cmd = (char*)arg; 53 | tDataNode * pNode = (tDataNode *)pLinkTableNode; 54 | if(strcmp(pNode->cmd, cmd) == 0) 55 | { 56 | return SUCCESS; 57 | } 58 | return FAILURE; 59 | } 60 | /* find a cmd in the linklist and return the datanode pointer */ 61 | tDataNode* FindCmd(tLinkTable * head, char * cmd) 62 | { 63 | tDataNode * pNode = (tDataNode*)GetLinkTableHead(head); 64 | while(pNode != NULL) 65 | { 66 | if(!strcmp(pNode->cmd, cmd)) 67 | { 68 | return pNode; 69 | } 70 | pNode = (tDataNode*)GetNextLinkTableNode(head,(tLinkTableNode *)pNode); 71 | } 72 | return NULL; 73 | } 74 | 75 | /* show all cmd in listlist */ 76 | int ShowAllCmd(tLinkTable * head) 77 | { 78 | tDataNode * pNode = (tDataNode*)GetLinkTableHead(head); 79 | while(pNode != NULL) 80 | { 81 | printf("%s - %s\n", pNode->cmd, pNode->desc); 82 | pNode = (tDataNode*)GetNextLinkTableNode(head,(tLinkTableNode *)pNode); 83 | } 84 | return 0; 85 | } 86 | 87 | int Help(int argc, char *argv[]) 88 | { 89 | ShowAllCmd(head); 90 | return 0; 91 | } 92 | 93 | int SetPrompt(char * p) 94 | { 95 | if (p == NULL) 96 | { 97 | return 0; 98 | } 99 | strcpy(prompt,p); 100 | return 0; 101 | } 102 | /* add cmd to menu */ 103 | int MenuConfig(char * cmd, char * desc, int (*handler)()) 104 | { 105 | tDataNode* pNode = NULL; 106 | if ( head == NULL) 107 | { 108 | head = CreateLinkTable(); 109 | pNode = (tDataNode*)malloc(sizeof(tDataNode)); 110 | pNode->cmd = "help"; 111 | pNode->desc = "Menu List:"; 112 | pNode->handler = Help; 113 | AddLinkTableNode(head,(tLinkTableNode *)pNode); 114 | } 115 | pNode = (tDataNode*)malloc(sizeof(tDataNode)); 116 | pNode->cmd = cmd; 117 | pNode->desc = desc; 118 | pNode->handler = handler; 119 | AddLinkTableNode(head,(tLinkTableNode *)pNode); 120 | return 0; 121 | } 122 | 123 | 124 | /* Menu Engine Execute */ 125 | int ExecuteMenu() 126 | { 127 | /* cmd line begins */ 128 | while(1) 129 | { 130 | int argc = 0; 131 | char *argv[CMD_MAX_ARGV_NUM]; 132 | char cmd[CMD_MAX_LEN]; 133 | char *pcmd = NULL; 134 | printf("%s",prompt); 135 | /* scanf("%s", cmd); */ 136 | pcmd = fgets(cmd, CMD_MAX_LEN, stdin); 137 | if(pcmd == NULL) 138 | { 139 | continue; 140 | } 141 | /* convert cmd to argc/argv */ 142 | pcmd = strtok(pcmd," "); 143 | while(pcmd != NULL && argc < CMD_MAX_ARGV_NUM) 144 | { 145 | argv[argc] = pcmd; 146 | argc++; 147 | pcmd = strtok(NULL," "); 148 | } 149 | if(argc == 1) 150 | { 151 | int len = strlen(argv[0]); 152 | *(argv[0] + len - 1) = '\0'; 153 | } 154 | tDataNode *p = (tDataNode*)SearchLinkTableNode(head,SearchConditon,(void*)argv[0]); 155 | if( p == NULL) 156 | { 157 | printf("This is a wrong cmd!\n "); 158 | continue; 159 | } 160 | 161 | if(p->handler != NULL) 162 | { 163 | p->handler(argc, argv); 164 | } 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /lab3/syswrapper.h: -------------------------------------------------------------------------------- 1 | 2 | /********************************************************************/ 3 | /* Copyright (C) SSE-USTC, 2012 */ 4 | /* */ 5 | /* FILE NAME : syswraper.h */ 6 | /* PRINCIPAL AUTHOR : Mengning */ 7 | /* SUBSYSTEM NAME : system */ 8 | /* MODULE NAME : syswraper */ 9 | /* LANGUAGE : C */ 10 | /* TARGET ENVIRONMENT : Linux */ 11 | /* DATE OF FIRST RELEASE : 2012/11/22 */ 12 | /* DESCRIPTION : the interface to Linux system(socket) */ 13 | /********************************************************************/ 14 | 15 | /* 16 | * Revision log: 17 | * 18 | * Created by Mengning,2012/11/22 19 | * 20 | */ 21 | 22 | #ifndef _SYS_WRAPER_H_ 23 | #define _SYS_WRAPER_H_ 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | //#define NDEBUG 33 | #include 34 | 35 | #define PORT 5001 36 | #define IP_ADDR "127.0.0.1" 37 | #define MAX_BUF_LEN 1024 38 | 39 | /* private macro */ 40 | #define PrepareSocket(addr,port) \ 41 | int sockfd = -1; \ 42 | struct sockaddr_in serveraddr; \ 43 | struct sockaddr_in clientaddr; \ 44 | socklen_t addr_len = sizeof(struct sockaddr); \ 45 | serveraddr.sin_family = AF_INET; \ 46 | serveraddr.sin_port = htons(port); \ 47 | serveraddr.sin_addr.s_addr = inet_addr(addr); \ 48 | memset(&serveraddr.sin_zero, 0, 8); \ 49 | sockfd = socket(PF_INET,SOCK_STREAM,0); 50 | 51 | #define InitServer() \ 52 | int ret = bind( sockfd, \ 53 | (struct sockaddr *)&serveraddr, \ 54 | sizeof(struct sockaddr)); \ 55 | if(ret == -1) \ 56 | { \ 57 | fprintf(stderr,"Bind Error,%s:%d\n", \ 58 | __FILE__,__LINE__); \ 59 | close(sockfd); \ 60 | return -1; \ 61 | } \ 62 | listen(sockfd,MAX_CONNECT_QUEUE); 63 | 64 | #define InitClient() \ 65 | int ret = connect(sockfd, \ 66 | (struct sockaddr *)&serveraddr, \ 67 | sizeof(struct sockaddr)); \ 68 | if(ret == -1) \ 69 | { \ 70 | fprintf(stderr,"Connect Error,%s:%d\n", \ 71 | __FILE__,__LINE__); \ 72 | return -1; \ 73 | } 74 | /* public macro */ 75 | #define InitializeService() \ 76 | PrepareSocket(IP_ADDR,PORT); \ 77 | InitServer(); 78 | 79 | #define ShutdownService() \ 80 | close(sockfd); 81 | 82 | #define OpenRemoteService() \ 83 | PrepareSocket(IP_ADDR,PORT); \ 84 | InitClient(); \ 85 | int newfd = sockfd; 86 | 87 | #define CloseRemoteService() \ 88 | close(sockfd); 89 | 90 | #define ServiceStart() \ 91 | int newfd = accept( sockfd, \ 92 | (struct sockaddr *)&clientaddr, \ 93 | &addr_len); \ 94 | if(newfd == -1) \ 95 | { \ 96 | fprintf(stderr,"Accept Error,%s:%d\n", \ 97 | __FILE__,__LINE__); \ 98 | } 99 | #define ServiceStop() \ 100 | close(newfd); 101 | 102 | #define RecvMsg(buf) \ 103 | ret = recv(newfd,buf,MAX_BUF_LEN,0); \ 104 | if(ret > 0) \ 105 | { \ 106 | printf("recv \"%s\" from %s:%d\n", \ 107 | buf, \ 108 | (char*)inet_ntoa(clientaddr.sin_addr), \ 109 | ntohs(clientaddr.sin_port)); \ 110 | } 111 | 112 | #define SendMsg(buf) \ 113 | ret = send(newfd,buf,strlen(buf),0); \ 114 | if(ret > 0) \ 115 | { \ 116 | printf("send \"hi\" to %s:%d\n", \ 117 | (char*)inet_ntoa(clientaddr.sin_addr), \ 118 | ntohs(clientaddr.sin_port)); \ 119 | } 120 | 121 | #endif /* _SYS_WRAPER_H_ */ 122 | 123 | 124 | -------------------------------------------------------------------------------- /np2019.md: -------------------------------------------------------------------------------- 1 | # 2019年秋网络程序设计 2 | 3 | 本课程从实践入手循序渐进,以Linux系统环境和Linux内核源代码为例,将Linux网络相关命令用法、Socket网络编程、TCP协议、IP协议及路由表、ARP协议及ARP缓存、二层交换网络的学习转发和过滤数据库等互联网架构的关键环节一一解析,并通过MenuOS实验系统进行代码跟踪分析。最终理解分析打开一个网页背后互联网的工作过程,其中重点分为三个抽象层次:一是便于人类理解的记忆的编址方式DNS Naming;二是便于全球定位编址和路由的IP Networking;三是便于局域网中实际完成数据交换传输的Layer 2 Switching;同时在理解互联网体系结构的基础上探寻它的历史演化渊源,乃至发现它背后的设计哲学,解读未来网络的演进方向。 4 | 5 | * [请加入班级博客————实验作业提交地址](http://edu.cnblogs.com/campus/ustc/np2019/join?id=CfDJ8DeHXSeUWr9KtnvAGu7_dX9TjzrnS5kltcFAcolgGsH1Ml6mPMIu6q9UAMMfwfHqJR0gIzf7C_jRP07BFPZdangSzlwCLd1km652ExcGpvRx83yBkPDMWyv4Nbu-sxSLTjqAfZyzS8zoQRBKsdT2f2o) 6 | * 实验答疑14-18周周四晚上7-9点思贤楼301和303实验室 7 | 8 | ## 互联网概述 9 | 10 | * [互联网概述.pptx](https://github.com/mengning/net/raw/master/lab1/%E4%BA%92%E8%81%94%E7%BD%91%E6%A6%82%E8%BF%B0.pptx) 11 | * [网络相关命令参考](https://man.linuxde.net/par/5) 12 | 13 | ### 实验作业一:网络相关的命令工具研究报告 14 | 15 | * 参加https://edu.cnblogs.com/campus/ustc/np2019/homework/10054 16 | 17 | ## 网络编程 18 | 19 | * [TCP范例代码](https://github.com/mengning/net/tree/master/lab2/socket_workspace) 20 | * BSD Socket/Linux Socket API 21 | * [SocketProgramming.pptx](https://github.com/mengning/net/raw/master/lab2/SocketProgramming.pptx) 22 | * [并发编程ConcurrentServer.pptx](https://github.com/mengning/net/raw/master/lab2/ConcurrentServer.pptx) 23 | 24 | * winsock/Linux Socket API 25 | * Java/Linux Socket API 26 | * Javascript/Nodejs/Linux Socket API 27 | * Python/Linux Socket API 28 | * go/Linux Socket API 29 | * ... 30 | 31 | ### 实验作业二 32 | 33 | * 以您熟悉的编程语言为例完成[一个hello/hi的简单的网络聊天程序](https://github.com/mengning/net/tree/master/lab2),并写一篇博客对比分析该编程语言提供的网络接口API与Linux Socket API之间的关系。 34 | * 参见https://edu.cnblogs.com/campus/ustc/np2019/homework/10012 35 | * 参考作业范例https://blog.csdn.net/vipshop_fin_dev/article/details/102966081 36 | 37 | ## 构建调试Linux内核网络代码的环境MenuOS系统 38 | ### 实验作业三 39 | * [自行搭建调试Linux内核的环境Build a Linux system](https://github.com/mengning/net/raw/master/lab3/BuildLinuxSystem.pptx) 40 | * [构建调试Linux内核网络代码的环境MenuOS](https://www.shiyanlou.com/courses/1198) 41 | * 将C/S方式的网络通信程序的服务端程序集成到MenuOS系统中,成为MenuOS系统的一个命令,跟踪分析MenuOS在执行这这个命令的过程中对Linux内核发出的系统调用请求,以及与socket接口函数的关系。 42 | * [初始化MenuOS系统的网络功能](https://www.shiyanlou.com/courses/1198) 43 | * 搞清楚如何激活Linux网络设备,并将MenuOS系统的网络设备用简便的方式配置好,使我们将TCP客户端也集成进去后可以完整的运行TCP网络程序的服务端和客户 44 | * [Linux内核5.0 source code](https://github.com/mengning/linux/tree/v5.0) 45 | * 参考[MenuOS](https://www.shiyanlou.com/courses/195) 46 | * [Linux内核的启动过程](https://github.com/mengning/linux/blob/v5.0/init/main.c#L537), [跟踪分析Linux内核的启动过程](https://www.shiyanlou.com/courses/195/labs/725/document) 47 | 48 | ### [Socket及系统调用](https://github.com/mengning/linuxkernel/raw/master/SystemCall.pdf) 49 | 50 | * glibc提供的系统调用函数API 51 | * int 0x80、系统调用号及参数传递过程 52 | * 保护现场与恢复现场 53 | * 系统调用内核处理函数 54 | * 使用库函数API和C代码中嵌入汇编代码两种方式使用同一个系统调用 55 | * [系统调用列表](https://github.com/mengning/linux/blob/master/arch/x86/entry/syscalls/syscall_32.tbl) 56 | * 分析system_call中断处理过程 57 | * start_kernel --> trap_init --> idt_setup_traps --> [0x80--entry_INT80_32](https://github.com/mengning/linux/blob/master/arch/x86/kernel/idt.c#L105) 58 | * 在5.0内核int0x80对应的中断服务例程是[entry_INT80_32](https://github.com/mengning/linux/blob/master/arch/x86/entry/entry_32.S#L989),而不是原来的名称system_call了。 59 | * [系统调用相关代码分析](https://github.com/mengning/net/blob/master/doc/systemcall.md) 60 | * [Socket接口对应的Linux内核系统调用处理代码](https://github.com/mengning/net/blob/master/doc/socketSourceCode.md) 61 | * [Linux内核初始化过程中加载TCP/IP协议栈](https://github.com/mengning/net/blob/master/doc/tcpip.md) 62 | * [TCP/IP协议栈的初始化](https://github.com/mengning/net/blob/master/doc/tcpipinit.md) 63 | ### 实验作业四 64 | Socket与系统调用深度分析http://edu.cnblogs.com/campus/ustc/np2019/homework/10175 65 | * Socket API编程接口之上可以编写基于不同网络协议的应用程序; 66 | * Socket接口在用户态通过系统调用机制进入内核; 67 | * 内核中将系统调用作为一个特殊的中断来处理,以socket相关系统调用为例进行分析; 68 | * socket相关系统调用的内核处理函数内部通过“多态机制”对不同的网络协议进行的封装方法; 69 | 请将Socket API编程接口、系统调用机制及内核中系统调用相关源代码、 socket相关系统调用的内核处理函数结合起来分析,并在X86 64环境下Linux5.0以上的内核中进一步跟踪验证。 70 | 完成一篇图文并茂、逻辑严谨、代码详实的实验报告。 71 | 72 | ### TCP 73 | 74 | * [TCP基本原理.ppt](https://github.com/mengning/net/raw/master/lab3/TCP.pptx) 75 | * [TCP/IP协议栈的初始化](doc/tcpipinit.md) 76 | * [TCP协议栈源代码分析.ppt](https://github.com/mengning/net/raw/master/lab3/TCP%E5%8D%8F%E8%AE%AE%E6%A0%88%E6%BA%90%E4%BB%A3%E7%A0%81%E5%88%86%E6%9E%90.pptx) - [TCP源代码](doc/tcp.md) 77 | 78 | ### 实验作业五:深入理解TCP协议及其源代码 79 | 选择如下任一个问题,通过理论分析、源代码阅读和运行跟踪深入理解TCP协议完成一篇实验报告博客 80 | * TCP协议的初始化及socket创建TCP套接字描述符; 81 | * connect及bind、listen、accept背后的三次握手 82 | * send和recv背后数据的首发过程 83 | * close背后的连接终止过程 84 | 另外您也可以任选一个您感兴趣的角度比如封包构造和解析、拥塞控制、执行视图等来深入理解TCP协议 85 | 86 | ### IP & ARP 87 | 88 | * [HowIPNetworking.pptx](https://github.com/mengning/net/raw/master/lab4/How%20IP%20Networking.pptx) 89 | * [ARP.pptx](https://github.com/mengning/net/raw/master/lab5/ARP.pptx) 90 | * [IP协议](doc/ip.md) 91 | * [ARP协议](doc/arp.md) 92 | 93 | ### L2 Switching 94 | 95 | * [L2 Switching](https://github.com/mengning/net/raw/master/lab6/L2Switching.pptx) 96 | 97 | ### [收发数据流的处理过程](doc/dataflow.md) 98 | ### DNS 99 | 100 | * [DNS.pptx](https://github.com/mengning/net/blob/master/lab7/DNS.pptx) 101 | 102 | ### 网络安全等级保护 103 | * [网络安全等级保护](https://github.com/mengning/net/raw/master/lab8/%E7%BD%91%E7%BB%9C%E5%AE%89%E5%85%A8%E7%AD%89%E7%BA%A7%E4%BF%9D%E6%8A%A4.pptx) 104 | ### 互联网架构设计背后的渊源 105 | 106 | * [互联网架构设计背后的渊源.pptx](https://github.com/mengning/net/blob/master/lab8/%E4%BA%92%E8%81%94%E7%BD%91%E6%9E%B6%E6%9E%84%E8%AE%BE%E8%AE%A1%E8%83%8C%E5%90%8E%E7%9A%84%E6%B8%8A%E6%BA%90.pptx) 107 | 108 | ## 参考资料 109 | 110 | * UNIX网络编程卷1:套接字联网API 111 | * 庖丁解牛Linux内核分析 112 | * POSIX: An Overview https://linuxhint.com/posix-standard/ 113 | * POSIX Full Document https://pubs.opengroup.org/onlinepubs/9699919799/ 114 | * POSIX System Interfaces https://pubs.opengroup.org/onlinepubs/9699919799/idx/functions.html 115 | * POSIX Utilities https://pubs.opengroup.org/onlinepubs/9699919799/idx/utilities.html 116 | * POSIX Networking Services https://pubs.opengroup.org/onlinepubs/9699919799/idx/networking.html 117 | 118 | -------------------------------------------------------------------------------- /lab3/linktable.c: -------------------------------------------------------------------------------- 1 | 2 | /********************************************************************/ 3 | /* Copyright (C) SSE-USTC, 2012-2013 */ 4 | /* */ 5 | /* FILE NAME : linktabe.c */ 6 | /* PRINCIPAL AUTHOR : Mengning */ 7 | /* SUBSYSTEM NAME : LinkTable */ 8 | /* MODULE NAME : LinkTable */ 9 | /* LANGUAGE : C */ 10 | /* TARGET ENVIRONMENT : ANY */ 11 | /* DATE OF FIRST RELEASE : 2012/12/30 */ 12 | /* DESCRIPTION : interface of Link Table */ 13 | /********************************************************************/ 14 | 15 | /* 16 | * Revision log: 17 | * 18 | * Created by Mengning,2012/12/30 19 | * Provide right Callback interface by Mengning,2012/09/17 20 | * 21 | */ 22 | 23 | #include 24 | #include 25 | #include 26 | #include"linktable.h" 27 | 28 | /* 29 | * LinkTable Type 30 | */ 31 | struct LinkTable 32 | { 33 | tLinkTableNode *pHead; 34 | tLinkTableNode *pTail; 35 | int SumOfNode; 36 | pthread_mutex_t mutex; 37 | 38 | }; 39 | 40 | 41 | /* 42 | * Create a LinkTable 43 | */ 44 | tLinkTable * CreateLinkTable() 45 | { 46 | tLinkTable * pLinkTable = (tLinkTable *)malloc(sizeof(tLinkTable)); 47 | if(pLinkTable == NULL) 48 | { 49 | return NULL; 50 | } 51 | pLinkTable->pHead = NULL; 52 | pLinkTable->pTail = NULL; 53 | pLinkTable->SumOfNode = 0; 54 | pthread_mutex_init(&(pLinkTable->mutex), NULL); 55 | return pLinkTable; 56 | } 57 | 58 | /* 59 | * Delete a LinkTable 60 | */ 61 | int DeleteLinkTable(tLinkTable *pLinkTable) 62 | { 63 | if(pLinkTable == NULL) 64 | { 65 | return FAILURE; 66 | } 67 | while(pLinkTable->pHead != NULL) 68 | { 69 | tLinkTableNode * p = pLinkTable->pHead; 70 | pthread_mutex_lock(&(pLinkTable->mutex)); 71 | pLinkTable->pHead = pLinkTable->pHead->pNext; 72 | pLinkTable->SumOfNode -= 1 ; 73 | pthread_mutex_unlock(&(pLinkTable->mutex)); 74 | free(p); 75 | } 76 | pLinkTable->pHead = NULL; 77 | pLinkTable->pTail = NULL; 78 | pLinkTable->SumOfNode = 0; 79 | pthread_mutex_destroy(&(pLinkTable->mutex)); 80 | free(pLinkTable); 81 | return SUCCESS; 82 | } 83 | 84 | /* 85 | * Add a LinkTableNode to LinkTable 86 | */ 87 | int AddLinkTableNode(tLinkTable *pLinkTable,tLinkTableNode * pNode) 88 | { 89 | if(pLinkTable == NULL || pNode == NULL) 90 | { 91 | return FAILURE; 92 | } 93 | pNode->pNext = NULL; 94 | pthread_mutex_lock(&(pLinkTable->mutex)); 95 | if(pLinkTable->pHead == NULL) 96 | { 97 | pLinkTable->pHead = pNode; 98 | } 99 | if(pLinkTable->pTail == NULL) 100 | { 101 | pLinkTable->pTail = pNode; 102 | } 103 | else 104 | { 105 | pLinkTable->pTail->pNext = pNode; 106 | pLinkTable->pTail = pNode; 107 | } 108 | pLinkTable->SumOfNode += 1 ; 109 | pthread_mutex_unlock(&(pLinkTable->mutex)); 110 | return SUCCESS; 111 | } 112 | 113 | /* 114 | * Delete a LinkTableNode from LinkTable 115 | */ 116 | int DelLinkTableNode(tLinkTable *pLinkTable,tLinkTableNode * pNode) 117 | { 118 | if(pLinkTable == NULL || pNode == NULL) 119 | { 120 | return FAILURE; 121 | } 122 | pthread_mutex_lock(&(pLinkTable->mutex)); 123 | if(pLinkTable->pHead == pNode) 124 | { 125 | pLinkTable->pHead = pLinkTable->pHead->pNext; 126 | pLinkTable->SumOfNode -= 1 ; 127 | if(pLinkTable->SumOfNode == 0) 128 | { 129 | pLinkTable->pTail = NULL; 130 | } 131 | pthread_mutex_unlock(&(pLinkTable->mutex)); 132 | return SUCCESS; 133 | } 134 | tLinkTableNode * pTempNode = pLinkTable->pHead; 135 | while(pTempNode != NULL) 136 | { 137 | if(pTempNode->pNext == pNode) 138 | { 139 | pTempNode->pNext = pTempNode->pNext->pNext; 140 | pLinkTable->SumOfNode -= 1 ; 141 | if(pLinkTable->SumOfNode == 0) 142 | { 143 | pLinkTable->pTail = NULL; 144 | } 145 | pthread_mutex_unlock(&(pLinkTable->mutex)); 146 | return SUCCESS; 147 | } 148 | pTempNode = pTempNode->pNext; 149 | } 150 | pthread_mutex_unlock(&(pLinkTable->mutex)); 151 | return FAILURE; 152 | } 153 | 154 | /* 155 | * Search a LinkTableNode from LinkTable 156 | * int Conditon(tLinkTableNode * pNode); 157 | */ 158 | tLinkTableNode * SearchLinkTableNode(tLinkTable *pLinkTable, int Conditon(tLinkTableNode * pNode, void * args), void * args) 159 | { 160 | if(pLinkTable == NULL || Conditon == NULL) 161 | { 162 | return NULL; 163 | } 164 | tLinkTableNode * pNode = pLinkTable->pHead; 165 | while(pNode != NULL) 166 | { 167 | if(Conditon(pNode,args) == SUCCESS) 168 | { 169 | return pNode; 170 | } 171 | pNode = pNode->pNext; 172 | } 173 | return NULL; 174 | } 175 | 176 | /* 177 | * get LinkTableHead 178 | */ 179 | tLinkTableNode * GetLinkTableHead(tLinkTable *pLinkTable) 180 | { 181 | if(pLinkTable == NULL) 182 | { 183 | return NULL; 184 | } 185 | return pLinkTable->pHead; 186 | } 187 | 188 | /* 189 | * get next LinkTableNode 190 | */ 191 | tLinkTableNode * GetNextLinkTableNode(tLinkTable *pLinkTable,tLinkTableNode * pNode) 192 | { 193 | if(pLinkTable == NULL || pNode == NULL) 194 | { 195 | return NULL; 196 | } 197 | tLinkTableNode * pTempNode = pLinkTable->pHead; 198 | while(pTempNode != NULL) 199 | { 200 | if(pTempNode == pNode) 201 | { 202 | return pTempNode->pNext; 203 | } 204 | pTempNode = pTempNode->pNext; 205 | } 206 | return NULL; 207 | } 208 | 209 | -------------------------------------------------------------------------------- /doc/arp.md: -------------------------------------------------------------------------------- 1 | ## 网络层与链路层的中间人——ARP协议及ARP缓存 2 | 3 | 路由选择得到输出结果是下一跳Next-Hop的IP地址和网络接口号,但是在发送IP数据包之前还需要得到目的MAC地址,这就需要用到ARP(Address Resolution Protocol)地址解析协议了。ARP用于将计算机的网络地址(IP地址32位)转化为物理地址(MAC地址48位),也就是将路由选择得到输出结果下一跳Next-Hop的IP地址通过查询ARP缓存得到对应的目的MAC地址。 4 | 5 | ![](https://s1.51cto.com/images/blog/201901/08/c76037e0cb827a22299460b85f408db5.png) 6 | 7 | 如上图,常见的MAC帧有三个类型:IP数据包、ARP和RARP。ARP请求/应答数据和IP数据包一样是由MAC帧承载的。 8 | 9 | 网络层的IP数据包(含有目的IP地址)需要封装成MAC帧(含有目的MAC地址)才能发送出去。如何得到目的MAC地址,从而将IP数据包发送到下一个中转站或最终目的地(下一跳Next-Hop)是TCP/IP网络得以有效工作的关键。从整个IP数据包传输的路径上,我们把ARP解析分解成四种典型的情况: 10 | 11 | ### 1 发送者与接收者在同一个网络 12 | 13 | ![](https://s1.51cto.com/images/blog/201901/08/b24d6f433b2f34df540cb310a7a19af5.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=) 14 | 15 | 发送者与接收者在同一个网络,这时发送者查询路由表得到结果是目的IP即是下一跳的IP地址,只要解析目的IP地址对应的MAC地址,即可直接将IP数据包发送到接收者。 16 | 17 | ### 2 发送者需要首先发送给一个路由器 18 | 19 | ![](https://s1.51cto.com/images/blog/201901/08/0156e0b6f7f32a413438b0be88ade7f9.png) 20 | 21 | 发送者查询路由表得到结果是下一跳是一个路由器,需要将路由器的IP地址进行ARP解析获得路由器对应网络接口的MAC地址,即可将IP数据包发往路由器。这时IP数据包中的目的IP地址和MAC帧中的目的MAC地址并没有对应关系,只是IP数据包通过路由器所在网络作为中转站进行传输而已。 22 | 23 | ### 3 一个路由器转发给另一个路由器 24 | 25 | ![](https://s1.51cto.com/images/blog/201901/08/c02d038be419bdc7c609bcd01db41a2a.png) 26 | 27 | 一个路由器接到一个IP数据包,需要将IP数据包解包出目的IP地址,通过查询路由表得到下一跳的IP地址,这时下一跳往往还是一个路由器,将下一跳的IP地址解析出对应的MAC地址,即可将IP数据包发往另一个路由器。这时IP数据包中的目的IP地址和MAC帧中的目的MAC地址并没有对应关系,但是我们发现下一跳的IP地址始终与MAC帧中的目的MAC地址有着对应关系。 28 | 29 | ### 4 路由器将IP数据包发送给最终接收者 30 | 31 | ![](https://s1.51cto.com/images/blog/201901/08/26ffab864c3f84f2d396f5071b8019e1.png) 32 | 33 | 同样路由器接到一个IP数据包,需要将IP数据包解包出目的IP地址,通过查询路由表得到下一跳的IP地址,这时下一跳的IP地址与目的IP地址相同,因为查询路由表时目的IP地址与路由器所在网络的网络号相同。将下一跳的IP地址(这时即为目的IP地址)解析出对应的MAC地址,即可将IP数据包发往接收者。这种情况下一跳的IP地址与目的IP地址相同,这时目的IP地址与MAC帧中的目的MAC地址代表同一台主机。 34 | 35 | ## ARP协议源代码分析 36 | 37 | ### ARP缓存的数据结构及初始化过程 38 | 39 | 参照tcp和ip协议的的初始化过程类似,查找arp初始化相关代码,见[inet_init](https://github.com/torvalds/linux/blob/v5.4/net/ipv4/af_inet.c#1730)函数: 40 | 41 | ``` 42 | 1730 /* 43 | 1731 * Set the ARP module up 44 | 1732 */ 45 | 1733 46 | 1734 arp_init(); 47 | ``` 48 | [arp_init](https://github.com/torvalds/linux/blob/v5.4/net/ipv4/arp.c#1293)函数如下,其中包括ARP缓存的初始化。 49 | ``` 50 | 1293void __init arp_init(void) 51 | 1294{ 52 | 1295 neigh_table_init(&arp_tbl); 53 | 1296 54 | 1297 dev_add_pack(&arp_packet_type); 55 | 1298 arp_proc_init(); 56 | 1299#ifdef CONFIG_SYSCTL 57 | 1300 neigh_sysctl_register(NULL, &arp_tbl.parms, NULL); 58 | 1301#endif 59 | 1302 register_netdevice_notifier(&arp_netdev_notifier); 60 | 1303} 61 | ``` 62 | ### ARP协议如何更新ARP缓存 63 | 64 | ARP协议的实现代码量不大,主要集中在[arp.c文件](https://github.com/torvalds/linux/blob/v5.4/net/ipv4/arp.c#1293)中。 65 | 66 | ### 创建和发送一个ARP封包 67 | 68 | ``` 69 | 692/* 70 | 693 * Create and send an arp packet. 71 | 694 */ 72 | 695void arp_send(int type, int ptype, __be32 dest_ip, 73 | 696 struct net_device *dev, __be32 src_ip, 74 | 697 const unsigned char *dest_hw, const unsigned char *src_hw, 75 | 698 const unsigned char *target_hw) 76 | 699{ 77 | 700 struct sk_buff *skb; 78 | 701 79 | 702 /* 80 | 703 * No arp on this interface. 81 | 704 */ 82 | 705 83 | 706 if (dev->flags&IFF_NOARP) 84 | 707 return; 85 | 708 86 | 709 skb = arp_create(type, ptype, dest_ip, dev, src_ip, 87 | 710 dest_hw, src_hw, target_hw); 88 | 711 if (skb == NULL) 89 | 712 return; 90 | 713 91 | 714 arp_xmit(skb); 92 | 715} 93 | ``` 94 | ### 接收并处理一个ARP封包 95 | 96 | ``` 97 | 1282/* 98 | 1283 * Called once on startup. 99 | 1284 */ 100 | 1285 101 | 1286static struct packet_type arp_packet_type __read_mostly = { 102 | 1287 .type = cpu_to_be16(ETH_P_ARP), 103 | 1288 .func = arp_rcv, 104 | 1289}; 105 | ``` 106 | 其中callback函数指针arp_rcv由链路层接收到数据后根据MAC帧的类型回调arp_rcv进行ARP数据封包的处理。 107 | ``` 108 | 947/* 109 | 948 * Receive an arp request from the device layer. 110 | 949 */ 111 | 950 112 | 951static int arp_rcv(struct sk_buff *skb, struct net_device *dev, 113 | 952 struct packet_type *pt, struct net_device *orig_dev) 114 | 953{ 115 | 954 const struct arphdr *arp; 116 | 955 117 | 956 /* do not tweak dropwatch on an ARP we will ignore */ 118 | 957 if (dev->flags & IFF_NOARP || 119 | 958 skb->pkt_type == PACKET_OTHERHOST || 120 | 959 skb->pkt_type == PACKET_LOOPBACK) 121 | 960 goto consumeskb; 122 | 961 123 | 962 skb = skb_share_check(skb, GFP_ATOMIC); 124 | 963 if (!skb) 125 | 964 goto out_of_mem; 126 | 965 127 | 966 /* ARP header, plus 2 device addresses, plus 2 IP addresses. */ 128 | 967 if (!pskb_may_pull(skb, arp_hdr_len(dev))) 129 | 968 goto freeskb; 130 | 969 131 | 970 arp = arp_hdr(skb); 132 | 971 if (arp->ar_hln != dev->addr_len || arp->ar_pln != 4) 133 | 972 goto freeskb; 134 | 973 135 | 974 memset(NEIGH_CB(skb), 0, sizeof(struct neighbour_cb)); 136 | 975 137 | 976 return NF_HOOK(NFPROTO_ARP, NF_ARP_IN, skb, dev, NULL, arp_process); 138 | ``` 139 | 具体的ARP协议解析过程主要集中在[arp_process函数中](https://github.com/torvalds/linux/blob/v5.4/net/ipv4/arp.c#arp_process) ,有兴趣的读者可以仔细研究。 140 | 141 | ``` 142 | 718/* 143 | 719 * Process an arp request. 144 | 720 */ 145 | 721 146 | 722static int arp_process(struct sk_buff *skb) 147 | 723{ 148 | ... 149 | ``` 150 | ### 如何将IP地址解析出对应的MAC地址 151 | 152 | 通过跟踪MenuOS中connect建立连接的代码, __ipv4_neigh_lookup_noref函数负责通过查询ARP缓存,connect在内核里的调用栈一直到__ipv4_neigh_lookup_noref函数: 153 | 154 | ``` 155 | 197 neigh = __ipv4_neigh_lookup_noref(dev, nexthop); 156 | (gdb) bt 157 | #0 ip_finish_output2 (skb=) at net/ipv4/ip_output.c:197 158 | #1 ip_finish_output (skb=0xc7bb30b8) at net/ipv4/ip_output.c:271 159 | #2 0xc1603de7 in NF_HOOK_COND (pf=, hook=, 160 | in=, okfn=, cond=, 161 | out=, skb=) at include/linux/netfilter.h:187 162 | #3 ip_output (sk=, skb=0xc7bb30b8) at net/ipv4/ip_output.c:343 163 | #4 0xc16034d2 in dst_output_sk (skb=, sk=) 164 | at include/net/dst.h:458 165 | #5 ip_local_out_sk (sk=0xc7bb8ac0, skb=0xc7bb30b8) at net/ipv4/ip_output.c:110 166 | #6 0xc16037df in ip_local_out (skb=) at include/net/ip.h:117 167 | #7 ip_queue_xmit (sk=0xc7bb8ac0, skb=0xc7ae6d00, fl=) 168 | at net/ipv4/ip_output.c:439 169 | #8 0xc1618513 in tcp_transmit_skb (sk=0xc7bb8ac0, skb=0xc7ae6d00, 170 | clone_it=, gfp_mask=208) at net/ipv4/tcp_output.c:1012 171 | #9 0xc1619fa0 in tcp_connect (sk=0xc7bb8ac0) at net/ipv4/tcp_output.c:3117 172 | #10 0xc161de66 in tcp_v4_connect (sk=0xd4, uaddr=0xc7859d70, 173 | addr_len=) at net/ipv4/tcp_ipv4.c:246 174 | #11 0xc1631226 in __inet_stream_connect (sock=0xc763cd80, 175 | uaddr=, addr_len=, flags=2) 176 | at net/ipv4/af_inet.c:592 177 | #12 0xc163149a in inet_stream_connect (sock=0xc763cd80, uaddr=0xc7859d70, 178 | addr_len=16, flags=2) at net/ipv4/af_inet.c:653 179 | #13 0xc15a9289 in SYSC_connect (fd=, uservaddr=, 180 | addrlen=16) at net/socket.c:1707 181 | #14 0xc15aa0ae in SyS_connect (addrlen=16, uservaddr=-1080639076, fd=4) 182 | at net/socket.c:1688 183 | #15 SYSC_socketcall (call=3, args=) at net/socket.c:2525 184 | #16 0xc15aa90e in SyS_socketcall (call=3, args=-1080639136) 185 | at net/socket.c:2492 186 | ``` 187 | 188 | 从代码的封装看arp_find是负责ARP缓存查询的,但实际上对于IPv4来讲是由__ipv4_neigh_lookup_noref函数完成ARP缓存查询的。 189 | -------------------------------------------------------------------------------- /doc/socket.md: -------------------------------------------------------------------------------- 1 | # 通过Socket编程接口学习计算机网络 2 | 3 | ## 动手写第一个网络程序 4 | 5 | ``` 6 | /* client.c */ 7 | #include /* perror */ 8 | #include /* exit */ 9 | #include /* WNOHANG */ 10 | #include /* waitpid */ 11 | #include /* memset */ 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include /* gethostbyname */ 20 | 21 | #define true 1 22 | #define false 0 23 | 24 | #define PORT 3490 /* Server的端口 */ 25 | #define MAXDATASIZE 100 /* 一次可以读的最大字节数 */ 26 | 27 | 28 | int main(int argc, char *argv[]) 29 | { 30 | int sockfd, numbytes; 31 | char buf[MAXDATASIZE]; 32 | struct hostent *he; /* 主机信息 */ 33 | struct sockaddr_in their_addr; /* 对方地址信息 */ 34 | if (argc != 2) 35 | { 36 | fprintf(stderr,"usage: client hostname\n"); 37 | exit(1); 38 | } 39 | 40 | /* get the host info */ 41 | if ((he=gethostbyname(argv[1])) == NULL) 42 | { 43 | /* 注意:获取DNS信息时,显示出错需要用herror而不是perror */ 44 | /* herror 在新的版本中会出现警告,已经建议不要使用了 */ 45 | perror("gethostbyname"); 46 | exit(1); 47 | } 48 | 49 | if ((sockfd=socket(PF_INET,SOCK_STREAM,0)) == -1) 50 | { 51 | perror("socket"); 52 | exit(1); 53 | } 54 | 55 | their_addr.sin_family = AF_INET; 56 | their_addr.sin_port = htons(PORT); /* short, NBO */ 57 | their_addr.sin_addr = *((struct in_addr *)he->h_addr_list[0]); 58 | memset(&(their_addr.sin_zero),0, 8); /* 其余部分设成0 */ 59 | 60 | if (connect(sockfd, (struct sockaddr *)&their_addr, sizeof(struct sockaddr)) == -1) 61 | { 62 | perror("connect"); 63 | exit(1); 64 | } 65 | 66 | if ((numbytes = recv(sockfd,buf,MAXDATASIZE,0)) == -1) 67 | { 68 | perror("recv"); 69 | exit(1); 70 | } 71 | 72 | buf[numbytes] = '\0'; 73 | printf("Received: %s",buf); 74 | close(sockfd); 75 | 76 | return true; 77 | } 78 | ``` 79 | ``` 80 | 81 | /* server.c */ 82 | #include /* perror */ 83 | #include /* exit */ 84 | #include /* WNOHANG */ 85 | #include /* waitpid */ 86 | #include /* memset */ 87 | #include 88 | #include 89 | #include 90 | #include 91 | #include 92 | #include 93 | #include 94 | #include /* gethostbyname */ 95 | 96 | #define true 1 97 | #define false 0 98 | 99 | #define MYPORT 3490 /* 监听的端口 */ 100 | #define BACKLOG 10 /* listen的请求接收队列长度 */ 101 | 102 | int main() 103 | { 104 | int sockfd, new_fd; /* 监听端口,数据端口 */ 105 | struct sockaddr_in sa; /* 自身的地址信息 */ 106 | struct sockaddr_in their_addr; /* 连接对方的地址信息 */ 107 | int sin_size; 108 | 109 | if ((sockfd = socket(PF_INET, SOCK_STREAM, 0)) == -1) 110 | { 111 | perror("socket"); 112 | exit(1); 113 | } 114 | 115 | sa.sin_family = AF_INET; 116 | sa.sin_port = htons(MYPORT); /* 网络字节顺序 */ 117 | sa.sin_addr.s_addr = INADDR_ANY; /* 自动填本机IP */ 118 | memset(&(sa.sin_zero),0, 8); /* 其余部分置0 */ 119 | 120 | if ( bind(sockfd, (struct sockaddr *)&sa, sizeof(sa)) == -1) 121 | { 122 | perror("bind"); 123 | exit(1); 124 | } 125 | 126 | if (listen(sockfd, BACKLOG) == -1) 127 | { 128 | perror("listen"); 129 | exit(1); 130 | } 131 | 132 | /* 主循环 */ 133 | while(1) 134 | { 135 | sin_size = sizeof(struct sockaddr_in); 136 | new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &sin_size); 137 | if (new_fd == -1) 138 | { 139 | perror("accept"); 140 | continue; 141 | } 142 | 143 | printf("Got connection from %s\n", inet_ntoa(their_addr.sin_addr)); 144 | if (fork() == 0) 145 | { 146 | /* 子进程 */ 147 | if (send(new_fd, "Hello, world!\n", 14, 0) == -1) 148 | perror("send"); 149 | close(new_fd); 150 | exit(0); 151 | } 152 | 153 | close(new_fd); 154 | 155 | /*清除所有子进程 */ 156 | while(waitpid(-1,NULL,WNOHANG) > 0); 157 | } 158 | close(sockfd); 159 | return true; 160 | } 161 | ``` 162 | 163 | ``` 164 | gcc client.c -o client 165 | gcc server.c -o server 166 | ``` 167 | ## Socket编程接口详解 168 | 169 | Socket套接字是通信过程中两端的编程接口。使用套接字类型(Socket Types)和特定协议来创建套接字,创建套接字时获得的文件描述符是编程中访问特定套接字的依据。除了套接字类型和协议族,还有网络地址的存储结构也非常重要,在进一步学习Socket编程接口之前,我们先来具体看看网络地址的存储结构、协议族和地址族、以及套接字类型。 170 | 171 | ## sockaddr和sockaddr_in的不同作用 172 | 173 | 一般在linux环境下/usr/include/bits/socket.h或/usr/include/sys/socket.h可以看到sockaddr的结构体声明。 174 | 175 | ``` 176 | /* Structure describing a generic socket address. */ 177 | struct sockaddr 178 | { 179 | __SOCKADDR_COMMON (sa_); /* Common data: address family and length. */ 180 | char sa_data[14]; /* Address data. */ 181 | }; 182 | ``` 183 | 184 | 这是一个通用的socket地址可以兼容不同的协议,当然包括基于TCP/IP的互联网协议,为了方便起见互联网socket地址的结构提供定义的更具体见/usr/include/netinet/in.h文件中的struct sockaddr_in。 185 | 186 | ``` 187 | /* Structure describing an Internet socket address. */ 188 | struct sockaddr_in 189 | { 190 | __SOCKADDR_COMMON (sin_); 191 | in_port_t sin_port; /* Port number. */ 192 | struct in_addr sin_addr; /* Internet address. */ 193 | 194 | /* Pad to size of `struct sockaddr'. */ 195 | unsigned char sin_zero[sizeof (struct sockaddr) - 196 | __SOCKADDR_COMMON_SIZE - 197 | sizeof (in_port_t) - 198 | sizeof (struct in_addr)]; 199 | }; 200 | ``` 201 | 202 | sockaddr和sockaddr_in的关系有点像面向对象编程中的父类和子类,子类重新定义了父类的地址数据格式。同一块数据我们根据需要使用两个不同的结构体变量来存取数据内容,这也是最简单的面向对象编程中的继承特性的实现方法。 203 | 204 | ## AF_INET和PF_INET 205 | 206 | 在/usr/include/bits/socket.h或/usr/include/sys/socket.h中一般可以找到AF_INET和PF_INET的宏定义如下。 207 | 208 | ``` 209 | /* Protocol families. */ 210 | ... 211 | #define PF_INET 2 /* IP protocol family. */ 212 | ... 213 | /* Address families. */ 214 | ... 215 | #define AF_INET PF_INET 216 | ... 217 | ``` 218 | 219 | 尽管他们的值相同,但它们的含义是不同的,网上很多代码将AF_INET和PF_INET混用,如果您了解他们的含义就不会随便混用了,根据如下注释可以看到A代表Address families,P代表Protocol families,也就是说当表示地址时用AF_INET,表示协议时用PF_INET。我们一般写代码时给地址结构体变量赋值如“serveraddr.sin_family = AF_INET;”中使用AF_INET,而创建套接口时如“sockfd = socket(PF_INET,SOCK_STREAM,0);”中使用PF_INET。 220 | 221 | ## SOCK_STREAM及其他套接字类型 222 | 223 | 在/usr/include/bits/socket_type.h可以找到“__socket_type”,不同协议族一般都会定义不同类型的通信方式,对于基于TCP/IP的互联网协议族(即PF_INET),面向连接的TCP协议的socket类型即为SOCK_STREAM,无连接的UDP协议即为SOCK_DGRAM,而SOCK_RAW 工作在网络层。SOCK_RAW 可以处理ICMP、IGMP等网络报文、特殊的IPv4报文等。 224 | 225 | ``` 226 | /* Types of sockets. */ 227 | enum __socket_type 228 | { 229 | SOCK_STREAM = 1, /* Sequenced, reliable, connection-based 230 | byte streams. */ 231 | #define SOCK_STREAM SOCK_STREAM 232 | SOCK_DGRAM = 2, /* Connectionless, unreliable datagrams 233 | of fixed maximum length. */ 234 | #define SOCK_DGRAM SOCK_DGRAM 235 | SOCK_RAW = 3, /* Raw protocol interface. */ 236 | #define SOCK_RAW SOCK_RAW 237 | SOCK_RDM = 4, /* Reliably-delivered messages. */ 238 | #define SOCK_RDM SOCK_RDM 239 | SOCK_SEQPACKET = 5, /* Sequenced, reliable, connection-based, 240 | datagrams of fixed maximum length. */ 241 | ... 242 | ``` 243 | 244 | 如上几点对于我们使用socket套接口编程非常重要,乃至后续进一步理解和分析Linux网络协议栈代码也比较重要,接下来我们继续看具体的socket套接口API。 245 | 246 | ## 创建一个新的套接字 247 | 248 | 创建一个新的套接字,返回套接字描述符。实际上就是告诉操作系统内核协议栈,我准备使用什么协议族、什么套接字类型、以及哪个协议,内核协议栈帮你分配一个套接字描述符用于后续操作。从程序员的角度看,就是通过socket函数创建一个通信协议实例对象,获得一个操作这个通信协议实例对象的句柄。对于Linux系统来说,套接字描述符使用的是文件描述符,换句话说套接字描述符是一个特殊的文件描述符。按照Unix类系统的设计理念————“一些皆文件”,I/O设备都是以设备文件的形式存在于系统中,网络设备也就是一个特殊的文件,这样也比较好理解。 249 | ``` 250 | int socket( int domain, int type, int protocol); 251 | ``` 252 | socket函数原型中有三个参数: 253 | * domain:域类型,指明使用的协议栈,如TCP/IP使用的是 PF_INET 254 | * type: 指明需要的服务类型, 如SOCK_DGRAM:数据报服务,UDP协议SOCK_STREAM: 流服务,TCP协议 255 | * protocol:一般都取0 256 | 257 | socket函数应用举例: 258 | ``` 259 | int socket_fd = socket(PF_INET, SOCK_STREAM, 0); 260 | ``` 261 | -------------------------------------------------------------------------------- /lab3/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "menu.h" 5 | 6 | #define FONTSIZE 10 7 | int PrintMenuOS() 8 | { 9 | int i, j; 10 | char data_M[FONTSIZE][FONTSIZE] = 11 | { 12 | " ", 13 | " * * ", 14 | " *** *** ", 15 | " * * * * ", 16 | " * * * * ", 17 | " * ** * ", 18 | " * * ", 19 | " * * ", 20 | " * * ", 21 | " " 22 | }; 23 | char data_e[FONTSIZE][FONTSIZE] = 24 | { 25 | " ", 26 | " ", 27 | " ** ", 28 | " * * ", 29 | " * * ", 30 | " ****** ", 31 | " * ", 32 | " * ", 33 | " *** ", 34 | " " 35 | }; 36 | char data_n[FONTSIZE][FONTSIZE] = 37 | { 38 | " ", 39 | " ", 40 | " ** ", 41 | " * * ", 42 | " * * ", 43 | " * * ", 44 | " * * ", 45 | " * * ", 46 | " * * ", 47 | " " 48 | }; 49 | char data_u[FONTSIZE][FONTSIZE] = 50 | { 51 | " ", 52 | " ", 53 | " * * ", 54 | " * * ", 55 | " * * ", 56 | " * * ", 57 | " * * ", 58 | " * ** ", 59 | " ** * ", 60 | " " 61 | }; 62 | char data_O[FONTSIZE][FONTSIZE] = 63 | { 64 | " ", 65 | " **** ", 66 | " * * ", 67 | " * * ", 68 | " * * ", 69 | " * * ", 70 | " * * ", 71 | " * * ", 72 | " **** ", 73 | " " 74 | }; 75 | char data_S[FONTSIZE][FONTSIZE] = 76 | { 77 | " ", 78 | " **** ", 79 | " ** ", 80 | " ** ", 81 | " *** ", 82 | " ** ", 83 | " ** ", 84 | " ** ", 85 | " **** ", 86 | " " 87 | }; 88 | 89 | for(i=0; i 179 | #include 180 | #include 181 | #include 182 | #include 183 | 184 | #include 185 | #include 186 | #define MAX_IFS 64 187 | 188 | int BringUpNetInterface() 189 | { 190 | printf("Bring up interface:lo\n"); 191 | struct sockaddr_in sa; 192 | struct ifreq ifreqlo; 193 | int fd; 194 | sa.sin_family = AF_INET; 195 | sa.sin_addr.s_addr = inet_addr("127.0.0.1"); 196 | fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP); 197 | strncpy(ifreqlo.ifr_name, "lo",sizeof("lo")); 198 | memcpy((char *) &ifreqlo.ifr_addr, (char *) &sa, sizeof(struct sockaddr)); 199 | ioctl(fd, SIOCSIFADDR, &ifreqlo); 200 | ioctl(fd, SIOCGIFFLAGS, &ifreqlo); 201 | ifreqlo.ifr_flags |= IFF_UP|IFF_LOOPBACK|IFF_RUNNING; 202 | ioctl(fd, SIOCSIFFLAGS, &ifreqlo); 203 | close(fd); 204 | 205 | printf("Bring up interface:eth0\n"); 206 | sa.sin_family = AF_INET; 207 | sa.sin_addr.s_addr = inet_addr("192.168.40.254"); 208 | fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP); 209 | strncpy(ifreqlo.ifr_name, "eth0",sizeof("eth0")); 210 | memcpy((char *) &ifreqlo.ifr_addr, (char *) &sa, sizeof(struct sockaddr)); 211 | ioctl(fd, SIOCSIFADDR, &ifreqlo); 212 | ioctl(fd, SIOCGIFFLAGS, &ifreqlo); 213 | ifreqlo.ifr_flags |= IFF_UP|IFF_RUNNING; 214 | ioctl(fd, SIOCSIFFLAGS, &ifreqlo); 215 | close(fd); 216 | 217 | printf("List all interfaces:\n"); 218 | struct ifreq *ifr, *ifend; 219 | struct ifreq ifreq; 220 | struct ifconf ifc; 221 | struct ifreq ifs[MAX_IFS]; 222 | int SockFD; 223 | 224 | 225 | SockFD = socket(PF_INET, SOCK_DGRAM, 0); 226 | 227 | 228 | ifc.ifc_len = sizeof(ifs); 229 | ifc.ifc_req = ifs; 230 | if (ioctl(SockFD, SIOCGIFCONF, &ifc) < 0) 231 | { 232 | printf("ioctl(SIOCGIFCONF): %m\n"); 233 | return 0; 234 | } 235 | 236 | ifend = ifs + (ifc.ifc_len / sizeof(struct ifreq)); 237 | for (ifr = ifc.ifc_req; ifr < ifend; ifr++) 238 | { 239 | printf("interface:%s\n", ifr->ifr_name); 240 | #if 0 241 | if (strcmp(ifr->ifr_name, "lo") == 0) 242 | { 243 | strncpy(ifreq.ifr_name, ifr->ifr_name,sizeof(ifreq.ifr_name)); 244 | ifreq.ifr_flags == IFF_UP; 245 | if (ioctl (SockFD, SIOCSIFFLAGS, &ifreq) < 0) 246 | { 247 | printf("SIOCSIFFLAGS(%s): IFF_UP %m\n", ifreq.ifr_name); 248 | return 0; 249 | } 250 | } 251 | #endif 252 | if (ifr->ifr_addr.sa_family == AF_INET) 253 | { 254 | strncpy(ifreq.ifr_name, ifr->ifr_name,sizeof(ifreq.ifr_name)); 255 | if (ioctl (SockFD, SIOCGIFHWADDR, &ifreq) < 0) 256 | { 257 | printf("SIOCGIFHWADDR(%s): %m\n", ifreq.ifr_name); 258 | return 0; 259 | } 260 | 261 | printf("Ip Address %s\n", inet_ntoa( ( (struct sockaddr_in *) &ifr->ifr_addr)->sin_addr)); 262 | printf("Device %s -> Ethernet %02x:%02x:%02x:%02x:%02x:%02x\n", ifreq.ifr_name, 263 | (int) ((unsigned char *) &ifreq.ifr_hwaddr.sa_data)[0], 264 | (int) ((unsigned char *) &ifreq.ifr_hwaddr.sa_data)[1], 265 | (int) ((unsigned char *) &ifreq.ifr_hwaddr.sa_data)[2], 266 | (int) ((unsigned char *) &ifreq.ifr_hwaddr.sa_data)[3], 267 | (int) ((unsigned char *) &ifreq.ifr_hwaddr.sa_data)[4], 268 | (int) ((unsigned char *) &ifreq.ifr_hwaddr.sa_data)[5]); 269 | } 270 | } 271 | 272 | return 0; 273 | } 274 | int main() 275 | { 276 | BringUpNetInterface(); 277 | PrintMenuOS(); 278 | SetPrompt("MenuOS>>"); 279 | MenuConfig("version","MenuOS V1.0(Based on Linux 3.18.6)",NULL); 280 | MenuConfig("quit","Quit from MenuOS",Quit); 281 | MenuConfig("replyhi", "Reply hi TCP Service", StartReplyhi); 282 | MenuConfig("hello", "Hello TCP Client", Hello); 283 | ExecuteMenu(); 284 | } 285 | 286 | -------------------------------------------------------------------------------- /doc/setupMenuOS.md: -------------------------------------------------------------------------------- 1 | # 构建调试Linux内核网络代码的环境MenuOS系统 2 | 3 | 您可以自定搭建环境,下文将基于Ubuntu 18.04 & linux-5.0.1提供简要指南,以便您能自行构建调试Linux内核网络代码的环境MenuOS系统。您也可以选择使用已经构建好的在线实验环境:[实验楼虚拟机https://www.shiyanlou.com/courses/1198](https://www.shiyanlou.com/courses/1198),只是在线环境构建的比较早,是基于[linux-src](https://github.com/torvalds/linux/blob/v5.4/)内核的。 4 | 5 | ## 编译运行Linux内核 6 | 7 | 您需要有一台带图形界面的Ubuntu Linux桌面主机,为了方便起见一般通过VMware Workstation或者Virtualbox安装Ubuntu Linux虚拟机。以下命令将以64位X86 CPU且带图形界面的Ubuntu Linux桌面主机环境为例。 8 | 9 | ``` 10 | wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.0.1.tar.xz 11 | xz -d linux-5.0.1.tar.xz 12 | tar -xvf linux-5.0.1.tar 13 | cd linux-5.0.1 14 | sudo apt install build-essential flex bison libssl-dev libelf-dev libncurses-dev 15 | make defconfig 16 | make menuconfig # Kernel hacking—>Compile-time checks and compiler options ---> [*] Compile the kernel with debug info 17 | make # 或者使用make -j4,其中4为多核CPU核心数,可以加快编译过程 18 | sudo apt install qemu 19 | qemu-system-x86_64 -kernel linux-5.0.1/arch/x86/boot/bzImage 20 | ``` 21 | ## 构建跟文件系统并运行调试Linux内核 22 | 23 | 光有Linux内核还不是一个完整的Linux系统,还需要有根文件系统及用户态的程序,用户态至少要有一个init可执行程序。 24 | ``` 25 | dd if=/dev/zero of=rootfs.img bs=1M count=128 26 | mkfs.ext4 rootfs.img 27 | mkdir rootfs 28 | sudo mount -o loop rootfs.img rootfs 29 | git clone https://github.com/mengning/net.git 30 | cd lab3 31 | make 32 | cp init rootfs/ 33 | sudo umount rootfs 34 | # KASLR是kernel address space layout randomization的缩写 35 | qemu-system-x86_64 -kernel linux-5.0.1/arch/x86_64/boot/bzImage -hda rootfs.img -append "root=/dev/sda init=/init nokaslr" -s -S 36 | # 另一个shell窗口 37 | gdb 38 | file linux-5.0.1/vmlinux 39 | target remote:1234 #则可以建立gdb和gdbserver之间的连接 40 | break start_kernel 41 | 按c 让qemu上的Linux继续运行 42 | ``` 43 | ## 使用实验楼在线环境运行MenuOS系统 44 | 45 | 实验楼在线环境中已经在LinuxKernel目录下构建好了基于linux-src的内核环境,可以使用实验楼的虚拟机打开Xfce终端(Terminal), 运行MenuOS系统。 46 | 47 | ``` 48 | shiyanlou:~/ $ cd LinuxKernel/ 49 | shiyanlou:LinuxKernel/ $ qemu -kernel linux-src/arch/x86/boot/bzImage -initrd rootfs.img 50 | ``` 51 | 52 | ![](http://i2.51cto.com/images/blog/201811/05/3c8b6968d7384d176a67f35765e371ea.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=) 53 | 54 | 内核启动完成后进入[menu](https://github.com/mengning/menu)程序,支持三个命令help、version和quit,您也可以添加更多的命令。 55 | 56 | # 跟踪分析Linux内核的启动过程的具体操作方法 57 | 58 | 使用gdb跟踪调试内核首先添加-s和-S选项启动MenuOS系 59 | 60 | ``` 61 | qemu -kernel linux-src/arch/x86/boot/bzImage -initrd rootfs.img -s -S 62 | ``` 63 | 64 | 关于-s和-S选项的说明: 65 | > -S freeze CPU at startup (use ’c’ to start execution) 66 | > -s shorthand for -gdb tcp::1234 若不想使用1234端口,则可以使用-gdb tcp:xxxx来取代-s选项 67 | 68 | 右击水平分割或者另外打开一个Xfce终端(Terminal),执行gdb 69 | 70 | ``` 71 | gdb 72 | (gdb)file linux-src/vmlinux # 在gdb界面中targe remote之前加载符号表 73 | (gdb)target remote:1234 # 建立gdb和gdbserver之间的连接 74 | (gdb)break start_kernel # 断点的设置可以在target remote之前,也可以在之后 75 | (gdb)c # 按c 让qemu上的Linux继续运行 76 | ``` 77 | 注意:按Ctrl+Alt从QEMU窗口里的MenuOS系统返回到当前系统,否则会误以为卡死在那里。 78 | 79 | ## 将网络通信程序的服务端集成到MenuOS系统中 80 | 81 | 接下来我们需要将C/S方式的网络通信程序的服务端集成到MenuOS系统中,成为MenuOS系统的命令replyhi,实际上我们已经给大家集成好了,我们git clone 克隆一个linuxnet.git;进入lab2目录执行make可以将我们集成好的代码copy到menu项目中。然后进入menu,我们写了一个脚本rootfs,运行make rootfs,脚本就可以帮助我们自动编译、自动生成根文件系统,还会帮我们运行起来MenuOS系统。详细命令如下: 82 | 83 | ``` 84 | cd LinuxKernel 85 | git clone https://github.com/mengning/linuxnet.git 86 | cd linuxnet/lab2 87 | make 88 | cd ../../menu/ 89 | make rootfs 90 | ``` 91 | 执行效果大致如下: 92 | ![](http://i2.51cto.com/images/blog/201811/07/cbff044523e47cad65fb363625df0b42.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=) 93 | 94 | 其中我们增加了命令replyhi,功能是回复hi的TCP服务,具体是怎么来做这个事情的呢?实现起来还是比较简单的,我们来看linuxnet/lab2/test_reply.c,里面我们从main()开始读发现里面增加了一行代码 95 | 96 | 97 | ``` 98 | MenuConfig("replyhi", "Reply hi TCP Service", StartReplyhi); 99 | ``` 100 | 101 | 其中的StartReplyhi代码如下: 102 | 103 | ``` 104 | int StartReplyhi(int argc, char *argv[]) 105 | { 106 | int pid; 107 | /* fork another process */ 108 | pid = fork(); 109 | if (pid < 0) 110 | { 111 | /* error occurred */ 112 | fprintf(stderr, "Fork Failed!"); 113 | exit(-1); 114 | } 115 | else if (pid == 0) 116 | { 117 | /* child process */ 118 | Replyhi(); 119 | printf("Reply hi TCP Service Started!\n"); 120 | } 121 | else 122 | { 123 | /* parent process */ 124 | printf("Please input hello...\n"); 125 | } 126 | } 127 | ``` 128 | 129 | 显然StartReplyhi代码使用fork创建了一个子进程,子进程调用了Replyhi函数: 130 | 131 | ``` 132 | #include"syswrapper.h" 133 | #define MAX_CONNECT_QUEUE 1024 134 | int Replyhi() 135 | { 136 | char szBuf[MAX_BUF_LEN] = "\0"; 137 | char szReplyMsg[MAX_BUF_LEN] = "hi\0"; 138 | InitializeService(); 139 | while (1) 140 | { 141 | ServiceStart(); 142 | RecvMsg(szBuf); 143 | SendMsg(szReplyMsg); 144 | ServiceStop(); 145 | } 146 | ShutdownService(); 147 | return 0; 148 | } 149 | ``` 150 | 151 | Replyhi函数的内容和之前的C/S网络通信程序的server.c基本一样。 152 | 如果还要给MenuOS增加新的命令只需要按这种方式MenuConfig增加一行,再增加对应的函数就可以了。 153 | 154 | 接下来您就可以参照前面“跟踪分析Linux内核的启动过程的具体操作方法”进行跟踪调试了,只是我们socket接口使用的是系统sys_socketcall,可以将sys_socketcall设为断点跟踪看看。 155 | 156 | ## 初始化MenuOS系统的网络功能 157 | 158 | 之前我们已经将TCP网络程序的服务端replyhi集成到MenuOS中了,而且可以正常的启动TCP服务,方便我们跟踪socket、bind、listen、accept几个API接口到内核处理函数,但是我们启动的TCP服务并不能正常对外提供服务,因为MenuOS没有初始化网络设备(包括本地回环loopback设备),因此它无法接收到任何网络请求。 159 | 接下来我们需要搞清楚如何激活Linux网络设备,并将MenuOS系统的网络设备用简便的方式配置好,使我们将TCP客户端也集成进去后可以完整的运行TCP网络程序的服务端和客户端程序。 160 | 161 | ### 如何激活Linux网络设备接口 162 | 163 | #### Linux发行版一般在启动过程中自动激活网络设备接口的方式 164 | 一般Linux系统中的/etc/network/interfaces文件里会看到大致如下的代码: 165 | ``` 166 | auto lo 167 | iface lo inet loopback 168 | auto eth0 169 | iface eth0 inet static 170 | auto wlan1 171 | iface wlan1 inet dhcp 172 | ``` 173 | auto lo/eth0/wlan1都是表示系统启动时自动激活该网络设备接口,一般使用ifup来激活网络设备接口。 174 | inet是指定该网络设备接口使用互联网地址,其中loopback表示使用IP地址127.0.0.1(网络地址127.0.0.0/8),RFC 1700中定义本地回环网络的地址;static表示手工设置地址;dhcp表示通过DHCP协议自动获取地址。 175 | 176 | Linux系统的启动脚本一般会通过执行ifconfig来激活网络设备接口,ifconfig内部通过调用socket和ioctl来通知内核给网络设备接口做适当的设置工作,例如在实验楼虚拟机环境下跟踪ifconfig配置IP并激活本地回环lo网络接口如下: 177 | ``` 178 | shiyanlou:~/ $ sudo strace ifconfig lo 127.0.0.1 up [18:04:53] 179 | execve("/sbin/ifconfig", ["ifconfig", "lo", "127.0.0.1", "up"], [/* 30 vars */]) = 0 180 | ... 181 | socket(PF_INET, SOCK_DGRAM, IPPROTO_IP) = 4 182 | ... 183 | ioctl(4, SIOCSIFADDR, {ifr_name="lo", ???}) = -1 EPERM (Operation not permitted) 184 | ... 185 | ioctl(4, SIOCGIFFLAGS, {ifr_name="lo", ifr_flags=IFF_UP|IFF_LOOPBACK|IFF_RUNNING}) = 0 186 | ioctl(4, SIOCSIFFLAGS, {ifr_name="lo", ???}) = -1 EPERM (Operation not permitted) 187 | ... 188 | ioctl(4, SIOCGIFFLAGS, {ifr_name="lo", ifr_flags=IFF_UP|IFF_LOOPBACK|IFF_RUNNING}) = 0 189 | ioctl(4, SIOCSIFFLAGS, {ifr_name="lo", ???}) = -1 EPERM (Operation not permitted) 190 | ... 191 | shiyanlou:~/ $ 192 | ``` 193 | 194 | #### 在MenuOS中手工编码激活网络设备接口lo 195 | 196 | 仿照Linux系统一般在启动过程中自动激活网络设备接口使用的ifconfig程序中调用socket和ioctl接口的方式撰写代码来激活网络接口lo,[手工编码激活网络设备接口lo](https://github.com/mengning/linuxnet/blob/master/lab3/main.c#L188)具体摘录代码如下: 197 | 198 | ``` 199 | struct sockaddr_in sa; 200 | struct ifreq ifreqlo; 201 | int fd; 202 | sa.sin_family = AF_INET; 203 | sa.sin_addr.s_addr = inet_addr("127.0.0.1"); 204 | fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP); 205 | strncpy(ifreqlo.ifr_name, "lo",sizeof("lo")); 206 | memcpy((char *) &ifreqlo.ifr_addr, (char *) &sa, sizeof(struct sockaddr)); 207 | ioctl(fd, SIOCSIFADDR, &ifreqlo); 208 | ioctl(fd, SIOCGIFFLAGS, &ifreqlo); 209 | ifreqlo.ifr_flags |= IFF_UP|IFF_LOOPBACK|IFF_RUNNING; 210 | ioctl(fd, SIOCSIFFLAGS, &ifreqlo); 211 | close(fd); 212 | ``` 213 | 其中创建了socket描述符fd,指明网络接口lo、地址族AF_INET和IP地址127.0.0.1,通过ioctl利用预定义的指令SIOCSIFADDR(Socket IO Configuration Set IF ADDR)设置网络接口配置信息,然后利用SIOCSIFFLAGS设置IFF_UP|IFF_LOOPBACK|IFF_RUNNING激活该网络接口。 214 | 215 | ## 将TCP网络通信程序的客户端也集成到MenuOS系统中 216 | 217 | 接下来我们需要将C/S方式的网络通信程序的客户端也集成到MenuOS系统中,成为MenuOS系统的命令hello。首先增加Hello,可以直接拷贝原来client.c中的代码如下: 218 | ``` 219 | int Hello(int argc, char *argv[]) 220 | { 221 | char szBuf[MAX_BUF_LEN] = "\0"; 222 | char szMsg[MAX_BUF_LEN] = "hello\0"; 223 | OpenRemoteService(); 224 | SendMsg(szMsg); 225 | RecvMsg(szBuf); 226 | CloseRemoteService(); 227 | return 0; 228 | } 229 | ``` 230 | 然后在main函数中和repplyhi方式一样增加一行MenuConfig("hello", "Hello TCP Client", Hello)如下: 231 | ``` 232 | MenuConfig("replyhi", "Reply hi TCP Service", StartReplyhi); 233 | MenuConfig("hello", "Hello TCP Client", Hello); 234 | ``` 235 | 实际上我们已经给大家集成好了,我们git clone 克隆一个linuxnet.git;进入lab3目录执行make可以将我们集成好的代码copy到menu项目中。然后进入menu,我们写了一个脚本rootfs,运行make rootfs,脚本就可以帮助我们自动编译、自动生成根文件系统,还会帮我们运行起来MenuOS系统。详细命令如下: 236 | ``` 237 | cd LinuxKernel 238 | git clone https://github.com/mengning/linuxnet.git 239 | cd linuxnet/lab3 240 | make rootfs 241 | ``` 242 | 运行起来的MenuOS中执行help命令可以看到其中不止有replyhi,也有了hello命令,我们可以先执行replyhi,然后执行hello。具体效果如下: 243 | 244 | ![](http://i2.51cto.com/images/blog/201811/12/f70b42b94f0bb72f74de1acaf51cfcf1.png) 245 | 246 | 这样我们构建好了带网络功能的MenoOS系统,方便我们后续跟踪Linux内核代码。 247 | -------------------------------------------------------------------------------- /doc/ip.md: -------------------------------------------------------------------------------- 1 | # 敢问路在何方?—— IP协议和路由表 2 | 3 | IP协议和路由表是整个互联网架构的核心基础设施,从本文开始我们将深入理解互联网架构的核心,其中包括IP协议和路由表是核心中的核心,本文将从IP地址及无类别区间路由CIDR谈起,先从原理上理解选路路由的方法,然后再阅读Linux内核中IP协议相关的代码,原理和代码相互印证,感兴趣的同学还可以基于我们的MenuOS进一步跟踪分析路由表查询相关的代码。 4 | 5 | ## IP协议及IP封包格式 6 | 7 | 协议本质上就是通讯的双方共同遵守的规则,对于IP协议来说最关键的规则就是IP的封包格式 8 | 9 | ![](https://s1.51cto.com/images/blog/201901/08/9840b1b6ce8d52ce0b1510e34b525fc8.png) 10 | 11 | 其中源IP地址和目的IP地址是最关键的信息。 12 | 13 | ## IP地址及无类别区间路由CIDR 14 | 15 | IP地址最初是分类别的,如A类、B类、C类等,IPv4地址长度32位,对应的A类网络的网络号占一个字节,B类网络的网络号占2个字节,C类网络的网络号占3个字节。随着互联网的发展32位的IP地址资源明星不足,而获得A类网络的组织却很可能有大量地址资源没有得到充分利用,而获得C类网络的组织随着规模的扩大却又申请了多个C类网络,因此按类别划分IP网络地址的方案逐渐暴露出IP地址资源利用率不高、灵活性差的问题。 16 | 17 | 解决IPv4地址利用率不高的问题,诞生了多种方案比如NAT和NAPT,通过网络地址转换将私有网络地址转换为公网地址,大大提高了IP地址的利用效率,这种方案在地址资源较为匮乏的国家和地区应用非常广泛,比如中国。 18 | 19 | 无类别区间路由CIDR是其中最重要的一个方案,它既可以有效提交IPv4地址的利用率,又能灵活低拆分和汇聚地址块,大大减少路由表条目,提高了整个网络的运作效率。CIDR是怎么做到这一点的呢? 20 | 21 | 所谓无类别区间路由CIDR,无类别就是打破最初A类、B类、C类等按类别划分网络的地址的方法,使用掩码Mast来指明网络号,比如原来的A网络我们可以使用255.0.0.0的掩码来提取出网络号,另外一种表现形式是这样一个A类网络的IP地址x.x.x.x/8可以通过后缀/8来指明网络的比特长度。 22 | 23 | 这么做的好处是显而易见的,通过增加网络号的长度可以灵活地划分子网,同时路由表可以通过缩短网络号合并其中包含的子网络,减少路由表的项目数量,提高路由查询速度。 24 | 25 | ## 路由表查询的方法 26 | 27 | 如图网络结构中R1路由器有四个接口分别连接了四个网络,其中一个m2接口连接的网络中有一台路由器连接到互联网上。 28 | 29 | ![](https://s1.51cto.com/images/blog/201901/08/58f8a4d12a50b72a6c2236140f6611b4.png) 30 | 31 | R1路由器的路由表如图所示: 32 | 33 | ![](https://s1.51cto.com/images/blog/201901/08/d45f75b01a717c4125f0700e1f974c51.png) 34 | 35 | 当R1路由器接到一个IP包时会解析出IP包中的目的IP地址,用掩码Mast提取出目的IP地址的网络地址作为输入参数查询路由表,匹配路由表项的网络地址,匹配成功获得下一跳next-hop的IP地址及网络接口号,如下图所示。 36 | 37 | ![](https://s1.51cto.com/images/blog/201901/08/218ed33cd03b1420721710ca52887c9d.png) 38 | 39 | 路由表查询结果是下一跳next-hop的IP地址及网络接口号,通过下一跳next-hop的IP地址及网络接口号可以进行ARP解析获取对应的MAC地址,这一部分是理解网络架构中网络层与链路层相互协作的关键,我们将在下一篇文章中专题介绍。 40 | 41 | ## 在Linux系统使用route命令可以查看路由表信息 42 | 43 | ``` 44 | shiyanlou:~/ $ route 45 | Kernel IP routing table 46 | Destination Gateway Genmask Flags Metric Ref Use Iface 47 | default 192.168.40.1 0.0.0.0 UG 0 0 0 eth0 48 | 192.168.40.0 * 255.255.255.0 U 0 0 0 eth0 49 | shiyanlou:~/ $ 50 | ``` 51 | 52 | 有了前面的相关背景知识的铺垫接下来就可以看代码了。 53 | 54 | ## IP协议的初始化 55 | 56 | [IP协议的初始化函数ip_init](https://github.com/torvalds/linux/blob/v5.4/net/ipv4/af_inet.c#1740)与TCP一样也是在inet_init函数中被调用的,如/linux-src/net/ipv4/af_inet.c中1740行代码处。 57 | ``` 58 | 1674static int __init inet_init(void) 59 | 1675{ 60 | ... 61 | 1730 /* 62 | 1731 * Set the ARP module up 63 | 1732 */ 64 | 1733 65 | 1734 arp_init(); 66 | 1735 67 | 1736 /* 68 | 1737 * Set the IP module up 69 | 1738 */ 70 | 1739 71 | 1740 ip_init(); 72 | 1741 73 | 1742 tcp_v4_init(); 74 | 1743 75 | 1744 /* Setup TCP slab cache for open requests. */ 76 | 1745 tcp_init(); 77 | 1746 78 | 1747 /* Setup UDP memory threshold */ 79 | 1748 udp_init(); 80 | 1749 81 | 1750 /* Add UDP-Lite (RFC 3828) */ 82 | 1751 udplite4_register(); 83 | 1752 84 | 1753 ping_init(); 85 | 1754 86 | 1755 /* 87 | 1756 * Set the ICMP layer up 88 | 1757 */ 89 | 1758 90 | 1759 if (icmp_init() < 0) 91 | 1760 panic("Failed to create the ICMP control socket.\n"); 92 | 1761 93 | 1762 /* 94 | 1763 * Initialise the multicast router 95 | 1764 */ 96 | 1765#if defined(CONFIG_IP_MROUTE) 97 | 1766 if (ip_mr_init()) 98 | 1767 pr_crit("%s: Cannot init ipv4 mroute\n", __func__); 99 | 1768#endif 100 | 1769 101 | 1770 if (init_inet_pernet_ops()) 102 | 1771 pr_crit("%s: Cannot init ipv4 inet pernet ops\n", __func__); 103 | 1772 /* 104 | 1773 * Initialise per-cpu ipv4 mibs 105 | 1774 */ 106 | 1775 107 | 1776 if (init_ipv4_mibs()) 108 | 1777 pr_crit("%s: Cannot init ipv4 mibs\n", __func__); 109 | 1778 110 | 1779 ipv4_proc_init(); 111 | 1780 112 | 1781 ipfrag_init(); 113 | 1782 114 | 1783 dev_add_pack(&ip_packet_type); 115 | ... 116 | 1795} 117 | 1796 118 | 1797fs_initcall(inet_init); 119 | ``` 120 | 路由表的结构和初始化过程,ip协议初始化ip_init过程中包含路由表的初始化ip_rt_init/ip_fib_init,主要代码见route.c及fib*.c。[ip_init函数](https://github.com/torvalds/linux/blob/v5.4/net/ipv4/ip_output.c#1602)主要做了三方面工作: 121 | * ip_rt_init() 初始化路由缓存,通过哈希结构提供快速获取目的IP地址的下一跳(Next Hop)访问, 以及初始化作为路由表内部表示形式的FIB (Forwarding Information Base) 122 | * ip_rt_init() 还调用ip_fib_init() 初始化上层的路由相关数据结构 123 | * inet_initpeers()初始化AVL tree用于跟踪最近有数据通信的IP peers和hosts。 124 | ``` 125 | 1602void __init ip_init(void) 126 | 1603{ 127 | 1604 ip_rt_init(); 128 | 1605 inet_initpeers(); 129 | 1606 130 | 1607#if defined(CONFIG_IP_MULTICAST) 131 | 1608 igmp_mc_init(); 132 | 1609#endif 133 | 1610} 134 | ``` 135 | 136 | 137 | ## 查询路由表 138 | 通过目的IP查询路由表的到下一跳的IP地址的过程, fib_lookup为起点,从[fib_lookup函数](https://github.com/torvalds/linux/blob/v5.4/include/net/ip_fib.h#222)这里可以进一步深入了解查询路由表的过程,当然这里需要理解路由表的数据结构和查询算法,会比较复杂。 139 | ``` 140 | 222static inline int fib_lookup(struct net *net, const struct flowi4 *flp, 141 | 223 struct fib_result *res) 142 | 224{ 143 | 225 struct fib_table *table; 144 | 226 145 | 227 table = fib_get_table(net, RT_TABLE_LOCAL); 146 | 228 if (!fib_table_lookup(table, flp, res, FIB_LOOKUP_NOREF)) 147 | 229 return 0; 148 | 230 149 | 231 table = fib_get_table(net, RT_TABLE_MAIN); 150 | 232 if (!fib_table_lookup(table, flp, res, FIB_LOOKUP_NOREF)) 151 | 233 return 0; 152 | 234 return -ENETUNREACH; 153 | 235} 154 | ``` 155 | [5.x版本的fib_lookup函数](https://github.com/torvalds/linux/blob/386403a115f95997c2715691226e11a7b5cffcfd/include/net/ip_fib.h#L294) - [fib_table_lookup](https://github.com/torvalds/linux/blob/386403a115f95997c2715691226e11a7b5cffcfd/net/ipv4/fib_trie.c#L1314) 156 | 157 | [struct flowi4](https://github.com/torvalds/linux/blob/63bdf4284c38a48af21745ceb148a087b190cd21/include/net/flow.h#L70) 158 | ``` 159 | struct flowi4 { 160 | struct flowi_common __fl_common; 161 | #define flowi4_oif __fl_common.flowic_oif 162 | #define flowi4_iif __fl_common.flowic_iif 163 | #define flowi4_mark __fl_common.flowic_mark 164 | #define flowi4_tos __fl_common.flowic_tos 165 | #define flowi4_scope __fl_common.flowic_scope 166 | #define flowi4_proto __fl_common.flowic_proto 167 | #define flowi4_flags __fl_common.flowic_flags 168 | #define flowi4_secid __fl_common.flowic_secid 169 | #define flowi4_tun_key __fl_common.flowic_tun_key 170 | #define flowi4_uid __fl_common.flowic_uid 171 | #define flowi4_multipath_hash __fl_common.flowic_multipath_hash 172 | 173 | /* (saddr,daddr) must be grouped, same order as in IP header */ 174 | __be32 saddr; 175 | __be32 daddr; 176 | 177 | union flowi_uli uli; 178 | #define fl4_sport uli.ports.sport 179 | #define fl4_dport uli.ports.dport 180 | #define fl4_icmp_type uli.icmpt.type 181 | #define fl4_icmp_code uli.icmpt.code 182 | #define fl4_ipsec_spi uli.spi 183 | #define fl4_mh_type uli.mht.type 184 | #define fl4_gre_key uli.gre_key 185 | } __attribute__((__aligned__(BITS_PER_LONG/8))); 186 | ``` 187 | 有兴趣的读者可以进一步阅读或跟踪代码,如下为在[实验三中MenuOS系统](https://www.shiyanlou.com/courses/1198)上跟踪到fib_lookup函数时的调用栈。 188 | ``` 189 | 2112 if (fib_lookup(net, fl4, &res)) { 190 | (gdb) bt 191 | #0 __ip_route_output_key (net=0xc1a08d40 , fl4=0xc7bb8cf4) 192 | at net/ipv4/route.c:2112 193 | #1 0xc161dc77 in ip_route_connect (protocol=, 194 | sk=, dport=, sport=, 195 | oif=, tos=, src=, 196 | dst=, fl4=) at include/net/route.h:268 197 | #2 tcp_v4_connect (sk=0x100007f, uaddr=, 198 | addr_len=) at net/ipv4/tcp_ipv4.c:171 199 | #3 0xc1631226 in __inet_stream_connect (sock=0xc763cd80, 200 | uaddr=, addr_len=, flags=2) 201 | at net/ipv4/af_inet.c:592 202 | ---Type to continue, or q to quit--- 203 | #4 0xc163149a in inet_stream_connect (sock=0xc763cd80, uaddr=0xc7859d70, 204 | addr_len=16, flags=2) at net/ipv4/af_inet.c:653 205 | #5 0xc15a9289 in SYSC_connect (fd=, uservaddr=, 206 | addrlen=16) at net/socket.c:1707 207 | #6 0xc15aa0ae in SyS_connect (addrlen=16, uservaddr=-1080639076, fd=4) 208 | at net/socket.c:1688 209 | #7 SYSC_socketcall (call=3, args=) at net/socket.c:2525 210 | #8 0xc15aa90e in SyS_socketcall (call=3, args=-1080639136) 211 | at net/socket.c:2492 212 | #9 213 | #10 0xb7783c5c in ?? () 214 | ``` 215 | 216 | ## 结合IP包的首发过程从整体上理解IP协议及路由选择 217 | 218 | IP数据包的收或发的过程是传输层协议数据收发过程的延伸,在分析TCP协议的过程中,我们涉及到数据收发的过程,同样对于IP协议,从上层传输层会调用IP协议的发送数据接口,见ip_queue_xmit;同时数据接收的过程中底层链路层会调用IP协议的接收数据的接口,见ip_rcv。 219 | 220 | ![](https://s1.51cto.com/images/blog/201901/08/62ed2fe659ad4a6d83b0e40969de6899.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=) 221 | 222 | 223 | ## 参考资料 224 | 225 | * https://people.cs.clemson.edu/~westall/853/notes/ipinit.pdf 226 | * Behrouz A. Forouzan, Sophia Chung Fegan. TCP/IP Protocol Suite (3rd Edition) 227 | * http://en.wikipedia.org/wiki/Routing_table 228 | * http://zh.wikipedia.org/wiki/%E5%86%85%E9%83%A8%E7%BD%91%E5%85%B3%E5%8D%8F%E8%AE%AE 229 | * http://computer.bowenwang.com.cn/internet-infrastructure1.htm 230 | * http://tools.ietf.org/html/rfc2453 231 | * http://zh.wikipedia.org/zh-cn/%E5%BC%80%E6%94%BE%E5%BC%8F%E6%9C%80%E7%9F%AD%E8%B7%AF%E5%BE%84%E4%BC%98%E5%85%88 232 | 233 | -------------------------------------------------------------------------------- /doc/tcpip.md: -------------------------------------------------------------------------------- 1 | # Linux内核初始化过程中加载TCP/IP协议栈 2 | 3 | Linux内核初始化过程中加载TCP/IP协议栈,从start_kernel、kernel_init、do_initcalls、inet_init,找出Linux内核初始化TCP/IP的入口位置,即为inet_init函数。 4 | 5 | ## Linux内核启动过程 6 | 7 | 之前的实验中我们设置了断点start_kernel,start_kernel即是Linux内核的起点,相当于我们普通C程序的main函数,我们知道C语言代码从main函数开启动,C程序的阅读也从main函数开始。这个start_kernel也是整个Linux内核启动的起点,我们可以在内核代码路面init/main.c中找到`start_kernel`函数,这个地方就是初始化Linux内核启动的起点。 8 | 9 | 10 | 我们知道如何跟踪内核代码运行过程的话,我们应该有目的的来跟踪它。我们来跟踪内核启动过程,并重点找出初始化TCP/IP协议栈的位置。 11 | 12 | 首先我们找到内核启动的起点`start_kernel`函数所在的main.c,我们简单浏览一下`start_kernel`函数,这里有很多其他的模块初始化工作,因为这里边每一个启动的点都涉及到比较复杂的模块,因为内核非常庞大,包括很多的模块,当然如果你研究内核的某个模块的话,往往都需要了解main.c中`start_kernel`这一块,因为内核的主要模块的初始化工作,都是直接或间接从`start_kernel`函数里开始调用的。涉及到的模块太多太复杂,那我们只看我们需要了解的东西,这里边有很多setup设置的东西,这里边有一个`trap_init`函数调用,涉及到一些初始化中断向量,可以看到它在`set_intr_gate`设置到很多的中断门,很多的硬件中断,其中有一个系统陷阱门,进行系统调用的。其他还有`mm_init`内存管理模块的初始化等等。`start_kernel`中的最后一句为`rest_init`,这个比较有意思。内核启动完了之后,有一个`call_cpu_idle`,当系统没有进程需要执行时就调用idle进程。`rest_init`是0号进程,它创建了1号进程init和其他的一些服务进程。这就是内核的启动过程,我们先简单这样看,然后可以在重点找出网络初始化以及初始化TCP/IP协议栈的位置。下面我们再分析一下关键的函数。 13 | 14 | ### `start_kernel()` 15 | 16 | main.c 中没有 main 函数,`start_kernel()` 相当于main函数。`start_kernel`是一切的起点,在此函数被调用之前内核代码主要是用汇编语言写的,完成硬件系统的初始化工作,为C代码的运行设置环境。由调试可得`start_kernel`在[/linux-src/init/main.c#500](https://github.com/torvalds/linux/blob/v5.4/init/main.c#500): 17 | 18 | ``` 19 | 500asmlinkage __visible void __init start_kernel(void) 20 | 501{ 21 | ... 22 | 679 /* Do the rest non-__init'ed, we're now alive */ 23 | 680 rest_init(); 24 | 681} 25 | ``` 26 | 27 | ### `rest_init()`函数 28 | 29 | 30 | rest_init在[linux-src/init/main.c#393](https://github.com/torvalds/linux/blob/v5.4/init/main.c#393)的位置: 31 | ``` 32 | 393static noinline void __init_refok rest_init(void) 33 | 394{ 34 | 395 int pid; 35 | 396 36 | 397 rcu_scheduler_starting(); 37 | 398 /* 38 | 399 * We need to spawn init first so that it obtains pid 1, however 39 | 400 * the init task will end up wanting to create kthreads, which, if 40 | 401 * we schedule it before we create kthreadd, will OOPS. 41 | 402 */ 42 | 403 kernel_thread(kernel_init, NULL, CLONE_FS); 43 | 404 numa_default_policy(); 44 | 405 pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES); 45 | 406 rcu_read_lock(); 46 | 407 kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns); 47 | 408 rcu_read_unlock(); 48 | 409 complete(&kthreadd_done); 49 | 410 50 | 411 /* 51 | 412 * The boot idle thread must execute schedule() 52 | 413 * at least once to get things moving: 53 | 414 */ 54 | 415 init_idle_bootup_task(current); 55 | 416 schedule_preempt_disabled(); 56 | 417 /* Call into cpu_idle with preempt disabled */ 57 | 418 cpu_startup_entry(CPUHP_ONLINE); 58 | 419} 59 | ``` 60 | 61 | 通过`rest_init()`新建`kernel_init`、`kthreadd`内核线程。403行代码 ```kernel_thread(kernel_init, NULL, CLONE_FS);```,由注释得调用 `kernel_thread()`创建1号内核线程(在`kernel_init`函数正式启动),`kernel_init`函数启动了init用户程序。 62 | 63 | 另外405行代码 ```pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);``` 调用`kernel_thread`执行`kthreadd`,创建PID为2的内核线程。 64 | 65 | `rest_init()`最后调用`cpu_idle()` 演变成了idle进程。 66 | 67 | ## Linux内核是如何加载TCP/IP协议栈的? 68 | 69 | ### `kernel_init`函数和do_basic_setup函数 70 | 71 | `kernel_init`函数的主要工作是夹在init用户程序,但是在加载init用户程序前通过kernel_init_freeable函数进一步做了一些初始化的工作。[`kernel_init`函数和kernel_init_freeable函数](https://github.com/torvalds/linux/blob/v5.4/init/main.c#934): 72 | 73 | ``` 74 | 930static int __ref kernel_init(void *unused) 75 | 931{ 76 | 932 int ret; 77 | 933 78 | 934 kernel_init_freeable(); 79 | 935 /* need to finish all async __init code before freeing the memory */ 80 | 936 async_synchronize_full(); 81 | 937 free_initmem(); 82 | 938 mark_rodata_ro(); 83 | 939 system_state = SYSTEM_RUNNING; 84 | 940 numa_default_policy(); 85 | 941 86 | 942 flush_delayed_fput(); 87 | 943 88 | 944 if (ramdisk_execute_command) { 89 | 945 ret = run_init_process(ramdisk_execute_command); 90 | 946 if (!ret) 91 | 947 return 0; 92 | 948 pr_err("Failed to execute %s (error %d)\n", 93 | 949 ramdisk_execute_command, ret); 94 | 950 } 95 | 951 96 | 952 /* 97 | 953 * We try each of these until one succeeds. 98 | 954 * 99 | 955 * The Bourne shell can be used instead of init if we are 100 | 956 * trying to recover a really broken machine. 101 | 957 */ 102 | 958 if (execute_command) { 103 | 959 ret = run_init_process(execute_command); 104 | 960 if (!ret) 105 | 961 return 0; 106 | 962 pr_err("Failed to execute %s (error %d). Attempting defaults...\n", 107 | 963 execute_command, ret); 108 | 964 } 109 | 965 if (!try_to_run_init_process("/sbin/init") || 110 | 966 !try_to_run_init_process("/etc/init") || 111 | 967 !try_to_run_init_process("/bin/init") || 112 | 968 !try_to_run_init_process("/bin/sh")) 113 | 969 return 0; 114 | 970 115 | 971 panic("No working init found. Try passing init= option to kernel. " 116 | 972 "See Linux Documentation/init.txt for guidance."); 117 | 973} 118 | 974 119 | 975static noinline void __init kernel_init_freeable(void) 120 | 976{ 121 | 977 /* 122 | 978 * Wait until kthreadd is all set-up. 123 | 979 */ 124 | 980 wait_for_completion(&kthreadd_done); 125 | 981 126 | ... 127 | 1004 do_basic_setup(); 128 | 1005 129 | ... 130 | 1033} 131 | 1034 132 | ``` 133 | 134 | kernel_init_freeable函数做的一些初始化的工作与我们网络初始化有关的主要在[do_basic_setup函数](https://github.com/torvalds/linux/blob/v5.4/init/main.c#867)中,其中do_initcalls用一种巧妙的方式对一些子系统进行了初始化,其中包括TCP/IP网络协议栈的初始化。 135 | ``` 136 | 867/* 137 | 868 * Ok, the machine is now initialized. None of the devices 138 | 869 * have been touched yet, but the CPU subsystem is up and 139 | 870 * running, and memory and process management works. 140 | 871 * 141 | 872 * Now we can finally start doing some real work.. 142 | 873 */ 143 | 874static void __init do_basic_setup(void) 144 | 875{ 145 | 876 cpuset_init_smp(); 146 | 877 usermodehelper_init(); 147 | 878 shmem_init(); 148 | 879 driver_init(); 149 | 880 init_irq_proc(); 150 | 881 do_ctors(); 151 | 882 usermodehelper_enable(); 152 | 883 do_initcalls(); 153 | 884 random_int_secret_init(); 154 | 885} 155 | ``` 156 | 157 | ### do_initcalls函数巧妙地对网络协议进行初始化 158 | 159 | [do_initcalls函数](https://github.com/torvalds/linux/blob/v5.4/init/main.c#859)是table驱动的,维护了一个initcalls的table,从而可以对每一个注册进来的初始化项目进行初始化,这个巧妙的机制可以理解成观察者模式,每一个协议子系统是一个观察者,将它的初始化入口注册进来,do_initcalls函数是被观察者负责统一调用每一个子系统的初始化函数指针。 160 | ``` 161 | 859static void __init do_initcalls(void) 162 | 860{ 163 | 861 int level; 164 | 862 165 | 863 for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++) 166 | 864 do_initcall_level(level); 167 | 865} 168 | ``` 169 | 以TCP/IP协议栈为例,[inet_init函数](https://github.com/torvalds/linux/blob/v5.4/net/ipv4/af_inet.c#1674)是TCP/IP协议栈初始化的入口函数,通过fs_initcall(inet_init)将inet_init函数注册进initcalls的table。 170 | ``` 171 | 1674static int __init inet_init(void) 172 | 1675{ 173 | ... 174 | 1795} 175 | 1796 176 | 1797fs_initcall(inet_init); 177 | ``` 178 | 这里do_initcalls的注册和调用机制是通过复杂的宏来实现的,代码读起来非常晦涩,这里我们换一种方法通过跟踪代码运行过程来验证它。 179 | 180 | 我们首先将端点设在kernel_init、do_initcalls、inet_init以及do_initcalls后面的random_int_secret_init,预期这四个断点会依次触发,从而可以间接验证fs_initcall(inet_init)确实将inet_init注册进了do_initcalls并被do_initcalls调用执行了。 181 | 182 | 在lab3目录下执行qemu -kernel ../../linux-src/arch/x86/boot/bzImage -initrd ../rootfs.img -s -S 183 | ``` 184 | shiyanlou:~/ $ cd LinuxKernel [14:08:18] 185 | shiyanlou:LinuxKernel/ $ git clone https://github.com/mengning/linuxnet.git 186 | \u6b63\u514b\u9686\u5230 'linuxnet'... 187 | remote: Enumerating objects: 175, done. 188 | remote: Counting objects: 100% (175/175), done. 189 | remote: Compressing objects: 100% (151/151), done. 190 | remote: Total 175 (delta 100), reused 47 (delta 21), pack-reused 0 191 | \u63a5\u6536\u5bf9\u8c61\u4e2d: 100% (175/175), 4.57 MiB | 2.58 MiB/s, done. 192 | \u5904\u7406 delta \u4e2d: 100% (100/100), done. 193 | \u68c0\u67e5\u8fde\u63a5... \u5b8c\u6210\u3002 194 | shiyanlou:LinuxKernel/ $ cd linuxnet/lab3 [14:08:38] 195 | shiyanlou:lab3/ (master) $ make rootfs [14:08:38] 196 | gcc -o init linktable.c menu.c main.c -m32 -static -lpthread 197 | find init | cpio -o -Hnewc |gzip -9 > ../rootfs.img 198 | 1889 \u5757 199 | qemu -kernel ../../linux-src/arch/x86/boot/bzImage -initrd ../rootfs.img 200 | shiyanlou:lab3/ (master*) $ qemu -kernel ../../linux-src/arch/x86/boot/bzImage -initrd ../rootfs.img -s -S 201 | ``` 202 | 在另一个窗口执行gdb并依次执行如下gdb命令: 203 | ``` 204 | (gdb) file ../../linux-src/vmlinux 205 | Reading symbols from ../../linux-src/vmlinux...done. 206 | (gdb) target remote:1234 207 | Remote debugging using :1234 208 | 0x0000fff0 in ?? () 209 | (gdb) b kernel_init 210 | Breakpoint 1 at 0xc1740240: file init/main.c, line 931. 211 | (gdb) b do_initcalls 212 | Breakpoint 2 at 0xc1a2fc2f: file init/main.c, line 851. 213 | (gdb) b inet_init 214 | Breakpoint 3 at 0xc1a76de3: file net/ipv4/af_inet.c, line 1675. 215 | (gdb) b random_int_secret_init 216 | Breakpoint 4 at 0xc132dbf0: file drivers/char/random.c, line 1712. 217 | ``` 218 | 这样我们就设置好了验证的系统环境,如图: 219 | ![](http://i2.51cto.com/images/blog/201812/04/fbc983529bb40b68d56968650a9ad166.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=) 220 | 221 | 依次按c让Linux内核从断点处继续执行,可以看到Linux内核依次断点在kernel_init、do_initcalls、inet_init以及do_initcalls后面的random_int_secret_init,如下输出信息与我们的预期是一致的,fs_initcall(inet_init)确实将inet_init注册进了do_initcalls并被do_initcalls调用执行了。 222 | ``` 223 | (gdb) c 224 | Continuing. 225 | 226 | Breakpoint 1, kernel_init (unused=0x0) at init/main.c:931 227 | 931 { 228 | (gdb) c 229 | Continuing. 230 | 231 | Breakpoint 2, kernel_init_freeable () at init/main.c:1004 232 | 1004 do_basic_setup(); 233 | (gdb) c 234 | Continuing. 235 | 236 | Breakpoint 3, inet_init () at net/ipv4/af_inet.c:1675 237 | 1675 { 238 | (gdb) c 239 | Continuing. 240 | 241 | Breakpoint 4, random_int_secret_init () at drivers/char/random.c:1712 242 | 1712 { 243 | (gdb) 244 | 245 | ``` 246 | 到这里我们就找到了Linux内核初始化TCP/IP的入口位置,即为[inet_init函数](https://github.com/torvalds/linux/blob/v5.4/net/ipv4/af_inet.c#1674)。 247 | -------------------------------------------------------------------------------- /doc/socketSourceCode.md: -------------------------------------------------------------------------------- 1 | # Socket接口对应的Linux内核系统调用处理代码分析 2 | 3 | 理解Linux内核中socket接口层的代码,找出112号系统调用socketcall的内核处理函数sys_socketcall,理解socket接口函数编号和对应的socket接口内核处理函数 4 | 通过前面构建MenuOS实验环境使得我们有方法跟踪socket接口通过系统调用进入内核代码,在我们的环境中socket接口通过[112号系统调用socketcall](http://codelab.shiyanlou.com/xref/linux-src/arch/x86/syscalls/syscall_32.tbl#111)进入内核的,具体系统调用的处理机制不是本专栏的重点,本专栏将重点放在网络部分的代码分析。 5 | 6 | # 112号系统调用socketcall的内核处理函数sys_socketcall 7 | 8 | 9 | 112号系统调用socketcall的内核处理函数为sys_socketcall,函数实现见[/linux-src/net/socket.c#2492](http://codelab.shiyanlou.com/xref/linux-src/net/socket.c#2492) ,我们摘录部分代码如下: 10 | 11 | ``` 12 | /* 13 | 2485 * System call vectors. 14 | 2486 * 15 | 2487 * Argument checking cleaned up. Saved 20% in size. 16 | 2488 * This function doesn't need to set the kernel lock because 17 | 2489 * it is set by the callees. 18 | 2490 */ 19 | 2491 20 | 2492SYSCALL_DEFINE2(socketcall, int, call, unsigned long __user *, args) 21 | 2493{ 22 | ... 23 | 2517 switch (call) { 24 | 2518 case SYS_SOCKET: 25 | 2519 err = sys_socket(a0, a1, a[2]); 26 | 2520 break; 27 | 2521 case SYS_BIND: 28 | 2522 err = sys_bind(a0, (struct sockaddr __user *)a1, a[2]); 29 | 2523 break; 30 | 2524 case SYS_CONNECT: 31 | 2525 err = sys_connect(a0, (struct sockaddr __user *)a1, a[2]); 32 | 2526 break; 33 | 2527 case SYS_LISTEN: 34 | 2528 err = sys_listen(a0, a1); 35 | 2529 break; 36 | 2530 case SYS_ACCEPT: 37 | 2531 err = sys_accept4(a0, (struct sockaddr __user *)a1, 38 | 2532 (int __user *)a[2], 0); 39 | 2533 break; 40 | 2534 case SYS_GETSOCKNAME: 41 | 2535 err = 42 | 2536 sys_getsockname(a0, (struct sockaddr __user *)a1, 43 | 2537 (int __user *)a[2]); 44 | 2538 break; 45 | 2539 case SYS_GETPEERNAME: 46 | 2540 err = 47 | 2541 sys_getpeername(a0, (struct sockaddr __user *)a1, 48 | 2542 (int __user *)a[2]); 49 | 2543 break; 50 | 2544 case SYS_SOCKETPAIR: 51 | 2545 err = sys_socketpair(a0, a1, a[2], (int __user *)a[3]); 52 | 2546 break; 53 | 2547 case SYS_SEND: 54 | 2548 err = sys_send(a0, (void __user *)a1, a[2], a[3]); 55 | 2549 break; 56 | 2550 case SYS_SENDTO: 57 | 2551 err = sys_sendto(a0, (void __user *)a1, a[2], a[3], 58 | 2552 (struct sockaddr __user *)a[4], a[5]); 59 | 2553 break; 60 | 2554 case SYS_RECV: 61 | 2555 err = sys_recv(a0, (void __user *)a1, a[2], a[3]); 62 | 2556 break; 63 | 2557 case SYS_RECVFROM: 64 | 2558 err = sys_recvfrom(a0, (void __user *)a1, a[2], a[3], 65 | 2559 (struct sockaddr __user *)a[4], 66 | 2560 (int __user *)a[5]); 67 | 2561 break; 68 | 2562 case SYS_SHUTDOWN: 69 | 2563 err = sys_shutdown(a0, a1); 70 | 2564 break; 71 | 2565 case SYS_SETSOCKOPT: 72 | 2566 err = sys_setsockopt(a0, a1, a[2], (char __user *)a[3], a[4]); 73 | 2567 break; 74 | 2568 case SYS_GETSOCKOPT: 75 | 2569 err = 76 | 2570 sys_getsockopt(a0, a1, a[2], (char __user *)a[3], 77 | 2571 (int __user *)a[4]); 78 | 2572 break; 79 | 2573 case SYS_SENDMSG: 80 | 2574 err = sys_sendmsg(a0, (struct msghdr __user *)a1, a[2]); 81 | 2575 break; 82 | 2576 case SYS_SENDMMSG: 83 | 2577 err = sys_sendmmsg(a0, (struct mmsghdr __user *)a1, a[2], a[3]); 84 | 2578 break; 85 | 2579 case SYS_RECVMSG: 86 | 2580 err = sys_recvmsg(a0, (struct msghdr __user *)a1, a[2]); 87 | 2581 break; 88 | 2582 case SYS_RECVMMSG: 89 | 2583 err = sys_recvmmsg(a0, (struct mmsghdr __user *)a1, a[2], a[3], 90 | 2584 (struct timespec __user *)a[4]); 91 | 2585 break; 92 | 2586 case SYS_ACCEPT4: 93 | 2587 err = sys_accept4(a0, (struct sockaddr __user *)a1, 94 | 2588 (int __user *)a[2], a[3]); 95 | 2589 break; 96 | 2590 default: 97 | 2591 err = -EINVAL; 98 | 2592 break; 99 | 2593 } 100 | 2594 return err; 101 | 2595} 102 | 2596 103 | ``` 104 | 105 | 在我们的实验环境中,socket接口的调用是通过给socket接口函数编号的方式通过112号系统调用来处理的。这些socket接口函数编号的宏定义见[/linux-src/include/uapi/linux/net.h#26](http://codelab.shiyanlou.com/xref/linux-src/include/uapi/linux/net.h#26) 106 | 107 | ``` 108 | 26#define SYS_SOCKET 1 /* sys_socket(2) */ 109 | 27#define SYS_BIND 2 /* sys_bind(2) */ 110 | 28#define SYS_CONNECT 3 /* sys_connect(2) */ 111 | 29#define SYS_LISTEN 4 /* sys_listen(2) */ 112 | 30#define SYS_ACCEPT 5 /* sys_accept(2) */ 113 | 31#define SYS_GETSOCKNAME 6 /* sys_getsockname(2) */ 114 | 32#define SYS_GETPEERNAME 7 /* sys_getpeername(2) */ 115 | 33#define SYS_SOCKETPAIR 8 /* sys_socketpair(2) */ 116 | 34#define SYS_SEND 9 /* sys_send(2) */ 117 | 35#define SYS_RECV 10 /* sys_recv(2) */ 118 | 36#define SYS_SENDTO 11 /* sys_sendto(2) */ 119 | 37#define SYS_RECVFROM 12 /* sys_recvfrom(2) */ 120 | 38#define SYS_SHUTDOWN 13 /* sys_shutdown(2) */ 121 | 39#define SYS_SETSOCKOPT 14 /* sys_setsockopt(2) */ 122 | 40#define SYS_GETSOCKOPT 15 /* sys_getsockopt(2) */ 123 | 41#define SYS_SENDMSG 16 /* sys_sendmsg(2) */ 124 | 42#define SYS_RECVMSG 17 /* sys_recvmsg(2) */ 125 | 43#define SYS_ACCEPT4 18 /* sys_accept4(2) */ 126 | 44#define SYS_RECVMMSG 19 /* sys_recvmmsg(2) */ 127 | 45#define SYS_SENDMMSG 20 /* sys_sendmmsg(2) */ 128 | ``` 129 | 130 | 接下来我们根据TCP server程序调用socket接口的顺序依次看一下socket、bind、listen、accept等socket接口的内核处理函数。 131 | 132 | # socket接口函数的内核处理函数sys_socket 133 | 134 | sys_socket内核处理函数见[/linux-src/net/socket.c#1377](http://codelab.shiyanlou.com/xref//linux-src/net/socket.c#1377) ,摘录其中的关键代码如下: 135 | 136 | ``` 137 | 1377SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol) 138 | 1378{ 139 | 1379 int retval; 140 | 1380 struct socket *sock; 141 | ... 142 | 1397 retval = sock_create(family, type, protocol, &sock); 143 | ... 144 | ``` 145 | 146 | socket接口函数主要作用是建立socket套接字描述符,Unix-like系统非常成功的设计是将一切都抽象为文件,socket套接字也是一种特殊的文件,sock_create内部就是使用文件系统中的数据结构inode为socket套接字分配了文件描述符。socket套接字与普通的文件在内部存储结构上是一致的,甚至文件描述符和套接字描述符是通用的,但是套接字和文件还是特殊之处,因此定义了结构体struct socket,struct socket的结构体定义见[/linux-src/include/linux/net.h#105](http://codelab.shiyanlou.com/xref/linux-src/include/linux/net.h#105),具体代码摘录如下: 147 | 148 | ``` 149 | 95/** 150 | 96 * struct socket - general BSD socket 151 | 97 * @state: socket state (%SS_CONNECTED, etc) 152 | 98 * @type: socket type (%SOCK_STREAM, etc) 153 | 99 * @flags: socket flags (%SOCK_ASYNC_NOSPACE, etc) 154 | 100 * @ops: protocol specific socket operations 155 | 101 * @file: File back pointer for gc 156 | 102 * @sk: internal networking protocol agnostic socket representation 157 | 103 * @wq: wait queue for several uses 158 | 104 */ 159 | 105struct socket { 160 | 106 socket_state state; 161 | 107 162 | 108 kmemcheck_bitfield_begin(type); 163 | 109 short type; 164 | 110 kmemcheck_bitfield_end(type); 165 | 111 166 | 112 unsigned long flags; 167 | 113 168 | 114 struct socket_wq __rcu *wq; 169 | 115 170 | 116 struct file *file; 171 | 117 struct sock *sk; 172 | 118 const struct proto_ops *ops; 173 | 119}; 174 | ``` 175 | 176 | sock_create内部还根据指定的网络协议族family和protocol初始化了相关协议的处理接口到结构体struct socket中,结构体struct socket在后续的分析和理解中还会用到,这里简单略过用到时再具体研究。 177 | 178 | # bind接口函数的内核处理函数sys_bind 179 | 180 | 内核处理函数sys_bind见[/linux-src/net/socket.c#1527](https://github.com/torvalds/linux/blob/v5.4/net/socket.c#1527),它的功能是绑定网络地址。 181 | 182 | ``` 183 | 1519/* 184 | 1520 * Bind a name to a socket. Nothing much to do here since it's 185 | 1521 * the protocol's responsibility to handle the local address. 186 | 1522 * 187 | 1523 * We move the socket address to kernel space before we call 188 | 1524 * the protocol layer (having also checked the address is ok). 189 | 1525 */ 190 | 1526 191 | 1527SYSCALL_DEFINE3(bind, int, fd, struct sockaddr __user *, umyaddr, int, addrlen) 192 | 1528{ 193 | 1529 struct socket *sock; 194 | 1530 struct sockaddr_storage address; 195 | 1531 int err, fput_needed; 196 | 1532 197 | 1533 sock = sockfd_lookup_light(fd, &err, &fput_needed); 198 | 1534 if (sock) { 199 | 1535 err = move_addr_to_kernel(umyaddr, addrlen, &address); 200 | 1536 if (err >= 0) { 201 | 1537 err = security_socket_bind(sock, 202 | 1538 (struct sockaddr *)&address, 203 | 1539 addrlen); 204 | 1540 if (!err) 205 | 1541 err = sock->ops->bind(sock, 206 | 1542 (struct sockaddr *) 207 | 1543 &address, addrlen); 208 | 1544 } 209 | 1545 fput_light(sock->file, fput_needed); 210 | 1546 } 211 | 1547 return err; 212 | 1548} 213 | ``` 214 | 215 | 如上代码可以看到,move_addr_to_kernel将用户态的struct sockaddr结构体数据拷贝到内核里的结构体变量struct sockaddr_storage address,然后使用sock->ops->bind将该网络地址绑定到之前创建的套接字。这里用到了通过套接字描述符fd找到之前分配的套接字struct socket *sock,利用该套接字中的成员const struct proto_ops *ops找到对应网络协议的bind函数指针即sock->ops->bind。这里即是一个socket接口层通往具体协议处理的接口。 216 | 217 | # listen接口函数的内核处理函数sys_listen 218 | 219 | 内核处理函数sys_listen见[/linux-src/net/socket.c#1556](https://github.com/torvalds/linux/blob/v5.4/net/socket.c#1556),具体代码如下: 220 | 221 | ``` 222 | 1550/* 223 | 1551 * Perform a listen. Basically, we allow the protocol to do anything 224 | 1552 * necessary for a listen, and if that works, we mark the socket as 225 | 1553 * ready for listening. 226 | 1554 */ 227 | 1555 228 | 1556SYSCALL_DEFINE2(listen, int, fd, int, backlog) 229 | 1557{ 230 | 1558 struct socket *sock; 231 | 1559 int err, fput_needed; 232 | 1560 int somaxconn; 233 | 1561 234 | 1562 sock = sockfd_lookup_light(fd, &err, &fput_needed); 235 | 1563 if (sock) { 236 | 1564 somaxconn = sock_net(sock->sk)->core.sysctl_somaxconn; 237 | 1565 if ((unsigned int)backlog > somaxconn) 238 | 1566 backlog = somaxconn; 239 | 1567 240 | 1568 err = security_socket_listen(sock, backlog); 241 | 1569 if (!err) 242 | 1570 err = sock->ops->listen(sock, backlog); 243 | 1571 244 | 1572 fput_light(sock->file, fput_needed); 245 | 1573 } 246 | 1574 return err; 247 | 1575} 248 | ``` 249 | 250 | listen接口的主要作用是通知网络底层开始监听套接字并接收网络连接请求,listen接口正常处理完TCP服务就已经启动了,只是这时网络连接请求都会暂存在缓冲区,等调用accept建立连接,listen接口函数的参数backlog就是用来配置支持的连接数。 251 | 252 | 我们发现实际处理的工作是由sock->ops->listen完成的,这也是一个socket接口层通往具体协议处理的接口。 253 | 254 | # accept接口函数的内核处理函数sys_accept 255 | 256 | 内核处理函数sys_accept的主要功能是调用sys_accept4完成的,sys_accept4见[/linux-src/net/socket.c#1589](https://github.com/torvalds/linux/blob/v5.4/net/socket.c#1589),具体代码摘录如下: 257 | 258 | ``` 259 | 1577/* 260 | 1578 * For accept, we attempt to create a new socket, set up the link 261 | 1579 * with the client, wake up the client, then return the new 262 | 1580 * connected fd. We collect the address of the connector in kernel 263 | 1581 * space and move it to user at the very end. This is unclean because 264 | 1582 * we open the socket then return an error. 265 | ... 266 | 1589SYSCALL_DEFINE4(accept4, int, fd, struct sockaddr __user *, upeer_sockaddr, 267 | 1590 int __user *, upeer_addrlen, int, flags) 268 | 1591{ 269 | ... 270 | 1608 newsock = sock_alloc(); 271 | ... 272 | 1612 newsock->type = sock->type; 273 | 1613 newsock->ops = sock->ops; 274 | ... 275 | 1621 newfd = get_unused_fd_flags(flags); 276 | ... 277 | 1627 newfile = sock_alloc_file(newsock, flags, sock->sk->sk_prot_creator->name); 278 | ... 279 | 1639 err = sock->ops->accept(sock, newsock, sock->file->f_flags); 280 | ... 281 | 1643 if (upeer_sockaddr) { 282 | 1644 if (newsock->ops->getname(newsock, (struct sockaddr *)&address, 283 | ... 284 | 1649 err = move_addr_to_user(&address, 285 | ... 286 | 1657 fd_install(newfd, newfile); 287 | 1658 err = newfd; 288 | ... 289 | 1668} 290 | ``` 291 | 292 | 在TCP的服务器端通过socket函数创建的套接字描述符只是用来监听客户连接请求,accept函数内部会为每一个请求连接的客户创建一个新的套接字描述符专门负责与该客户端进行网络通信,并将该客户的网络地址和端口等地址信息返回到用户态。这里涉及更多的网络协议处理的接口如sock->ops->accept、ewsock->ops->getname。 293 | 294 | send和recv接口的内核处理函数类似也是通过调用网络协议处理的接口来将具体的工作交给协议层来完成,比如sys_recv最终调用了sock->ops->recvmsg,sys_send最终调用了sock->ops->sendmsg,但send和recv接口涉及网络数据流,是理解网络部分的关键内容,我们后续部分专门具体研究。 295 | -------------------------------------------------------------------------------- /doc/netutilities.md: -------------------------------------------------------------------------------- 1 | # 通过网络命令学习计算机网络 2 | 3 | ## 配置Linux系统连接Internet 4 | 5 | 典型的情形是我们的Linux机器在一个局域网中,局域网中有一个网关,能够让我们的Linux机器访问Internet,那么我们就需要首先给我们的Linux机器上连接到网关的网卡上设置IP地址,然后将这台网关的IP地址设置为Linux机器的默认路由,也称为默认网关。 6 | 7 | ### 使用ifconfig设置网卡 8 | 9 | ifconfig是Linux系统中最常用的一个用来显示和设置网络设备的工具。其中“if”是“interface”的缩写。ifconfig可以用来设置网卡的状态,或是显示当前的设置。 10 | * 查看网卡的状态 11 | ``` 12 | ifconfig eth0 # 查看第一块网卡的状态 13 | ifconfig # 查看所有网卡的状态 14 | ``` 15 | * 将第一块网卡的IP地址设置为192.168.0.100 16 | ``` 17 | ifconfig eth0 192.168.0.100 18 | ``` 19 | 对于以太网网卡的默认命名方式为eth0、eth1... 20 | * 同时设置IP地址和子网掩码 21 | ``` 22 | ifconfig eth0 192.168.0.100 netmask 255.255.255.0 23 | ``` 24 | * 暂时关闭或启用网卡 25 | ``` 26 | ifconfig eth0 down # 关闭第一块以太网网卡 27 | ifconfig eth0 up # 启用第一块以太网网卡 28 | ``` 29 | 30 | ### 使用route设置默认网关 31 | 32 | route命令是用来查看和设置Linux系统的路由信息,以实现与其它网络的通讯。要实现两个不同的子网之间的网络通讯,需要一台连接两个网络路由器或者同时位于两个网络的网关来实现。这里的两个网络典型情况是:一个是我们的机器所在的局域网;另一个是外部的Internet。 33 | 34 | * 显示出当前路由表 35 | ``` 36 | route 37 | ``` 38 | * 增加一个默认路由 39 | ``` 40 | route add 192.168.0.1 gw 41 | ``` 42 | ### 配置DNS域名解析服务 43 | 44 | 我们的Linux系统实际上已经连接到Internet上了,只是我们只能通过IP地址来访问Internet上的网络服务。要想通过域名来来访问Internet上的网络服务,还需要配置DNS域名解析服务,也就是指定DNS服务器的IP地址。 45 | 46 | * 参看系统默认DNS配置 47 | ``` 48 | cat /etc/resolv.conf 49 | ``` 50 | * 配置系统默认DNS 51 | ``` 52 | vi /etc/resolv.conf 53 | nameserver 114.114.114.114 # 在/etc/resolv.conf文件里添加一行 54 | ``` 55 | 通过文本编辑器(这里以vi为例,您也可以使用其他方式)打开/etc/resolv.conf 配置文件,添加一行nameserver 114.114.114.114 ,这个DNS服务器IP地址一般使用网络管理员指定的本地DNS服务器IP地址。 56 | 57 | 至此,我们可以通过域名来来访问Internet上的网络服务,通过浏览器打开网页http://staff.ustc.edu.cn/~mengning/ 测试一下看看吧!如果您的Linux没有图形界面,也可以通过wget命令来下载一个网页。 58 | ``` 59 | wget http://staff.ustc.edu.cn/~mengning/ 60 | ``` 61 | 如果无法打开网页怎么办?在确保IP地址、默认网关和DNS配置正确的情况下,就需要通过网络诊断工具来跟踪网络发现问题。 62 | ### 网络故障排查方法 63 | * 网络故障排查的顺序 64 | 65 | 网络故障大体可以分为系统IP设置、网卡、网关和线路故障几个方面。正常情况下,当用户使用ping命令来检测网络运行情况和查找网络故障时,往往需要调用许多ping命令。按照一定的顺序规则使用该命令将快速找到问题所在,通常按照本地循环地址、本地IP地址、默认网关地址、远程IP地址、域名的顺序依次进行排查。 66 | 67 | * 常见返回信息 68 | 69 | 在使用ping命令进行网络故障排查时,经常会遇到各种各样的返回信息。了解每种信息表示的含义以及可能导致该信息产生的原因,将有助于用户有效地排查网络故障。下面对一些常见的返回信息进行简单的说明。 70 | 71 | * Request Timed Out 72 | 该信息表示没有收到对方主机的回应导致请求超时。出现这种信息的原因大体包括:目标机器设置了ICMP数据包过滤如设置了防火墙、目标机器已经关机、IP地址不正确以及网关设置错误等。 73 | * Destination Host Unreachable 74 | 该信息表示无法与主机建立连接或该主机根本就不存在。通常这种情况的产生是由于局域网中DHCP无法正常分配IP导致,也就是本机网络设置有问题。 75 | * Unknown Host 76 | 该信息表示未知主机,错误的域名或线路故障可能导致不能被域名服务器解析成IP,从而产生了该返回信息,这时应该关注DNS的配置是否有问题。 77 | * TTL Expired in Transit 78 | 该信息表示TTL过期。造成该问题的主要原因是在网络内部出现了路由循环从而致使数据包无法到达目的主机。 79 | * Bad IP Address 80 | 该信息表示IP地址本身就不存在或者DNS服务器无法解析该地址。 81 | ### ping命令举例 82 | * 测试某网站的连通性 83 | ``` 84 | ping www.163.com 85 | ping 114.114.114.114 86 | ``` 87 | * 指定ping的次数为10次 88 | ``` 89 | ping -c 10 -i 0.6 www.163.com 90 | ``` 91 | * 每隔0.6秒ping一次,一共ping 10次 92 | ``` 93 | ping -c 10 www.163.com 94 | ``` 95 | * 设置发送包的大小为1024 96 | ``` 97 | ping -s 1024 www.163.com 98 | ``` 99 | ## 网络诊断工具 100 | ### ping 101 | 102 | ping是潜水艇人员的专用术语,表示回应的声纳脉冲,在网络ping是一个十分好用的TCP/IP工具————PING(Packet Internet Grope)因特网包探索器。它的主要的功能是用来检测网络的连通情况和分析网络速度,网络通与不通,也叫时延,其值越大,速度越慢。ping发送一个ICMP回声请求消息给目的地并报告是否收到所希望的ICMP回声应答,是用来检查网络是否通畅或者网络连接速度的命令。 103 | 104 | ping命令通过使用TCP/IP协议下的ICMP子协议向目标主机(地址)发送一个回声请求数据包,要求目标主机收到请求后给予答复,从而判断本机与目标主机(地址)是否连通,网络响应时间的长短以及传送过程中数据包是否有所丢失。用户可以借助以上信息分析判断出网络中的故障,并找到针对性的解决方法。 105 | ### ping命令背后的ICMP协议 106 | 107 | 执行ping指令会使用ICMP传输协议,发出要求回应的信息,若远端主机的网络功能没有问题,就会回应该信息,因而得知该主机运作正常。所以,在进一步说明ping命令之前,我们不妨先了解下ICMP协议。 108 | 109 | 在IP通信中,经常有数据包到达不了对方的情况。原因是在通信途中的某处的一个路由器由于不能处理所有的数据包,就将数据包部分或全部丢弃了;或者虽然到达了目的地,但是由于某种原因服务器软件可能不能接收处理它。为了与错误发生的现场联络而飞过来的信鸽就是ICMP报文。在IP网络上,由于数据包被丢弃等原因,为了将必要的信息传递给发送方而诞生了ICMP协议。ICMP协议是为了辅助IP协议交换各种各样的控制信息,可以说ICMP协议分担了IP协议的一部分功能。这也是为什么TCP/IP的框图中一般讲ICMP协议和IP协议一起放在网络层的原因。 110 | 111 | ICMP协议有何功能? 112 | * 确认IP包是否成功到达目标地址 ,如果没有告知原因————差错报文; 113 | * 回应对方主机询问的一些问题,比如调查自己网络的子网掩码,取得对方机器的时间信息等,这其实就是包括ping命令的一些功能,主要是请求与响应报文。 114 | 115 | ![ICMP协议报文的结构](images/icmp-packet.png) 116 | 图:ICMP协议报文的结构(图片质量不高,可以考虑重画) 117 | 118 | 根据报文结构,我们来说说两种报文,先说说差错报文。 119 | 120 | 举一个例子,好比网购商家给另外一个城市的顾客寄送快递。现在快递服务顾客不满意,快递公司或顾客就用ICMP来反映快递寄送的一些情况给商家。常见情况如下: 121 | 122 | * Destination Host Unreachable终点不可达:比如我们主机的传输层写错了端口号,结果IP包送过去,到了对方的主机上,对方没有对应的端口接受,就像写错了门牌号快递被退回来了。发快递填写地址出错的情况就更多了,可能是门牌号,可能是小区号,可能干脆省份都写错了,查无此人,只能被退回来。类似的情况在网络就由ICMP汇报为什么送不到,用字段表示:网络不可达代码为0,主机不可达代码为1,协议不可达代码为2,端口不可达代码为3,需要进行分片但设置了不分片位代码为4,等等。 123 | * 源点抑制:这个就是说,快递送的太多了,拆快递都来不及了,叫我们这里送慢点,别一次性送这么多过来。 124 | * Request Timed Out时间超时:快递在路上耽搁太久,时间超时了,或者快递员直接偷了快递跑路了。也就是IP包超过时间(TTL)还没到达,为了不让它继续在网络中游荡堵塞网络,所以它就自行销毁了。 125 | * 路由重定向:是发给另外一个路由器的。也就是相当于,上次快递交给一个中转站,那个中转站的人莫名其妙去绕了远路(路由算法有问题),导致东西送到顾客手上很晚了,顾客投诉了,这次特地来告诉那个中转站的负责人,下次要找一条好走的路(重新更新路由表)。 126 | 127 | 说完了差错报文,现在来说说另外一个,也就是请求与响应报文。这就进入了我们今天的主题——ping命令。 128 | 129 | ping命令用来在IP层次上调查与指定机器是否连通,调查数据包发送和响应需要多少时间。为了实现这个功能,ping命令使用了两个ICMP报文。主机的ping发送一个ICMP请求报文,对方主机收到之后,也用ICMP发送一个响应报文,这样就能检测网络是否畅通,同时利用发送请求报文和接收响应报文之间的时间间隔来评估网络速度怎么样。就像我往山那头的人家喊一句,看什么时候有回声,如果始终没回声,那就是不在家。如果过了一会儿有回声,那么我们就可以根据回声的快慢来推断我们两人之间距离有多少。 130 | 131 | 具体来说,ping命令是怎么样使用ICMP协议的呢? 132 | * 我方主机先发送一个为ping量身打造的ICMP请求报文————增加了标识符和序号字段。这两个新增加的字段,是用来标识各个不同的ICMP报文。标识符是表示某一批送出的ICMP报文,同一批送出的都是一样的标识符。序号字段则是表示这是送出的第几个包,每送出一个就增加1。 133 | * 对方主机收到之后,就做了一点小小的工作,把源地址和目标地址交换了一下,然后再把类型字段改成0,表示这个是响应报文,然后就原封不动地送回来了。这就好像是我送了个快递到别人家,别人把地址交换了下,改成我是收件人,就直接叫快递员原封不动地退回来了。 134 | * 我方主机收到响应报文的时间减去发送请求报文的时间就可以计算出两个主机之间的网络延时。 135 | 136 | 137 | 138 | ## 网络管理 139 | 140 | ### iptables 141 | 简介toto 142 | ### iptable的基本原理 143 | iptables有5种chain: 144 | * PREROUTING:数据包进入路由表之前 145 | * INPUT:通过路由表后目的地为本机 146 | * FORWARDING:通过路由表后,目的地不为本机 147 | * OUTPUT:由本机产生,向外转发 148 | * POSTROUTIONG:发送到网卡接口之前。 149 | 150 | 将报文的处理过程大致分成三类,每类报文会经过不同的chain: 151 | 152 | * 到本机某进程的报文:PREROUTING --> INPUT 153 | * 由本机转发的报文:PREROUTING --> FORWARD --> POSTROUTING 154 | * 由本机的某进程发出报文(通常为响应报文):OUTPUT --> POSTROUTING 155 | 156 | ![iptables的5种chain和3类处理过程](images/iptables-chains.png) 157 | 图:iptables的5种chain和3类处理过程(图片需要修改,将“web服务终点/原点”改为“网络应用程序”) 158 | 159 | 为了方便管理,把具有相同功能的规则的集合叫做table,不同功能的规则,放置在不同的table中进行管理,iptables中定义了4种table。四种表的功能为 160 | * filter表:负责过滤功能,防火墙;内核模块:iptables_filter 161 | * nat表:network address translation,网络地址转换功能;内核模块:iptable_nat 162 | * mangle表:拆解报文,做出修改,并重新封装 的功能;iptable_mangle 163 | * raw表:关闭nat表上启用的连接追踪机制;iptable_raw 164 | 四种table处理顺序由先到后为:raw→mangle→nat→filter。但除了OUTPUT外有全部的四种表规则外,其他的chain只有两三个表规则。 165 | * PREROUTING :raw表,mangle表,nat表 166 | * INPUT :mangle表,filter表,(centos7中还有nat表,centos6中没有) 167 | * FORWARD:mangle表,filter表 168 | * OUTPUT:raw表,mangle表,nat表,filter表 169 | * POSTROUTING:mangle表,nat表 170 | 171 | ![iptables的5种chain和4种tables](images/iptables-tables.png) 172 | 图:iptables的5种chain和4种tables (图片来源于网络,考虑重新配色) 173 | ### iptables的基本用法 174 | todo 175 | ### iptables的典型应用举例(该部分未经实验验证) 176 | 177 | * 禁止别人ping入,以防浪费服务器资源 178 | ``` 179 | iptables -t filter -A INPUT -p icmp --icmp-type 0 -j ACCEPT 180 | iptables -t filter -A OUTPUT -p icmp --icmp-type 8 -j ACCEPT 181 | ``` 182 | * 丢弃eth0网卡收到来自 192.168.1.0/24 的 1024:65535 端口的访问本机 ssh 端口的数据包 183 | ``` 184 | iptables -A INPUT -i eth0 -p tcp -s 192.168.1.0/24 --sport 1024:65535 --dport ssh -j DROP 185 | ``` 186 | ## 网络测绘 187 | 188 | ### nmap 189 | 在著名的黑客专用操作系统KaliLinux中有很多非常强大的工具,其中就包括nmap。根据名字我们大致可以猜测一下,n可能代表network,那么难道它可以画出“网络地图”或者说“网络拓扑图” ?让我们先来看一下它的参数说明。 190 | 191 | 通过下面的命令查看nmap的使用方法 192 | ``` 193 | $ nmap --help 194 | 195 | Nmap 7.60 ( https://nmap.org ) 196 | Usage: nmap [Scan Type(s)] [Options] {target specification} 197 | TARGET SPECIFICATION: 198 | Can pass hostnames, IP addresses, networks, etc. 199 | Ex: scanme.nmap.org, microsoft.com/24, 192.168.0.1; 10.0.0-255.1-254 200 | -iL : Input from list of hosts/networks 201 | -iR : Choose random targets 202 | --exclude : Exclude hosts/networks 203 | --excludefile : Exclude list from file 204 | HOST DISCOVERY: 205 | -sL: List Scan - simply list targets to scan 206 | -sn: Ping Scan - disable port scan 207 | -Pn: Treat all hosts as online -- skip host discovery 208 | -PS/PA/PU/PY[portlist]: TCP SYN/ACK, UDP or SCTP discovery to given ports 209 | -PE/PP/PM: ICMP echo, timestamp, and netmask request discovery probes 210 | -PO[protocol list]: IP Protocol Ping 211 | -n/-R: Never do DNS resolution/Always resolve [default: sometimes] 212 | --dns-servers : Specify custom DNS servers 213 | --system-dns: Use OS's DNS resolver 214 | --traceroute: Trace hop path to each host 215 | SCAN TECHNIQUES: 216 | -sS/sT/sA/sW/sM: TCP SYN/Connect()/ACK/Window/Maimon scans 217 | -sU: UDP Scan 218 | -sN/sF/sX: TCP Null, FIN, and Xmas scans 219 | --scanflags : Customize TCP scan flags 220 | -sI : Idle scan 221 | -sY/sZ: SCTP INIT/COOKIE-ECHO scans 222 | -sO: IP protocol scan 223 | -b : FTP bounce scan 224 | PORT SPECIFICATION AND SCAN ORDER: 225 | -p : Only scan specified ports 226 | Ex: -p22; -p1-65535; -p U:53,111,137,T:21-25,80,139,8080,S:9 227 | --exclude-ports : Exclude the specified ports from scanning 228 | -F: Fast mode - Scan fewer ports than the default scan 229 | -r: Scan ports consecutively - don't randomize 230 | --top-ports : Scan most common ports 231 | --port-ratio : Scan ports more common than 232 | SERVICE/VERSION DETECTION: 233 | -sV: Probe open ports to determine service/version info 234 | --version-intensity : Set from 0 (light) to 9 (try all probes) 235 | --version-light: Limit to most likely probes (intensity 2) 236 | --version-all: Try every single probe (intensity 9) 237 | --version-trace: Show detailed version scan activity (for debugging) 238 | SCRIPT SCAN: 239 | -sC: equivalent to --script=default 240 | --script=: is a comma separated list of 241 | directories, script-files or script-categories 242 | --script-args=: provide arguments to scripts 243 | --script-args-file=filename: provide NSE script args in a file 244 | --script-trace: Show all data sent and received 245 | --script-updatedb: Update the script database. 246 | --script-help=: Show help about scripts. 247 | is a comma-separated list of script-files or 248 | script-categories. 249 | OS DETECTION: 250 | -O: Enable OS detection 251 | --osscan-limit: Limit OS detection to promising targets 252 | --osscan-guess: Guess OS more aggressively 253 | TIMING AND PERFORMANCE: 254 | Options which take