内核版本采用4.9.88
套接字分析
系统入口要求:每个操作系统都必须提供网络子系统入口及API,Linux内核网络子系统提供标准的POSIX套接字API接口
用户空间特性:在Linux中传输层之上的一切都属于用户空间,遵循Unix"一切皆为文件"范式
文件关联性:套接字与文件相关联,使用统一API使应用程序移植更容易
1)套接字类型详解
SOCK_STREAM流套接字
通信特性:提供可靠的字节流通信信道
典型协议:TCP套接字属于流套接字类型
可靠性表现:保证数据按顺序到达且不重复
SOCK_DGRAM数据报套接字
消息交换:支持以数据包为单位的消息交换
不可靠特性:通信信道不可靠,可能出现丢包、乱序或重复
典型协议:UDP套接字属于此类型
风险说明:数据可能被丢弃、不按序到达或重复传输
SOCK_RAW原始套接字
访问层级:直接访问IP层,绕过传输层协议
协议无关性:支持使用协议无关的传输层格式收发数据流
特殊用途:常用于网络协议开发和分析
SOCK_RDM可靠消息套接字
核心特性:提供可靠传输的消息服务
应用场景:主要用于透明进程间通信(TIPC)
开发背景:最初由爱立信公司开发用于执行应用程序
SOCK_SEQPACKET顺序数据包
连接特性:面向连接,类似SOCK_STREAM
边界维护:独特之处在于维护记录边界
识别方式:接收方可通过MSG_EOR标志确定边界
SOCK_DCCP数据报拥塞控制
协议定位:传输层协议,兼具TCP和UDP特点
核心功能:提供不可靠数据报的拥塞控制流
设计目标:解决数据报传输中的拥塞控制问题
1)套接字API与内核方法
内核映射关系:应用层套接字API在内核中对应net/socket.c文件中的系统调用实现
调用机制:通过SYSCALL_DEFINE2宏定义系统调用,如socketcall处理所有套接字相关操作
参数传递:使用unsigned long数组存储用户空间传递的参数,通过copy_from_user安全拷贝
2)socket.c文件与内核函数定义
文件位置:内核源码中位于net/socket.c目录下
函数前缀:所有套接字系统调用函数都以sys_开头,如sys_socket、sys_bind等
调用流程:通过switch-case结构分发不同的套接字操作请求
需阅读源码部分:
3)套接字类型与功能描述
SOCK_STREAM:提供可靠的字节流通信,TCP套接字属于此类型
SOCK_DGRAM:支持消息交换但不可靠,UDP套接字属于此类型
SOCK_RAW:直接访问IP层,支持协议无关的传输层数据收发
SOCK_RDM:用于透明进程间通信(TIPC)
SOCK_SEQPACKET:面向连接的顺序数据包流,类似SOCK_STREAM
SOCK_DCCP:数据报拥塞控制协议,提供不可靠数据报拥塞控制流
4)套接字操作函数
socket():创建套接字,类型由参数决定
bind():将套接字与本地端口和IP地址关联
connect():建立到对等套接字的连接,适用于SOCK_STREAM和SOCK_SEQPACKET类型
listen():使套接字能够接收连接请求,不适用于数据报套接字
accept():接收套接字连接请求,仅适用于基于连接的套接字类型
send()/recv():分别对应消息发送和接收,内核实现为sys_send和sys_recv
实现机制:每个应用层API调用对应内核中的sys_前缀函数
参数处理:通过a0,a1等寄存器传递参数,进行安全检查后调用具体实现
错误处理:返回负值表示错误,如-EINVAL表示无效参数
3. 传输控制协议
1)内核中的套接字结构
双结构表示:内核中有两个表示套接字的结构体,分别是socket和sock
源码位置:位于include/linux/net.h文件中,从第104行开始定义
功能分工:
socket:面向用户空间提供接口
sock:面向网络层(L3)提供接口
2)socket结构的主要成员
核心成员:
state:套接字状态(如SS_CONNECTED等)
type:套接字类型(如SOCK_STREAM等)
flags:套接字标志(如SOCK_NOSPACE等)
ops:协议特定的套接字操作,包含套接字回调函数的proto_ops结构
file:文件反向指针用于垃圾回收,关联的文件结构指针
sk:内部网络协议无关的套接字表示
wq:多种用途的等待队列
源码阅读:
3)sock结构的网络层位置
协议无关性:sock结构位于网络层,是与协议无关的结构
创建关联:创建套接字时会同时创建关联的sock对象
示例:在IPv4中,调用inet_create方法时会分配sock对象并关联到对应套接字
4)socket与sock的功能区别
socket功能:
面向用户空间提供接口
由sys_socket()方法创建
sock功能:
面向网络层(L3)提供接口
协议无关的结构体
包含网络层相关操作和数据
5)socket状态及其取值
状态枚举:
SS_FREE = 0:未分配状态
SS_UNCONNECTED:未连接任何套接字
SS_CONNECTING:正在连接过程中
SS_CONNECTED:已连接到套接字
SS_DISCONNECTING:正在断开连接
源码阅读:
典型场景:
刚创建的INET套接字状态为SS_UNCONNECTED
流套接字成功连接后状态变为SS_CONNECTED
6)socket类型及其枚举类型
类型定义:
SOCK_STREAM:流式套接字
SOCK_RAW:原始套接字
类型特性:
使用kmemcheck_bitfield_begin/end宏进行类型检查
类型值存储在short类型的变量中
7)socket标志与分配方式
标志作用:表示套接字的特殊属性和状态
分配方式:
非系统调用socket分配时直接设置核心标志
可通过通用try_open方法进行打开操作
8)socket相关对象与文件
关联文件:
file指针:与套接字关联的文件对象
关联对象:
sk指针:与套接字关联的sock对象
提供网络层接口功能
9)socket操作与回调函数
操作集合:
包含connect、listen、sendmsg、recvmsg等回调函数
实现用户空间接口的多个库级例程
协议特定:
每种协议需要自定义proto_ops结构
不是随意定义的固定结构
典型回调:
sendmsg实现send、sendto、sendmsg等功能
recvmsg实现read、recv、recvfrom、recvmsg等功能
4. 套接字在网络层中的表示
1)套接字结构
核心结构:sock结构体是套接字的网络层表示,位于内核源码的include/net/sock.h文件中
源码阅读位置:建议整个结构体内容都有所了解
重要成员:
sk_common:包含套接字的基础公共成员
sk_lock:套接字锁
sk_drops:数据包丢弃计数器
sk_rcvlowat:接收低水位标记
sk_error_queue:错误队列头
sk_receive_queue:接收队列头
实现特点:
结构体实现代码相当长,包含套接字的所有网络层属性
与socket结构体相关联,通过struct sock *sk指针连接
包含数据包队列存储、接收缓冲区大小、各种标志等关键信息
2)回调函数
核心成员:
sk_receive_queue:存储入站数据包的队列
sk_write_queue:存储出站数据包的队列
sk_rcvbuf:接收缓冲区大小(单位:字节)
sk_sndbuf:发送缓冲区大小(单位:字节)
sk_protocol:协议标识符(8位)
sk_type:套接字类型(16位)
关键回调:
sk_data_ready:通知套接字有新数据到达的回调函数
sk_write_space:指出可用内存处理数据传输的回调函数
其他特性:
sk_no_check_tx:禁用发送校验和标志
sk_no_check_rx:禁用接收校验和标志
4)系统调用socket参数与返回值
参数说明:
socket_family:地址族(AF_INET/AF_INET6)
socket_type:套接字类型(SOCK_STREAM/SOCK_DGRAM/SOCK_RAW)
protocol:协议类型(IPPROTO_TCP/IPPROTO_UDP)
返回值:
返回文件描述符sockfd,用于后续套接字操作
内核内部调用sys_socket进行处理
5)msghdr结构体
核心成员:
msg_name:目标套接字地址指针,可转换为sockaddr_in结构
msg_namelen:地址长度
msg_iter:数据块迭代器
msg_control:控制信息(辅助数据)指针
msg_controllen:控制信息长度
msg_flags:接收消息的标志位
源码阅读位置:
使用场景:
用于用户空间套接字发送/接收数据
通过sendmsg/recvmsg系统调用处理数据传输
包含完整的数据块和控制信息
用户数据包协议
1. UDP报头内核源码
msg_name字段:指向目标套接字地址的指针,可转换为指向结构sockaddr_in的指针
msg_namelen字段:表示地址长度
msg_control字段:用于存储控制信息(辅助数据)
msg_flags字段:接收到的消息标志位
cmsg_len字段:数据字节计数,包括头部信息
cmsg_level字段:标识原始协议
cmsg_type字段:协议特定类型
1)UDP数据包协议
source字段:16位源端口号,取值范围1-65535
dest字段:16位目的端口号,取值范围1-65535
len字段:表示有效负载和UDP报头的总长度,单位为字节
check字段:数据包的校验和,用于验证数据完整性
源码阅读:
2)UDP初始化操作
初始化操作定义对象并使用方法
初始化对象:通过定义udp_protocol(net_protocol对象)实现
添加方法:使用inet_add_protocol()函数进行注册
关键成员:
early_demux:设置为udp_v4_early_demux
handler:设置为udp_rcv处理函数
err_handler:设置为udp_err错误处理函数
no_policy:设置为1表示无策略
netns_ok:设置为1表示支持网络命名空间
源码阅读:
2. 套接字的网络层表示
数据结构:内核中使用struct sock结构体表示套接字
接收队列:sk_receive_queue存储入站数据包
缓冲区大小:sk_rcvbuf表示接收缓冲区大小(字节),sk_sndbuf表示发送缓冲区大小
发送队列:sk_write_queue存储出站数据包
协议标识:sk_protocol字段标识协议类型
回调函数:sk_data_ready通知新数据到达,sk_write_space指示可用内存
系统调用:
socket()参数:
socket_family:AF_INET/AF_INET6
socket_type:SOCK_STREAM/SOCK_DGRAM/SOCK_RAW
protocol:IPPROTO_TCP/IPPROTO_UDP
返回值:返回文件描述符sockfd用于后续操作
数据传输:通过sendmsg()/recvmsg()处理,使用msghdr对象封装数据
3. UDP初始化机制
1)协议注册
核心结构:通过struct net_protocol udp_protocol注册
处理函数:handler=udp_rcv处理接收数据
错误处理:err_handler=udp_err处理错误
特性标志:no_policy=1表示无策略检查,netns_ok=1支持网络命名空间
初始化流程:
注册时机:系统启动时通过inet_init_net()初始化
端口范围:默认本地端口范围32768-60999
统计信息:为CPU分配统计数据结构(ip_statistics等)
2)网络层初始化
关键函数:inet_init_net()执行初始化
端口锁定:使用seqlock_init初始化ip_local_ports锁
Ping控制:设置ping_group_range限制ping权限
默认参数:
TTL值:IPDEFTTL
动态地址:sysctl_ip_dynaddr=0
早期解复用:对TCP/UDP启用(值=1)
源码阅读:
实现细节:
多协议支持:通过inet_add_protocol()注册ICMP/UDP/TCP
错误处理:失败时输出pr_crit级别日志
回调机制:如udp_sendmsg()处理UDP数据发送
源码阅读:这个挺重要的,略微重点阅读
用户数据包协议内核源码
1)接收L3的UDP数据包
方法udp_rcv()介绍
主处理程序:udp_rcv()是Linux内核中负责接收来自网络层(L3)的UDP数据包的核心函数
调用位置:位于net/ipv4/udp.c文件中,函数签名为int udp_rcv(struct sk_buff *skb)
嵌套调用:实际工作通过调用udp4_lib_rcv(skb, &udp_table, IPPROTO_UDP)完成,传入UDP协议表和处理协议类型
udp_rcv()处理流程
初始化阶段:
获取socket结构体指针struct sock *sk
解析UDP头部struct udphdr *uh获取报文长度ulen
获取路由表项struct rtable *rt = skb_rtable(skb)
数据包验证:
检查是否有足够空间存放UDP头部:pskb_may_pull(skb, sizeof(struct udphdr))
验证报文长度是否合法:ulen > skb->len时跳转到short_packet处理
初始化校验和:udp4_csum_init(skb, uh, proto)
套接字查找:
使用skb_steal_sock(skb)尝试获取关联的socket
通过__udp4_lib_lookup_skb()在UDP哈希表中查找匹配的套接字
数据包验证与 处理
匹配处理:
找到匹配套接字时:调用udp_queue_rcv_skb(sk, skb)将数据包加入接收队列
未找到匹配时:检查校验和是否正确,错误则直接丢弃数据包
异常处理:
广播/组播数据包:调用__udp4_lib_mcast_deliver()处理
端口不可达情况:发送ICMP"目的不可达"响应,更新SNMP计数器
资源释放:
最终通过kfree_skb()释放SKB缓冲区
更新内存统计信息:atomic_sub(size, &sk->sk_rmem_alloc)
完整流程:
入口函数udp_rcv()接收L3数据包
调用__udp4_lib_rcv()进行核心处理
检查是否为组播数据(是则特殊处理)
在UDP哈希表中查找匹配套接字
找到匹配则入队,否则校验后发送ICMP响应
最终释放数据包资源
TCP分析
1. TCP报头结构主要成员
源端口与目的端口:
source:均为16位长度,取值范围1-65535
dest:源端口对应应用层写的源端口,目的端口标识目标服务
序列号与确认号:
seq:序列号(seq)为32位,用于数据包排序
ack_seq:确认号(ack_seq)为32位,当设置ACK标志时,表示期望收到的下一个数据包序列号
保留字段:
res1:4位长度,必须设置为0,为未来协议扩展保留
数据偏移量:
doff:4位长度,以4字节为单位表示TCP头长度
最小值为5(20字节),最大为15(60字节)
控制标志位:
FIN(1位):发送方数据发送完毕,用于关闭连接
SYN(1位):用于三次握手建立连接
RST(1位):收到非当前连接数据时使用
PSH(1位):要求尽快将数据交付用户空间
ACK(1位):确认号字段有效
URG(1位):紧急指针字段有效
ECE(1位):显式拥塞通知,提供网络拥塞反馈
CWR(1位):拥塞窗口缩小标志
窗口大小:
window:16位长度,表示接收窗口大小(字节单位)
校验和:
check:包含TCP头和数据的校验值
紧急指针:
urg_ptr:16位长度,仅当URG标志设置时有效
表示紧急数据相对于序列号的偏移量
源码阅读:
TCP初始化操作
核心结构体:
struct net_protocol:定义传输层协议
包含ICMP/IGMP协议处理
桥接网络层和传输层的报文接收流程
源码阅读:
关键成员:
handler:数据包接收处理函数指针
err_handler:错误报文处理函数
no_policy:安全策略标志
初始化流程:
1.定义tcp_protocol对象
2.通过inet_add_protocol()注册协议
3.内核根据协议类型(TCP/UDP/ICMP)调用对应处理函数
4.返回值<0表示注册失败
源码位置:
位于net/ipv4/目录下
TCP实现文件为tcp_ipv4.c
错误处理:
当inet_add_protocol()返回错误时
内核会输出协议添加失败信息
TCP定时器
1)定时器实现位置
文件路径: 位于Linux内核的net/ipv4/tcptimer.c文件中
查找方法: 通过内核源码目录结构可快速定位,IPv4相关实现都在net/ipv4/目录下
2)重传定时器
核心功能: 负责在指定时间内未得到确认的数据包重传
触发条件:
数据包丢失或损坏
每次数据段发送后自动启动
终止机制: 定时器到期后若仍未收到确认则取消定时器
3)延迟确认定时器
作用原理: 推迟发送确认数据包
适用场景: 当TCP收到需要确认但无需立即确认的数据时启用
4)存活定时器
检测功能: 检查连接是否已断开
应用场景: 连接长时间空闲时检测对方状态
处理机制: 通过tcpsendactivereset()函数重置连接
5)零窗口探测定时器
别名: 持续定时器(persist timer)
工作流程:
接收方缓冲区满时通告零窗口
发送方停止发送数据
若包含新窗口大小的数据段丢失,定时器定期探测窗口状态
终止条件: 当探测到接收方窗口大小不为零时停止
TCP初始化
1)初始化入口
用户空间调用: 创建SOCK_STREAM类型套接字
内核处理函数:
系统调用入口为sys_socket()
实际回调函数为tcp_v4_init_sock()(IPv6对应tcp_v6_init_sock)
2)主要初始化任务
状态设置
初始状态: 设置为TCP_CLOSE状态
定时器初始化
调用函数:tcp_init_xmit_timers()
作用: 初始化所有TCP定时器模块
缓冲区设置
发送缓冲区:sk−>sk_sndbuf默认16,384字节
接收缓冲区:sk−>sk_rcvbuf默认87,380字节
队列初始化
无序队列: 处理非常规数据包
有序队列: 维护正常数据流顺序
参数初始化
包含内容: TCP头部各字段默认值初始化
典型值: 窗口大小初始化为10等基础参数
源码阅读:
TCP的连接和拆除
1)状态转换机制
监听状态: 调用listen()后进入TCP_LISTEN状态
状态标识: 通过sk−>skstate成员变量表示
2)三次握手过程
第一次握手
客户端动作: 发送SYN请求
状态转换: 进入TCP_SYN_SENT状态
第二次握手
服务端响应:
创建TCP_SYN_RECV状态套接字
返回SYN_ACK应答
第三次握手
客户端确认:
收到SYN_ACK后进入TCP_ESTABLISHED状态
发送最终ACK确认
连接建立
服务端最终状态: 收到ACK后改为TCP_ESTABLISHED状态
数据传输: 连接建立完成后即可开始数据传输
3)数据包接收处理
核心函数:tcp_v4_rcv()
源码阅读:
处理流程:
完整性检查(包类型、长度等)
初始化工作
调用__inet_lookup_skb()查找匹配套接字
异常处理: 检查失败时直接丢弃数据包
初始化相关问题
1. skb分析
1)数据包丢弃条件
非本地数据包处理:当数据包不是发送或发往本地的包时,系统会直接丢弃(discard)该数据包
掩码执行位置:丢弃操作通过1612行的掩码进行处理
2)TCP头部验证
长度校验:通过PS x/PS KB脉破函数检查包长是否大于TCP头长度
头部提取:使用th指针取得TCP首部,通过1612行掩码处理
偏移量验证:检查TCP首部长度与偏移量字段(offset)是否匹配
2. 接收TCP数据包过程
1)数据包查找流程
socket查找:通过inet_look_up函数查找匹配的socket,若找不到则丢弃数据包
状态检查:检查socket是否处于半关闭状态(half-closed)
规则验证:依次检查IPsec规则、MD5哈希校验、BPF校验规则
2)用户态处理
进程锁定:检查是否有用户态进程对socket进行锁定
状态保护:当socket被锁定时,其状态不可更改
队列处理:被锁定的数据包会进入后备处理队列,相关进程进入套接字后备等待队列
3. 发送TCP数据包过程
1)发送初始化
系统调用:通过send/sendto/sendmsg等系统调用触发
核心函数:最终由tcp_sendmsg函数处理,将用户空间数据复制到内核空间
状态检查:验证socket是否处于ESTABLISHED或CLOSE_WAIT状态
2)数据复制流程
内存分配:分配SKB缓冲区对象存储用户数据
循环拷贝:通过while循环控制所有用户数据块到内核空间的拷贝
队列管理:操作发送队列(双向链表)的尾节点进行数据添加
3)错误处理机制
do_error处理:当部分数据已复制时,仍发送已复制的数据
wait_for处理:
snd_buff:发送队列数据达到缓冲区上限时触发
memory:系统内存不足时触发
超时处理:内存分配超时跳转到错误处理流程
4)标志位说明
MSG_MORE:表示本次发送没有后续数据
TCP_NODELAY:禁用Nagle算法立即发送数据
SK_MEM_QUERY:内存配额检查标志
总结
1. 套接字分析
核心概念:套接字(Socket)是网络通信的基础,提供进程间通信的端点
工作机制:通过IP地址和端口号的组合实现不同主机间的数据传输
主要功能:建立连接、数据传输、连接释放等网络通信全过程管理
2. 用户数据包协议(UDP)
协议特点:无连接、不可靠但高效的传输协议
数据单元:以独立的数据包形式传输,每个包包含完整的目标地址信息
适用场景:适用于实时性要求高但允许少量丢包的应用,如视频会议、在线游戏等
3. 传输控制协议(TCP)
协议特点:面向连接、可靠传输的协议
工作机制:通过三次握手建立连接,四次挥手释放连接
可靠性保障:采用确认应答、超时重传、流量控制等机制确保数据完整有序传输
适用场景:适用于要求数据完整性的应用,如文件传输、网页浏览等