本文主要是介绍网络协议栈学习之socket, sock_common, sock, 和 sk_buff,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
一. 前言
一直很好奇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 idstruct 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; // 网络地址familyvolatile 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; // 目标entryvoid (*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;
......
};
四. 总结
本文分析了网络协议栈中经典的套接字结构体,希望对大家有所帮助
欢迎关注本人公众号,公众号会更新一些不一样的内容,相信一定会有所收获。
这篇关于网络协议栈学习之socket, sock_common, sock, 和 sk_buff的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!