TCP/IP小白看源码

写在前面:
本文章是本人关于Linux内核网络协议栈的课程学习报告,关于网络协议栈源码本人理解很浅,初学,不建议大家参考学习,仅作为个人的阶段性记录。
关于个人的初学建议,如果仅仅的阅读源码,可以尝试使用一些在线linux源码网站进行阅读。本人是因为接受不了虚拟机的卡顿(cup太老了,明年再换)大部分时候使用以下几个网站,各有优缺点。
https://elixir.bootlin.com/linux/latest/source/net
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net?h=v6.7-rc5
当然 其实直接去下个也很简单。
步入正题:
TCP/IP协议栈源代码分析
1、inet_init是如何被调用的?从start_kernel到inet_init调用路径
inet_init函数是TCP/IP协议栈的初始化函数之一,定义位于net/ipv4/af_inet.c文件中。
在这里插入图片描述

  1. start_kernel 函数是内核的入口函数,它位于 init/main.c 文件中。在这个函数中,内核进行了一系列的初始化工作。

  2. start_kernel 函数中,会调用 rest_init 函数来创建一个内核线程,并将其指定为 kernel_init- 函数。

  3. kernel_init 函数位于 init/main.c 文件中,它是内核初始化的第一个用户空间进程。在这个函数中,会进行一系列的初始化工作,包括初始化进程调度器、初始化内存管理、初始化设备驱动等。

  4. kernel_init 函数中,会调用 do_basic_setup 函数,该函数位于 init/main.c 文件中。在 do_basic_setup 函数中,会进行一些基本的系统设置,包括初始化控制台、初始化定时器、初始化系统调用接口等。

  5. do_basic_setup 函数中,会调用 init_IRQ 函数,该函数位于 arch/x86/kernel/irq.c 文件中。在 init_IRQ 函数中,会初始化中断控制器和处理器的中断设置。

  6. init_IRQ 函数中,会调用 init_timers 函数,该函数位于 kernel/time/timer.c 文件中。在 init_timers 函数中,会初始化内核的定时器。

  7. init_timers 函数中,会调用 timekeeping_init 函数,该函数位于 kernel/time/timekeeping.c 文件中。在 timekeeping_init 函数中,会初始化内核的时间管理。

  8. timekeeping_init 函数中,会调用 inet_init 函数,该函数位于 net/ipv4/af_inet.c 文件中。在 inet_init 函数中,会进行 TCP/IP 协议栈的初始化工作,如注册套接字类型、注册协议处理函数等。

start_kernel -> kernel_init -> do_basic_setup -> init_IRQ -> init_timers -> timekeeping_init -> inet_init

2、跟踪分析TCP/IP协议栈如何将自己与上层套接口与下层数据链路层关联起来的?
在Linux内核网络协议栈中,TCP/IP协议栈通过套接字(Socket)接口与上层应用程序进行交互,同时通过网络设备驱动程序与下层数据链路层进行通信。
具体来说,TCP/IP协议栈与上层套接口的关联是通过套接字(Socket)实现的。套接字是应用程序与网络协议栈之间的接口,它提供了一组函数调用(和操作系统中的有点不一样),用于创建、连接、发送和接收数据等操作。应用程序通过调用套接字接口提供的函数来与TCP/IP协议栈进行通信。
而与下层数据链路层的关联是通过网络设备驱动程序实现的。网络设备驱动程序负责将TCP/IP协议栈中的数据包发送到物理网络设备上,或者从物理网络设备接收数据包并传递给TCP/IP协议栈。这样,TCP/IP协议栈就能够与下层数据链路层进行通信。
3、TCP的三次握手源代码跟踪分析,跟踪找出设置和发送SYN/ACK的位置,以及状态转换的位置
TCP的三次握手的相关代码在Linux内核源码的网络协议栈部分。具体来说,涉及到TCP的三次握手的代码位于以下文件中:net/ipv4/tcp_ipv4.c:这个文件包含了TCP协议的实现,其中包括了TCP的连接建立、断开等操作的代码。include/net/tcp.h:这个文件定义了TCP协议的数据结构和函数原型,包括了与TCP三次握手相关的结构体和函数。
在这里插入图片描述

//简化源码:
#include <linux/net.h>
#include <linux/tcp.h>

// TCP连接建立的函数
int tcp_v4_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len, int flags)
{
    // 创建一个TCP套接字
    struct sock *sk;
    sk = tcp_create_openreq_child(sk, &req, NULL);
    
    // 设置连接请求的目标地址和端口号
    tcp_set_state(sk, TCP_SYN_SENT);
    tcp_connect(sk);
    sk->sk_state_change(sk);
    
    // 发送SYN报文
    tcp_send_syn(sk, req->saddr, req->daddr, req->sport, req->dport);

    // 等待接收SYN/ACK报文
    tcp_wait_for_ack(sk);
    
    // 发送ACK报文
    tcp_send_ack(sk);
    
    // 进行状态转换
    tcp_set_state(sk, TCP_ESTABLISHED);
    
    return 0;
}
具体过程:

