一. 前言
一直很好奇socket是如何实现的,底层的数据结构又是如何,因此在这里对socket的数据结构进行分析。
socket是传输层使用的数据结构,用于声明、定义套接字,网络层会调用sock结构体,其中sock会用到了通用sock_common结构体。而sk_buff则是内核中使用的套接字缓冲区结构体。在我们前文提到的NAT转换中,除了修改内核已有的Netfilter源码外,还有一种方式就是自己建立新的钩子函数,独立于Netfilter已有的NAT表之外,然后通过自己建立sk_buff的方式实现自制数据包或者修改已有数据包,完成NAT的类型转换(即实现通信)。下文会单独用一篇来说如何去自己建立sk_buff和钩子函数。
这里我们看看源码中的数据结构。
二. 套接字结构体
网络协议封装为多层,因此套接字结构体定义也有着多层结构,但是这里有一点要注意的:在网络通信中,我们通过网卡获取到的数据包至少包括了物理层,链路层和网络层的内容,因此套接字结构体仅仅从网络层开始,即通常我们只定义了传输层的套接字socket
和网络层的套接字sock
。socket
是用于负责对上给用户提供接口,并且和文件系统关联。而 sock
负责向下对接内核网络协议栈。
首先看传输层的socket
结构体,这个结构体表征BSD套接字的通用特性。首先是状态state
,用以表示连接情况。type
是套接字类型,如SOCK_STREAM
。wq
是等待队列,在后续文章中会说明。file
是套接字对应的文件指针,毕竟一切皆文件,所以需要统一的文件系统。sock
结构体的sk
变量则为网络层的套接字,ops
是协议相关的一系列套接字操作。
struct socket {
socket_state state;
short type;
unsigned long flags;
struct socket_wq *wq;
struct file *file;
struct sock *sk;
const struct proto_ops *ops;
};
接着看看网络层,这一层即IP层,该结构体sock
中包含了一个基本结构体sock_common
,整体较为复杂,所以对于其重要变量进行了说明,以注释的形式在每个变量后进行分析。
struct sock {
struct sock_common __sk_common; // 网络层套接字通用结构体
......
socket_lock_t sk_lock; // 套接字同步锁
atomic_t sk_drops; // IP/UDP包丢包统计
int sk_rcvlowat; // SO_RCVLOWAT标记位
......
struct sk_buff_head sk_receive_queue; // 收到的数据包队列
......
int sk_rcvbuf; // 接收缓存大小
......
union {
struct socket_wq __rcu *sk_wq; // 等待队列
struct socket_wq *sk_wq_raw;
};
......
int sk_sndbuf; // 发送缓存大小
/* ===== cache line for TX ===== */
int sk_wmem_queued; // 传输队列大小
refcount_t sk_wmem_alloc; // 已确认的传输字节数
unsigned long sk_tsq_flags; // TCP Small Queue标记位
union {
struct sk_buff *sk_send_head; // 发送队列对首
struct rb_root tcp_rtx_queue;
};
struct sk_buff_head sk_write_queue; // 发送队列
......
u32 sk_pacing_status; /* see enum sk_pacing 发包速率控制状态*/
long sk_sndtimeo; // SO_SNDTIMEO 标记位
struct timer_list sk_timer; // 套接字清空计时器
__u32 sk_priority; // SO_PRIORITY 标记位
......
unsigned long sk_pacing_rate; /* bytes per second 发包速率*/
unsigned long sk_max_pacing_rate; // 最大发包速率
struct page_frag sk_frag; // 缓存页帧
......
struct proto *sk_prot_creator;
rwlock_t sk_callback_lock;
int sk_err, // 上次错误
sk_err_soft; // “软”错误:不会导致失败的错误
u32 sk_ack_backlog; // ack队列长度
u32 sk_max_ack_backlog; // 最大ack队列长度
kuid_t sk_uid; // user id
struct pid *sk_peer_pid; // 套接字对应的peer的id
......
long sk_rcvtimeo; // 接收超时
ktime_t sk_stamp; // 时间戳
......
struct socket *sk_socket; // Identd协议报告IO信号
void *sk_user_data; // RPC层私有信息
......
struct sock_cgroup_data sk_cgrp_data; // cgroup数据
struct mem_cgroup *sk_memcg; // 内存cgroup关联
void (*sk_state_change)(struct sock *sk); // 状态变化回调函数
void (*sk_data_ready)(struct sock *sk); // 数据处理回调函数
void (*sk_write_space)(struct sock *sk); // 写空间可用回调函数
void (*sk_error_report)(struct sock *sk); // 错误报告回调函数
int (*sk_backlog_rcv)(struct sock *sk, struct sk_buff *skb); // 处理存储区回调函数
......
void (*sk_destruct)(struct sock *sk); // 析构回调函数
struct sock_reuseport __rcu *sk_reuseport_cb; // group容器重用回调函数
......
};
sock_common
是套接口在网络层的最小表示,即最基本的网络层套接字信息,具体内容分析见注释。
struct sock_common {
/* skc_daddr and skc_rcv_saddr must be grouped on a 8 bytes aligned
* address on 64bit arches : cf INET_MATCH()
*/
union {
__addrpair skc_addrpair;
struct {
__be32 skc_daddr; // 外部/目的IPV4地址
__be32 skc_rcv_saddr; // 本地绑定IPV4地址
};
};
union {
unsigned int skc_hash; // 根据协议查找表获取的哈希值
__u16 skc_u16hashes[2]; // 2个16位哈希值,UDP专用
};
/* skc_dport && skc_num must be grouped as well */
union {
__portpair skc_portpair; //
struct {
__be16 skc_dport; // inet_dport占位符
__u16 skc_num; // inet_num占位符
};
};
unsigned short skc_family; // 网络地址family
volatile unsigned char skc_state; // 连接状态
unsigned char skc_reuse:4; // SO_REUSEADDR 标记位
unsigned char skc_reuseport:1; // SO_REUSEPORT 标记位
unsigned char skc_ipv6only:1; // IPV6标记位
unsigned char skc_net_refcnt:1; // 该套接字网络名字空间内引用数
int skc_bound_dev_if; // 绑定设备索引
union {
struct hlist_node skc_bind_node; // 不同协议查找表组成的绑定哈希表
struct hlist_node skc_portaddr_node; // UDP/UDP-Lite protocol二级哈希表
};
struct proto *skc_prot; // 协议回调函数,根据协议不同而不同
......
union {
struct hlist_node skc_node; // 不同协议查找表组成的主哈希表
struct hlist_nulls_node skc_nulls_node; // UDP/UDP-Lite protocol主哈希表
};
unsigned short skc_tx_queue_mapping; // 该连接的传输队列
unsigned short skc_rx_queue_mapping; // 该连接的接受队列
......
union {
int skc_incoming_cpu; // 多核下处理该套接字数据包的CPU编号
u32 skc_rcv_wnd; // 接收窗口大小
u32 skc_tw_rcv_nxt; /* struct tcp_timewait_sock */
};
refcount_t skc_refcnt; // 套接字引用计数
......
};
三. 套接字缓冲区结构体
套接字结构体用于表征一个网络连接对应的本地接口的网络信息,而sk_buff
则是该网络连接对应的数据包的存储。sk_buff
的详细介绍宜参考《Linux网络技术内幕》,专门有一章来描述该结构体。对于我们学习源码来说,最重要的是了解其重点成员变量以及其整体结构。
其源码大致可以分为四部分:
- 布局:方便搜索以及组织结构,主要是一个双向链表用于管理全部的
sk_buff
。每个sk_buff
对应一个数据包,多个sk_buff
以双向链表的形式组合而成。
除此之外还有指向sock
的指针,缓冲区数据块大小,缓冲区及数据边界tail,end,head,data,truesize
- 通用字段:与特定内核无关的字段,主要包括时间戳
tstamp
,网络设备dev
,源设备input_device
,L2-L4层包头对应的mac_header, network_header, transport_header
等。其头部组织结构如下所示
- 功能专用:当编译防火墙(
Netfilter
) 以及QOS
等时才会用到的特殊字段,在此暂时不做详细介绍 - 管理函数:由内核提供的简单的管理工具函数,用于对
sk_buff
元素和元素列表进行操作,如数据预留及对齐函数skb_put(), skb_push(),skb_pull(),skb_reserve()
再比如分配回收函数alloc_skb()
和dev_alloc_skb()
释放内存函数kfree_skb()
和dev_kfree_skb()
除此之外还有克隆,复制等函数,不做过多展开介绍。
sk_buff
的整体填充过程如下图所示:
通过以上学习,对sk_buff
应该有了较为全面系统的了解,其详细源码如下所示,对于重点部分已写明中文注释,其他参见英文注释。
struct sk_buff {
union {
struct {
/* These two members must be first. 构成sk_buff链表*/
struct sk_buff *next;
struct sk_buff *prev;
union {
struct net_device *dev; //网络设备对应的结构体,很重要但是不是本文重点,所以不做展开
/* Some protocols might use this space to store information,
* while device pointer would be NULL.
* UDP receive path is one user.
*/
unsigned long dev_scratch; // 对于某些不适用net_device的协议需要采用该字段存储信息,如UDP的接收路径
};
};
struct rb_node rbnode; /* used in netem, ip4 defrag, and tcp stack 将sk_buff以红黑树组织,在TCP中有用到*/
struct list_head list; // sk_buff链表头指针
};
union {
struct sock *sk; // 指向网络层套接字结构体
int ip_defrag_offset;
};
union {
ktime_t tstamp; // 时间戳
u64 skb_mstamp_ns; /* earliest departure time */
};
/* 存储私有信息
* This is the control buffer. It is free to use for every
* layer. Please put your private variables there. If you
* want to keep them across layers you have to do a skb_clone()
* first. This is owned by whoever has the skb queued ATM.
*/
char cb[48] __aligned(8);
union {
struct {
unsigned long _skb_refdst; // 目标entry
void (*destructor)(struct sk_buff *skb); // 析构函数
};
struct list_head tcp_tsorted_anchor; // TCP发送队列(tp->tsorted_sent_queue)
};
....
unsigned int len, // 实际长度
data_len; // 数据长度
__u16 mac_len, // mac层长度
hdr_len; // 可写头部长度
/* Following fields are _not_ copied in __copy_skb_header()
* Note that queue_mapping is here mostly to fill a hole.
*/
__u16 queue_mapping; // 多队列设备的队列映射
......
/* fields enclosed in headers_start/headers_end are copied
* using a single memcpy() in __copy_skb_header()
*/
/* private: */
__u32 headers_start[0];
/* public: */
......
__u8 __pkt_type_offset[0];
__u8 pkt_type:3;
__u8 ignore_df:1;
__u8 nf_trace:1;
__u8 ip_summed:2;
__u8 ooo_okay:1;
__u8 l4_hash:1;
__u8 sw_hash:1;
__u8 wifi_acked_valid:1;
__u8 wifi_acked:1;
__u8 no_fcs:1;
/* Indicates the inner headers are valid in the skbuff. */
__u8 encapsulation:1;
__u8 encap_hdr_csum:1;
__u8 csum_valid:1;
......
__u8 __pkt_vlan_present_offset[0];
__u8 vlan_present:1;
__u8 csum_complete_sw:1;
__u8 csum_level:2;
__u8 csum_not_inet:1;
__u8 dst_pending_confirm:1;
......
__u8 ipvs_property:1;
__u8 inner_protocol_type:1;
__u8 remcsum_offload:1;
......
union {
__wsum csum;
struct {
__u16 csum_start;
__u16 csum_offset;
};
};
__u32 priority;
int skb_iif; // 接收到该数据包的网络接口的编号
__u32 hash;
__be16 vlan_proto;
__u16 vlan_tci;
......
union {
__u32 mark;
__u32 reserved_tailroom;
};
union {
__be16 inner_protocol;
__u8 inner_ipproto;
};
__u16 inner_transport_header;
__u16 inner_network_header;
__u16 inner_mac_header;
__be16 protocol;
__u16 transport_header; // 传输层头部
__u16 network_header; // 网络层头部
__u16 mac_header; // mac层头部
/* private: */
__u32 headers_end[0];
/* public: */
/* These elements must be at the end, see alloc_skb() for details. */
sk_buff_data_t tail;
sk_buff_data_t end;
unsigned char *head, *data;
unsigned int truesize;
refcount_t users;
......
};
四. 总结
本文分析了网络协议栈中经典的套接字结构体,希望对大家有所帮助
欢迎关注本人公众号,公众号会更新一些不一样的内容,相信一定会有所收获。