初次发表掘金
笔者此前对Linux内核相关模块稍有研究,实现内核级通信加密、视频流加密等,涉及:Linux内核网络协议栈、Linux内核通信模块、Linux内核加密模块、秘钥生成分发等。
后续考虑开设Linux内核专栏。
在此前的文章中讨论了Linux内核中Netfilter子系统的基本架构及其实现原理,本篇文章将讨论Linux内核另一个重要的子系统——XFRM框架。
下面开始上才艺,带你走进Linux内核之XFRM框架。
XFRM的正确读音是transform(转换), 这表示内核协议栈收到的IPsec报文需要经过转换才能还原为原始报文;
同样地,要发送的原始报文也需要转换为IPsec报文才能发送出去。
IPsec(Internet协议安全)应该很多人都听过,IPsec是一组协议,他们通过对通信会话中的每个数据包进行身份验证和加密,以确保IP流量的安全。
XFRM框架是IPsec的“基础设施”,IPsec通过XFRM框架实现的。XFRM源自USAGI项目,该项目旨在提供适用于生产环境的IPv6和IPsec协议栈。自内核2.5之后引入了XFRM框架,这个“基础设施”独立于协议簇,包含可同时应用于IPv4和IPv6的通用部分,位于源代码的net/xfrm/
目录下。
XFRM框架支持网络命名空间。这是一种轻型的进程虚拟化,它可以使得一个或者一组进程有属于自己的网络栈。每个网络命名空间都含有一个名为xfrm
的成员——一个netns_xfrm
结构实例。这个对象包含着许多的数据结构和变量,例如:XFRM策略散列表、XFRM状态散列表、sysctl参数、XFRM状态垃圾收集器、计数器等。
netns_xfrm
结构体定义,文件路径include/net/netns/xfrm.h
struct netns_xfrm { struct hlist_head *state_bydst; struct hlist_head *state_bysrc; struct hlist_head *state_byspi; . . . unsigned int state_num; . . . struct work_struct state_gc_work; . . . u32 sysctl_aevent_etime; u32 sysctl_aevent_rseqth; int sysctl_larval_drop; u32 sysctl_acq_expires; };
在IPv4中,XFRM初始化是通过在ip_rt_init()
函数(位于net/ipv4/route.c
文件)调用相关函数完成,函数调用结构为:ip_rt_init()->xfrm4_init()->xfrm_init()
。
而在IPv6中,在ipv6_route_init()
函数中调用xfrm6_init()
方法实现了XFRM的初始化。
用户空间和内核之间的通信创建NETLINK_XFRM
类型netlink
套接字(socket)以及发送和接收netlink
消息来完成。内核NETLINK_XFRM Netlink
套接字是在下面的函数中完成创建。
static int __net_init xfrm_user_net_init(struct net *net) { struct sock *nlsk; struct netlink_kernel_cfg cfg = { .groups = XFRMNLGRP_MAX, .input = xfrm_netlink_rcv, }; nlsk = netlink_kernel_create(net, NETLINK_XFRM, &cfg); . . . return 0; }
从用户空间发出的消息(比如XFRM_MSG_NEWPOLICY创建新的安全策略或者XFRM_MSG_NEWSA创建新的安全联盟)会被xfrm_netlink_rcv()方法处理,该方法又会被xfrm_user_rcv_msg()方法调用(第二章曾讨论过netlink套接字)。
XFRM策略和XFRM状态是XFRM框架的基础数据结构,接下来我将陆续介绍什么是XFRM策略以及XFRM状态。
安全策略是一种规则,告诉IPsec一条特定流量是否应该处理或者旁路,xfrm_policy结构用来描述IPsec策略。一个安全策略包含一个选择器(一个xfrm_selector对象)。当其选择器匹配一条流时会提供一种策略。XFRM选择器有一系列属性组成:比如source和destination address、source和destination port、protocol等等,用这些属性来识别一条流:
文件路径:include/uapi/linux/xfrm.h
struct xfrm_selector { xfrm_address_t daddr; xfrm_address_t saddr; __be16 dport; __be16 dport_mask; __be16 sport; __be16 sport_mask; __u16 family; __u8 prefixlen_d; __u8 prefixlen_s; __u8 proto; int ifindex; __kernel_uid32_t user; };
xfrm_selector_match()方法使用XFRM selector、flow和family(IPv4对应AF_INET,IPv6对应AF_INET6)作为参数,当特定XFRM流量匹配中特定选择器时返回true。注意xfrm_selector结构同样用在XFRM状态中。
安全策略(Security Policy)使用xfrm_policy结构表示,xfrm_policy结构用于描述SP在内核内部的具体实现:
文件路径:include/net/xfrm.h
struct xfrm_policy { struct xfrm_policy *next; // 下一个策略 struct hlist_node bydst; // 按目的地址HASH的链表 struct hlist_node byidx; // 按索引号HASH的链表 /* This lock only affects elements except for entry. */ rwlock_t lock; // 策略结构锁 atomic_t refcnt; // 引用次数 struct timer_list timer; // 策略定时器 u8 type; // 类型 u32 priority; // 策略优先级 u32 index; // 策略索引号 struct xfrm_selector selector; // 选择子 struct xfrm_lifetime_cfg lft; // 策略生命期 struct xfrm_lifetime_cur curlft; // 当前的生命期数据 struct dst_entry *bundles; // 路由链表 __u16 family; // 协议族 __u8 action; // 策略动作, 接受/加密/阻塞... __u8 flags; // 标志 __u8 dead; // 策略死亡标志 __u8 xfrm_nr; // 使用的xfrm_vec的数量 struct xfrm_sec_ctx *security; // 安全上下文 struct xfrm_tmpl xfrm_vec[XFRM_MAX_DEPTH]; // 状态模板 };
这个结构的字段很多,但大部分并不用关心,我们重点关注下面列举出的这几个字段就行:
用户可以通过下面命令,列出当前主机上的xfrm_policy
ip xfrm policy ls
src 10.1.0.0/16 dst 10.2.0.0/16 uid 0 dir out action allow index 5025 priority 383615 ptype main share any flag (0x00000000) lifetime config: limit: soft (INF)(bytes), hard (INF)(bytes) limit: soft (INF)(packets), hard (INF)(packets) expire add: soft 0(sec), hard 0(sec) expire use: soft 0(sec), hard 0(sec) lifetime current: 0(bytes), 0(packets) add 2019-09-02 10:25:39 use 2019-09-02 10:25:39 tmpl src 192.168.0.1 dst 192.168.0.2 proto esp spi 0xc420a5ed(3290473965) reqid 1(0x00000001) mode tunnel level required share any enc-mask ffffffff auth-mask ffffffff comp-mask ffffffff
结构xfrm_state
表示IPsec安全关联(include/net/xfrm.h)。它表示的是单向流量,包含加密密钥、标志、请求ID、统计信息、重放参数等信息。要添加XFRM状态,可从用户空间套接字发送请求XFRM_MSG_NEWSA
,在内核中,这种请求方法由xfrm_state_add()
处理(位于文件net/xfrm/xfrm_user.c)。同样,要删除状态,可发送XFRM_MSG_NEWSAXFRM_MSG_DELSA
消息,在内核中,这种请求方法由xfrm_del_sa()
处理。
xfrm_state状态结构用来描述SA在内核中的具体实现:
struct xfrm_state\ {\ /* Note: bydst is re-used during gc */\ // 每个状态结构挂接到三个HASH链表中\ struct hlist_node bydst; // 按目的地址HASH\ struct hlist_node bysrc; // 按源地址HASH\ struct hlist_node byspi; // 按SPI值HASH atomic_t refcnt; // 所有使用计数\ spinlock_t lock; // 状态锁 struct xfrm_id id; // ID结构, 即目的地址,SPI,协议三元组\ struct xfrm_selector sel; // 状态选择子 u32 genid; // 状态的标志值, 防止发生碰撞 /* Key manger bits */\ struct {\ u8 state;\ u8 dying;\ u32 seq;\ } km; // KEY回调管理处理结构参数 /* Parameters of this state. */\ struct {\ u32 reqid; // 请求ID\ u8 mode; // 模式: 传输/通道\ u8 replay_window; // 回放窗口\ u8 aalgo, ealgo, calgo; // 认证,加密,压缩算法ID值\ u8 flags; // 一些标准\ u16 family; // 协议族\ xfrm_address_t saddr; // 源地址\ int header_len; // 添加的协议头长度\ int trailer_len; //\ } props; // SA相关参数结构 struct xfrm_lifetime_cfg lft; // 生存时间配置 /* Data for transformer */\ struct xfrm_algo *aalg; // hash算法\ struct xfrm_algo *ealg; // 加密算法\ struct xfrm_algo *calg; // 压缩算法 /* Data for encapsulator */\ struct xfrm_encap_tmpl *encap; // NAT-T封装信息 /* Data for care-of address */\ xfrm_address_t *coaddr; /* IPComp needs an IPIP tunnel for handling uncompressed packets */\ struct xfrm_state *tunnel; // 通道, 实际是另一个SA /* If a tunnel, number of users + 1 */\ atomic_t tunnel_users; // 通道的使用数 /* State for replay detection */\ struct xfrm_replay_state replay; // 回放检测结构,包含各种序列号掩码等信息 /* Replay detection state at the time we sent the last notification */\ struct xfrm_replay_state preplay; // 上次的回放记录值 /* internal flag that only holds state for delayed aevent at the\ * moment\ */\ u32 xflags; // 标志 /* Replay detection notification settings */\ u32 replay_maxage; // 回放最大时间间隔\ u32 replay_maxdiff; // 回放最大差值 /* Replay detection notification timer */\ struct timer_list rtimer; // 回放检测定时器 /* Statistics */\ struct xfrm_stats stats; // 统计值 struct xfrm_lifetime_cur curlft; // 当前时间计数器\ struct timer_list timer; // SA定时器 /* Last used time */\ u64 lastused; // 上次使用时间 /* Reference to data common to all the instances of this\ * transformer. */\ struct xfrm_type *type; // 协议, ESP/AH/IPCOMP\ struct xfrm_mode *mode; // 模式, 通道或传输 /* Security context */\ struct xfrm_sec_ctx *security; // 安全上下文, 加密时使用 /* Private data of this transformer, format is opaque,\ * interpreted by xfrm_type methods. */\ void *data; // 内部数据\ };
xfrm_state包含的字段很多,这里就不贴了,仅仅列出其中最重要的字段:
每个xfrm_state在内核中会加入多个哈希表,因此,内核可以从多个特征查找到同一个个SA:
用户可以通过下面命令,列出当前主机上的xfrm_state
ip xfrm state ls
src 192.168.0.1 dst 192.168.0.2 proto esp spi 0xc420a5ed(3290473965) reqid 1(0x00000001) mode tunnel replay-window 0 seq 0x00000000 flag af-unspec (0x00100000) auth-trunc hmac(sha256) 0xa65e95de83369bd9f3be3afafc5c363ea5e5e3e12c3017837a7b9dd40fe1901f (256 bits) 128 enc cbc(aes) 0x61cd9e16bb8c1d9757852ce1ff46791f (128 bits) anti-replay context: seq 0x0, oseq 0x1, bitmap 0x00000000 lifetime config: limit: soft (INF)(bytes), hard (INF)(bytes) limit: soft (INF)(packets), hard (INF)(packets) expire add: soft 1004(sec), hard 1200(sec) expire use: soft 0(sec), hard 0(sec) lifetime current: 84(bytes), 1(packets) add 2019-09-02 10:25:39 use 2019-09-02 10:25:39 stats: replay-window 0 replay 0 failed 0
xfrm模板结构, 用于状态和策略的查询:
struct xfrm_tmpl\ {\ /* id in template is interpreted as:\ * daddr - destination of tunnel, may be zero for transport mode.\ * spi - zero to acquire spi. Not zero if spi is static, then\ * daddr must be fixed too.\ * proto - AH/ESP/IPCOMP\ */\ // SA三元组, 目的地址, 协议, SOI\ struct xfrm_id id; /* Source address of tunnel. Ignored, if it is not a tunnel. */\ // 源地址\ xfrm_address_t saddr; // 请求ID\ __u32 reqid; /* Mode: transport, tunnel etc. */\ __u8 mode; /* Sharing mode: unique, this session only, this user only etc. */\ __u8 share; /* May skip this transfomration if no SA is found */\ __u8 optional; /* Bit mask of algos allowed for acquisition */\ __u32 aalgos;\ __u32 ealgos;\ __u32 calgos;\ };
《精通Linux内核》
Linux内核网络:实现和理论
Linux内核中的IPSEC实现
xfrm框架