创建一个TCP套接字:使用tcp_create_openreq_child()函数创建一个TCP套接字。

设置连接请求的目标地址和端口号:使用tcp_set_state()函数将套接字状态设置为TCP_SYN_SENT,并调用tcp_connect()函数进行连接。

发送SYN报文:使用tcp_send_syn()函数向目标地址发送SYN报文。

等待接收SYN/ACK报文:使用tcp_wait_for_ack()函数等待接收SYN/ACK报文。

发送ACK报文:使用tcp_send_ack()函数向目标地址发送ACK报文。

进行状态转换:使用tcp_set_state()函数将套接字状态设置为TCP_ESTABLISHED,表示连接已建立。

# 导入必要的库
import socket

# 创建一个TCP套接字
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 绑定IP地址和端口号
sock.bind(('localhost', 8888))

# 监听连接
sock.listen(1)

# 接受客户端连接
conn, addr = sock.accept()

# 服务器发送SYN报文
conn.send(b'SYN')

# 服务器接收客户端的SYN报文
client_syn = conn.recv(1024)

# 服务器发送SYN/ACK报文
conn.send(b'SYN/ACK')

# 服务器接收客户端的ACK报文
client_ack = conn.recv(1024)

# 进行状态转换
# 设置连接状态为已建立
conn_status = 'ESTABLISHED'

# 关闭连接
conn.close()

# 关闭服务器套接字
sock.close()

在这些文件中,有TCP三次握手的具体实现细节,包括SYN和ACK标志的设置和发送,以及状态转换的过程。
4、send在TCP/IP协议栈中的执行路径
有点没看懂这个题目,是总体上各个层还是具体代码层层调用? 都写一下吧。
与send函数相关的代码主要位于net/socket.c文件中,具体实现可以在sock_sendmsg函数和各个协议族的发送函数中找到。
分层路径:
应用层调用send函数,将数据发送给操作系统内核。
传输层将数据封装为TCP报文段,并添加TCP首部信息。
网络层将TCP报文段封装为IP数据报,并添加IP首部信息。
数据链路层将IP数据报封装为帧,并添加帧首部信息。
帧通过物理接口发送到目标主机。
目标主机的物理接口接收到帧,并将其传递给数据链路层。
数据链路层解析帧首部信息,并将IP数据报提取出来。
网络层解析IP首部信息,并将TCP报文段提取出来。
传输层解析TCP首部信息,并将数据传递给应用层,完成数据的接收过程。

5、recv在TCP/IP协议栈中的执行路径
recv函数的执行路径与send函数类似
应用层: 应用程序调用recv()函数,开始接收数据。
系统调用层: recv()函数被内核接管后,首先调用sys_recv()函数。
套接字层: sys_recv()函数进一步处理后,由网络层的sock_recvmsg()函数接收。
传输层: sock_recvmsg()函数将调用inet_recvmsg()或udp_recvmsg()等传输层的接收函数。
TCP层: 最终,这个函数将调用tcp_recvmsg()或tcp_pullupdata()等TCP层的接收函数。
与recv函数相关的代码主要位于net/socket.c文件中
6、路由表的结构和初始化过程
相关源码在net/ipv4/route.c
408经典知识点
路由表结构:
Linux系统中的路由表使用一个叫做"路由缓存"(Route Cache)的结构来管理路由信息。每个路由缓存项包含以下字段:
目标地址(Destination Address):表示目标网络的IP地址和子网掩码。
网关(Gateway):表示下一跳的IP地址,即数据包在到达目标网络之前需要经过的下一跳路由器。
接口(Interface):表示数据包从本地主机发出时,通过的网络接口。
标志(Flags):用于指示路由缓存项的状态和属性,如是否可用、是否需要重新计算等。
王道讲的好像是如下:
目标网络 子网掩码 网关 接口
10.0.0.0 255.0.0.0 0.0.0.0 eth0
192.168.1.0 255.255.255.0 0.0.0.0 wlan0
0.0.0.0 0.0.0.0 192.168.1.1 wlan0
路由表初始化过程:
启动时,内核会初始化一个空的路由缓存表
在系统运行时,当网络配置发生变化(如添加、删除、修改网络接口、路由器等)时,内核会根据配置信息更新路由缓存表。
当数据包需要进行路由选择时,内核会根据目标地址在路由缓存表中查找匹配的路由缓存项。
如果找到匹配的路由缓存项,则根据其中的网关和接口信息进行下一跳的选择。
如果没有找到匹配的路由缓存项,则需要进行路由查找。此时,内核会根据路由表中的规则和策略来选择最佳的路由。
如果找到最佳路由,则会将该路由信息添加到路由缓存表中,以便下次快速查找。
7、通过目的IP查询路由表的到下一跳的IP地址的过程
获取目的IP地址
遍历路由表中的每个路由表项。
对于每个路由表项,将目的IP地址与路由表项的目标网络地址进行逻辑与操作,使用子网掩码来确定网络范围是否匹配。
如果目的IP地址与某个路由表项的目标网络地址匹配,则找到了匹配的路由表项。
检查匹配的路由表项中的网关字段。如果网关段为0.0.0.0,则表示该路由表项为直接连接路由,下一跳的IP地址即为目的IP地址本身。
如果网关字段不为0.0.0.0,则下一跳的IP地址为网关字段的值。
返回下一跳的IP地址。
8、ARP缓存的数据结构及初始化过程,包括ARP缓存的初始化
net/ipv4/arp.c 源码中可以查看过程:

结构:

struct etharp_entry {
#if ARP_QUEUEING
  /** 指向此ARP表项上挂起的数据包队列的指针. */
  struct etharp_q_entry *q;
#else /* ARP_QUEUEING */
  /** 指向此ARP表项上的单个挂起数据包的指针. */
  struct pbuf *q;
#endif
  ip4_addr_t ipaddr; //记录目标IP地址
  struct netif *netif; //对应网卡信息
  struct eth_addr ethaddr; //记录与目标IP地址对应的MAC地址
  u16_t ctime; //生存时间
  u8_t state; //表项的状态
};

ARP缓存初始化过程:
启动时,内核会初始化一个空的ARP缓存表。
当主机或路由器需要发送数据包到目标设备时,首先会检查ARP缓存表中是否存在目标设备的IP地址和对应的MAC地址映射关系。
如果在ARP缓存表中找到了匹配的映射关系,则直接使用该MAC地址发送数据包。
如果在ARP缓存表中没有找到匹配的映射关系,则需要进行ARP请求。
ARP请求是一种广播消息,主机或路由器会向本地网络上的所有设备发送ARP请求,询问目标设备的MAC地址。
目标设备收到ARP请求后,会在ARP响应中回复自己的MAC地址。
主机或路由器收到ARP响应后,将目标设备的IP地址和MAC地址映射关系添加到ARP缓存表中。
ARP缓存表中的每个条目都有一个过期时间,内核会定期检查ARP缓存表中的条目是否过期,并删除过期的条目。
9、如何将IP地址解析出对应的MAC地址
通过ARP协议,源码还是在net/ipv4/arp.c
步骤大体如下:
创建一个socket,使用AF_INET和SOCK_DGRAM参数来创建一个UDP socket。
设置目标IP地址,将IP地址转换为网络字节序,并将其保存在arpreq结构体的arp_pa字段中。
调用ioctl函数,使用SIOCGARP命令发送ARP请求,并将结果保存在arpreq结构体中。
从arpreq结构体中获取MAC地址,并使用ether_ntoa函数将其转换为字符串格式进行打印。
关闭socket。

10、跟踪TCP send过程中的路由查询和ARP解析的最底层实现
在这里插入图片描述
通过上面这个函数
ARP就这几个具体函数:
arp_rcv():接收和处理接收到的ARP请求和响应数据包。
arp_send():发送ARP请求或响应数据包。
arp_create():创建和初始化ARP请求或响应数据包。
arp_find():在ARP缓存中查找指定的IP地址对应的MAC地址。
arp_tbl:ARP缓存表的数据结构,存储了IP地址和对应的MAC地址信息。
在这里插入图片描述
果路由查询成功,且目的地址在本地网络上,接下来需要确定目的地址的MAC地址。这是通过ARP协议完成的。
ARP请求由 neigh_resolve_output发起,如果目的MAC地址不在ARP缓存中,将广播收到ARP响应后,MAC地址被存储在ARP缓存中,用于实际的数据包传输。ARP请求。

课程感想:
首先,学习套接字编程让我对网络通信有了更深刻的理解。我学会了如何使用套接字进行网络通信,包括创建套接字、绑定地址、监听连接、接受连接、发送和接收数据等。通过实际编写套接字程序,我深刻体会到了网络通信的过程和细节,如数据的分割和重组、错误处理和异常情况处理等。这让我对网络编程的实际应用有了更清晰的认识
其次,学习TCP/IP协议栈源码让我对网络协议有了更深入的了解。通过分析TCP/IP协议栈的源码,我了解了协议栈中各个层次的功能和交互过程。我深入研究了IP地址解析为MAC地址的过程,了解了ARP协议的实现细节。这让我对网络协议的工作原理有了更直观的认识,并且对网络通信中的一些常见问题和调试方法有了更深入的了解。
孟宁老师授课方式也相当值得称赞。

  • 24
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值