一. 简介

在Linux内核中,XFRM被用作IPsec协议的框架。

XFRM状态数据库存在于每个网络命名空间中。
这意味着在不同的网络命名空间中,可以存在不同的XFRM状态,它们之间相互隔离。这种设计使得在多个网络环境中同时运行IPsec等安全协议变得更加灵活和可行。

本文主要介绍网络命名空间中的XFRM状态数据库中的XFRM state——XFRM state是用于存储IPsec安全关联信息的数据结构。

  • 网络命名空间下负责管理xfrm state的字段
  • xfrm state的存储方式——链表和哈希表的初始化
  • 创建xfrm state
  • 注册xfrm state节点
  • 删除xfrm state节点
  • 探讨站点到站点案例中 ICMP 报文的封装和加密过程

本文使用的内核版本为6.3.6
本文中提到的命名空间均指网络命名空间或者netns。

二. netns下的XFRM

网络命名空间(netns)是一种将网络资源隔离开来的机制,每个网络命名空间都有自己的网络设备、IP地址、路由表和其他网络相关的资源。如果内核编译选项开启了CONFIG_XFRM,那么在struct net中就会多个叫做xfrm的字段。

1
2
3
4
5
6
7
struct net {

#ifdef CONFIG_XFRM
struct netns_xfrm xfrm;
#endif

};

2.1 netns中的xfrm state

本文主要介绍struct netns_xfrm结构体中与xfrm state相关字段。

1
2
3
4
5
6
7
8
9
10
struct netns_xfrm {
struct list_head state_all;
struct hlist_head __rcu *state_bydst;
struct hlist_head __rcu *state_bysrc;
struct hlist_head __rcu *state_byspi;
struct hlist_head __rcu *state_byseq;
unsigned int state_hmask;
unsigned int state_num;
struct work_struct state_hash_work;
};

每个netns下可以存在很多xfrm state节点,这些节点在被创建后都会注册到一个链表中。
为了快速地查找节点,内核会按照某些特定的查找方式将xfrm state节点加入到哈希表中。

  • state_all:是一个链表头,用于存储所有的XFRM state。

  • state_bydststate_bysrcstate_byspistate_byseq:这些字段是指向哈希表头的指针,用于根据目的地地址、源地址、安全关联标识符(Security Association Identifier,SAID)和序列号来查找XFRM状态。

  • state_hmaskstate_num:state_hmask是一个哈希掩码,state_num是当前xfrm state的数量。

  • state_hash_work:这是一个用于处理XFRM状态哈希表的工作队列。

2.2 xfrm state初始化

每当创建一个netns,都将调用xfrm_state_init()来对上一节提到的字段进行初始化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int __net_init xfrm_state_init(struct net *net)
{
unsigned int sz;

INIT_LIST_HEAD(&net->xfrm.state_all);
sz = sizeof(struct hlist_head) * 8;
net->xfrm.state_bydst = xfrm_hash_alloc(sz);
net->xfrm.state_bysrc = xfrm_hash_alloc(sz);
net->xfrm.state_byspi = xfrm_hash_alloc(sz);
net->xfrm.state_byseq = xfrm_hash_alloc(sz);
net->xfrm.state_hmask = ((sz / sizeof(struct hlist_head)) - 1);
net->xfrm.state_num = 0;
INIT_WORK(&net->xfrm.state_hash_work, xfrm_hash_resize);
return 0;
}

三. 添加一个xfrm state

我们将以站点到站点的案例来分析xfrm state

1
2
10.1.0.0/16 -- | 192.168.0.1 | === | 192.168.0.2 | -- 10.2.0.0/16
moon-net moon sun sun-net

一对IPSEC VPN之间会建立两个xfrm state,在IKE(网络密钥交换)协议中我们也称为CHILD SA。一个是出方向的SA,一个是入方向的SA。
我在这里通过ip命令来查看一下moon站点的两个SA(xfrm state)

1
2
3
4
5
6
7
8
9
10
11
12
moon:~# ip xfrm state
src 192.168.0.1 dst 192.168.0.2
proto esp spi 0xc4828a4d reqid 1 mode tunnel
replay-window 0 flag af-unspec
aead rfc4106(gcm(aes)) 0x2235580fd327990c6a47fbd52092a5e6dd8e8ecd 128
anti-replay context: seq 0x0, oseq 0x0, bitmap 0x00000000
src 192.168.0.2 dst 192.168.0.1
proto esp spi 0xcc250164 reqid 1 mode tunnel
replay-window 32 flag af-unspec
aead rfc4106(gcm(aes)) 0x62f649ba190225498f4e5a9fe35b9e8a72b032ed 128
anti-replay context: seq 0x0, oseq 0x0, bitmap 0x00000000

向内核发起创建SA是由IKE协议来负责的,这里我们不展开描述,我们将从IKE进程发出XFRM_MSG_NEWSA开始,来分析xfrm state节点的创建过程。

3.1 xfrm_state

第二节我们介绍了一些管理xfrm state的字段,但都没有提及xfrm state节点的具体结构是什么样的。由于这个结构体比较大,本小结只会介绍最重要的一些字段。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
struct xfrm_state {
struct xfrm_id id;
struct xfrm_selector sel;
struct xfrm_mark mark;

/* Key manager bits */
struct xfrm_state_walk km;

/* Parameters of this state. */
struct {
u8 mode;
xfrm_address_t saddr;
} props;

struct xfrm_lifetime_cfg lft;

/* Data for transformer */
struct xfrm_algo_auth *aalg;
struct xfrm_algo *ealg;
const char *geniv;

/* Reference to data common to all the instances of this
* transformer. */
const struct xfrm_type *type;
struct xfrm_mode inner_mode;
struct xfrm_mode outer_mode;
};

1. id

每一个id都标识一个xfrm state,我们可以看下id由哪些参数构成:

1
2
3
4
5
6
7
8
struct xfrm_id {
/* 目的地址 */
xfrm_address_t daddr;
/* SPI(Security Parameter Index)是一种用于标识和区分安全关联(SA)的参数 */
__be32 spi;
/* 常见的有IPPROTO_ESP(50),IPPROTO_AH(51) */
__u8 proto;
};

2. sel

sel包含了一系列用于匹配数据包的条件,以便确定是否需要对数据包进行加密、认证或其他安全处理操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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;
};

3. props

xfrm state的一些参数

1
2
3
4
5
6
struct {
/* 区分传输模式和隧道模式 */
u8 mode;
/* 源地址 */
xfrm_address_t saddr;
} props;

4. type

xfrm_get_type()中,可以通过协议族和协议号来获取到相关的type,我的理解是type决定了安全关联的种类,常见的type如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
static const struct xfrm_type esp_type =
{
.owner = THIS_MODULE,
.proto = IPPROTO_ESP,
.flags = XFRM_TYPE_REPLAY_PROT,
.init_state = esp_init_state,
.destructor = esp_destroy,
.input = esp_input,
.output = esp_output,
};

static const struct xfrm_type ah_type =
{
.owner = THIS_MODULE,
.proto = IPPROTO_AH,
.flags = XFRM_TYPE_REPLAY_PROT,
.init_state = ah_init_state,
.destructor = ah_destroy,
.input = ah_input,
.output = ah_output
};
  • ESP(Encapsulating Security Payload):
    ESP协议提供了加密和认证机制,用于保护IP数据包的传输。它可以用于传输模式和隧道模式,并提供了对有效载荷的加密和认证功能。

  • AH(Authentication Header):
    AH协议提供了对IP数据包进行认证的机制,但不提供加密功能。AH可以用于传输模式和隧道模式,用于确保IP数据包的完整性和认证。

5. 算法相关

xfrm state中还记录了加密和认证算法

1
2
3
struct xfrm_algo_auth *aalg;
struct xfrm_algo *ealg;
const char *geniv;

3.2 创建一个state

用户空间可以通过netlink命令字XFRM_MSG_NEWSAXFRM_MSG_ALLOCSPI创建一个state实例。

  • 首先检查用户空间的参数是否合法
  • 其次创建state并向内核进行注册
  • 最后调用通知链上的函数。

我们主要关注创建state和向内核注册的过程。

1. 创建state

xfrm_state_construct()中会申请一块空间,并做一些默认初始化,然后将用户空间传入的参数拷贝到内核空间。拷贝的主要内容就是3.1节我们提到的一些字段。

主要包括:

  • id:用以唯一标识一个节点
  • sel:流量筛选器
  • props: xfrm state相关的参数,例如源地址、传输模式、隧道模式
  • lft: 生存周期
  • aalg:注册算法
  • ealg:加密算法
  • geniv:加密算法的初始化向量

2. 初始化state

接下来会根据目前已知的state信息,在__xfrm_init_state()做一些初始化。

我们会根据用户空间对这个SA type的配置,来调用相关type的初始化函数。

例如本例中用户要求的type为esp,协议族为IPv4,那么就会调用esp4模块注册的init_state回调函数。这里不再展开描述。

3. 注册state

初始化完毕后,将会调用__xfrm_state_insert()将xfrm start节点进行注册。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
static void __xfrm_state_insert(struct xfrm_state *x)
{
struct net *net = xs_net(x);

list_add(&x->km.all, &net->xfrm.state_all);

h = xfrm_dst_hash(net, &x->id.daddr, &x->props.saddr,
x->props.reqid, x->props.family);
XFRM_STATE_INSERT(bydst, &x->bydst, net->xfrm.state_bydst + h,
x->xso.type);

h = xfrm_src_hash(net, &x->id.daddr, &x->props.saddr, x->props.family);
XFRM_STATE_INSERT(bysrc, &x->bysrc, net->xfrm.state_bysrc + h,
x->xso.type);

if (x->id.spi) {
h = xfrm_spi_hash(net, &x->id.daddr, x->id.spi, x->id.proto,
x->props.family);

XFRM_STATE_INSERT(byspi, &x->byspi, net->xfrm.state_byspi + h,
x->xso.type);
}

if (x->km.seq) {
h = xfrm_seq_hash(net, x->km.seq);

XFRM_STATE_INSERT(byseq, &x->byseq, net->xfrm.state_byseq + h,
x->xso.type);
}

net->xfrm.state_num++;
}

4. 通知

最后会在km_state_notify()调用通知回调函数

1
2
3
list_for_each_entry_rcu(km, &xfrm_km_list, list)
if (km->notify)
km->notify(x, c);

四. 删除xfrm state

使用ip xfrm state flush命令可以清除所有的xfrm state,也可以设置过滤规则清除指定xfrm state。

不论如何,最后都会调用到__xfrm_state_delete()来清除这个xfrm state。

如果我们需要真正区释放调这块空间的话需要指定sync,否则的话内核仅仅把这个节点从这种表中摘除,同时把这个节点添加到一个垃圾回收链表中(gclist)。

1
static DECLARE_WORK(xfrm_state_gc_work, xfrm_state_gc_task);

内核的垃圾回收机制会定期调用xfrm_state_gc_task()来释放内存。

五. 观察一个分组的加密过程

从本节开始,将介绍XFRM框架下如何使用XFRM state来对分组进行安全处理。

XFRM框架除了state外还有一个policy,policy负责匹配分组是否进入XFRM流程,后续会出一个XFRM policy的文档,在此文中,了解到协议栈会根据policy来选择是否将分组交给XFRM来处理就够了。

所以,后文的介绍默认协议栈已经经过了policy的匹配,确认分组需要交给XFRM,那XFRM要如何处理这个分组呢?我们提出以下问题:

  • 是需要使用传输模式还是隧道模式呢?
  • 是需要使用ESP封装还是使用AH封装呢?
  • 如需加密,应该使用哪种算法套件呢?

这些问题的答案都存储在了XFRM state中。这意味着我们需要从policy中寻找线索,使用这些线索,来在XFRM state中查找答案。

policy中的线索就是一个名为template的模板,我们将使用这个模板,在netns中的XFRM state节点中去匹配,找出匹配的XFRM state。事实上,IPsec策略通常由多个因素组成,包括源地址、目标地址、传输层协议等。当进行策略匹配时,可能会有多个xfrm state与给定的策略条件匹配,这可能导致匹配深度的增加。XFRM_MAX_DEPTH的存在就是为了限制这种情况的发生。它确保了在进行策略匹配时,系统不会无限递归地搜索和匹配xfrm state,从而避免潜在的性能问题。

但是我们本文只介绍最简单的环境,一个template只能匹配到一个XFRM state

5.1 IPSEC场景

本文将分析最传统的站点到站点之间建立IPSEC的案例。两个站点为moon和sun。

两个站点之间建立了使用ESP封装的隧道(即mode为tunnel,type为ESP4)。

两个站点将保护各自子网中的流量。

1
2
10.1.0.0/16 -- | 192.168.0.1 | === | 192.168.0.2 | -- 10.2.0.0/16
moon-net moon sun sun-net

当隧道建立好以后,我们使用moon-net中的一个主机alice(10.1.0.10)来ping sun-net中的一个主机bob(10.2.0.10)。

  • 有效载荷为ICMP数据——data
  • 内层的原始IP头为:Internet Protocol Version 4, Src: 10.1.0.10, Dst: 10.2.0.10 —— Original IP Header
  • ESP Header
  • 外层站点的IP头为:Internet Protocol Version 4, Src: 192.168.0.1, Dst: 192.168.0.2 —— Outer IP Header

最终的IP Packet格式如下:

1
2
3
4
-----------------------------------------
| Outer | ESP | Original | ICMP |
| IP Header |Header | IP Header | Date |
-----------------------------------------

由于moon站点收到了目的地址为10.2.0.10的IP Packet,所以协议栈会转发这个数据包。

我们现在应该位于网络层的ip_forward()

5.2 查找state

从现在开始,我们将从通过模板查找state开始,追踪这个分组的生命。

入口函数为xfrm_state_find()

1
2
3
4
5
6
struct xfrm_state *xfrm_state_find(const xfrm_address_t *daddr,
const xfrm_address_t *saddr,
const struct flowi *fl,
struct xfrm_tmpl *tmpl,
struct xfrm_policy *pol, int *err,
unsigned short family, u32 if_id);

daddr为192.168.0.2,saddr为192.168.0.1,在查找xfrm state的时候还会结合tmpl->reqid这类标记,在netns中通过net->xfrm.state_bydst这个哈希表来快速匹配state。最终会使用tmpl中的字段和xfrm state中的sel字段进行精准匹配。

结果就是匹配到了一个合适的xfrm state并返回。那么针对这个分组的安全处理方法就都拿到了。

5.3 创建dst_entry

struct dst_entry是用于表示网络数据包转发信息的结构体。它包含了一系列字段,用于描述数据包的转发目标、路径和相关信息,我们将重点关注他的output

XFRM框架最后也需要给协议栈返回dst_entry来让协议栈继续处理数据包,但是在XFRM框架中,对这个结构体作了扩展,这也是c语言中对继承的一种表示方法。

XFRM中有一个结构体继承自dst_entry,他叫struct xfrm_dst

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struct xfrm_dst {
union {
struct dst_entry dst;
struct rtable rt;
struct rt6_info rt6;
} u;
struct dst_entry *route;
struct dst_entry *child;
struct dst_entry *path;
struct xfrm_policy *pols[XFRM_POLICY_TYPE_MAX];
int num_pols, num_xfrms;
u32 xfrm_genid;
u32 policy_genid;
u32 route_mtu_cached;
u32 child_mtu_cached;
u32 route_cookie;
u32 path_cookie;
};

这个结构体用于表示一组要应用于某个数据流的转换。将路由、xfrm policy、xfrm state进行了关联

在对xdst实例化的过程中,将向dst_entry注册XFRM框架的ops。在xfrm_alloc_dst()中完成了这种注册。

决定协议栈下一层应该使用什么处理函数的是dst_entry的output字段。

1
2
afinfo = xfrm_state_afinfo_get_rcu(inner_mode->family);
dst1->output = afinfo->output;

这个afinfo是通过xfrm_state_register_afinfo()注册的。对于IPv4,注册的函数为:

1
2
3
4
5
6
7
static struct xfrm_state_afinfo xfrm4_state_afinfo = {
.family = AF_INET,
.proto = IPPROTO_IPIP,
.output = xfrm4_output,
.transport_finish = xfrm4_transport_finish,
.local_error = xfrm4_local_error,
};

这里我们总结一下:

  • 协议栈对分组进行了xfrm policy的匹配
  • XFRM模块通过policy中的模板,与XFRM state中的sel进行了匹配
  • 得到一个(可能有多个)xfrm state
  • XFRM模块将创建一个继承自dst_entry的结构xfrm_dst
  • xfrm_dst将XFRM state、XFRM policy组织起来
  • xfrm_dst将返回一个网络数据包转发信息的结构体dst_entry
  • dst_entry中包含了协议栈下一层要使用的处理函数skb_dst(skb)->output
  • skb_dst(skb)->output在此时为xfrm4_output

5.4 ip_forward_finish

通过之前的匹配、查找与注册,网络转发层已经知道这个分组要怎么处理了,他会调用dst_output来进一步处理分组。

1
2
3
4
5
6
static inline int dst_output(struct net *net, struct sock *sk, struct sk_buff *skb)
{
return INDIRECT_CALL_INET(skb_dst(skb)->output,
ip6_output, ip_output,
net, sk, skb);
}

这个宏定义在IPv4中展开是这样的:

1
2
3
4
likely(skb_dst(skb)->output == ip_output)
ip_output(net, sk, skb);
else
skb_dst(skb)->output(net, sk, skb);

我们目前处于第二个分支,我们的output函数为xfrm4_output

5.5 xfrm4_output

沿着函数调用一路追下来,可以看到最终的核心处理函数在这里:

  • 首先调用xfrm_output_one()处理分组
  • 其次调用dst->ops->local_out
  • 最后检查skb_dst(skb)->xfrm字段,为空表示处理完XFRM框架了,直接通过dst_output()送往下一层
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int xfrm_output_resume(struct sock *sk, struct sk_buff *skb, int err)
{
struct net *net = xs_net(skb_dst(skb)->xfrm);

while (likely((err = xfrm_output_one(skb, err)) == 0)) {
nf_reset_ct(skb);

err = skb_dst(skb)->ops->local_out(net, sk, skb);

if (!skb_dst(skb)->xfrm)
return dst_output(net, sk, skb);

err = nf_hook(skb_dst(skb)->ops->family,
NF_INET_POST_ROUTING, net, sk, skb,
NULL, skb_dst(skb)->dev, xfrm_output2);
}

return err;
}

5.6 local_output

我们稍后在沿着xfrm_output_one()进行分析,这一次我们站在一个更高的角度来看分组的生命周期。

还记得这个dst是怎么生成的吗?他在xfrm_alloc_dst()中注册了XFRM框架的dst ops:

1
2
3
4
static struct dst_ops xfrm4_dst_ops_template = {
.family = AF_INET,
.local_out = __ip_local_out,
};

所以skb_dst(skb)->ops->local_out()就是__ip_local_out(),这也意味着XFRM框架已经完成了tunnel封装和ESP加密了,不难猜测,xfrm_output_one()中所做的就是完成tunnel封装和ESP加密。

1
2
3
4
5
6
7
8
int __ip_local_out(struct net *net, struct sock *sk, struct sk_buff *skb)
{
skb->protocol = htons(ETH_P_IP);

return nf_hook(NFPROTO_IPV4, NF_INET_LOCAL_OUT,
net, sk, skb, NULL, skb_dst(skb)->dev,
dst_output);
}

可以看到,目前skb->protocol指明为ETH_P_IP,然后将IP Packet送往下一层。此时skb_dst(skb)->output的回调函数就是ip_output()至此,我们完整地处理完了ipforward动作

六. 封装与加密

让我们回归xfrm_output_one(),看一下这里发生了什么。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static int xfrm_output_one(struct sk_buff *skb, int err)
{
do {
err = xfrm_outer_mode_output(x, skb);

err = NF_HOOK(dst->ops->family, NF_INET_XFRM_OUT, net, NULL,
skb, NULL, dst->dev, xfrm_type_output);
resume:
dst = skb_dst_pop(skb);
skb_dst_set(skb, dst);
x = dst->xfrm;
} while (x && !(x->outer_mode.flags & XFRM_MODE_FLAG_TUNNEL));

return 0;
}

总体上是一个do while,在IPSEC中是存在内层IP和外层IP的说法的,所以这个截至条件就是外层IP的mode不再是tunnel(这是否意味着可以像套娃一样套很多层呢?)。

接下来看一下循环的主要流程:

  • xfrm_outer_mode_output:添加外层IP头
  • xfrm_type_output:进行ESP加密

6.1 封装

我们主要关注隧道模式的封装,它是在xfrm4_tunnel_encap_add()中完成的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
static int xfrm4_tunnel_encap_add(struct xfrm_state *x, struct sk_buff *skb)
{
skb_set_inner_network_header(skb, skb_network_offset(skb));
skb_set_inner_transport_header(skb, skb_transport_offset(skb));
skb_set_network_header(skb, -x->props.header_len);

skb->mac_header = skb->network_header +
offsetof(struct iphdr, protocol);
skb->transport_header = skb->network_header + sizeof(*top_iph);

top_iph = ip_hdr(skb);

top_iph->ihl = 5;
top_iph->version = 4;

top_iph->saddr = x->props.saddr.a4;
top_iph->daddr = x->id.daddr.a4;

return 0;
}

我们看一下原始的Packet,如果skb->head为0的话,64到78刚好为14字节的Eth header,78到98刚好是IP header,98开始就是ICMP负载了。
ICMP

他主要分为以下操作:

  • skb_set_inner_network_header:用inner_network_header指向network_head,表示这个IP Packet的外层IP报文头现在只是内层IP头了。
  • skb_set_network_header:更新外层IP头相对于内层IP头的偏移。
  • 更新mac_header相对于外层IP头的偏移。
  • 通过iphdr()拿到指向外层IP头的指针
  • 初始化外层IP头,包括长度、协议、源目IP地址等。

此时,经过tunnel的封装,我们的skb中的一些指针状态将变为以下的样子。

隧道封装

6.2 加密

在完成隧道的封装以后,XFRM将交给之前注册的type模块来处理分组——x->type->output(x, skb)

还记得这个type是在哪里进行注册的吗?我们在3.2.2节中简单提到了在创建好XFRM state之后会调用__xfrm_init_state()对state进行初始化。也就是在这里注册了type的output函数。

1
2
3
4
5
6
7
8
9
10
static const struct xfrm_type esp_type =
{
.owner = THIS_MODULE,
.proto = IPPROTO_ESP,
.flags = XFRM_TYPE_REPLAY_PROT,
.init_state = esp_init_state,
.destructor = esp_destroy,
.input = esp_input,
.output = esp_output,
};

所以,我们进入了ESP模块,看下他是怎么创建ESP封装与加密的。

1
2
3
4
5
6
7
8
9
10
11
static int esp_output(struct xfrm_state *x, struct sk_buff *skb)
{
*skb_mac_header(skb) = IPPROTO_ESP;

esp.esph = ip_esp_hdr(skb);

esph = esp.esph;
esph->spi = x->id.spi;

return esp_output_tail(x, skb, &esp);
}
  • 首先修改了mac header,IPPROTO_ESP为50
  • 填充esp header
  • esp_output_tail进行加密认证

1. 封装

通过上面的图片,我们可以看到在封装外层IP的时候,预留了16字节的空间,交给了transport_header,所以ip_esp_hdr()是用的skb_transport_header()来拿到的地址

1
2
3
4
static inline struct ip_esp_hdr *ip_esp_hdr(const struct sk_buff *skb)
{
return (struct ip_esp_hdr *)skb_transport_header(skb);
}

拿到esp头后会填充spi、seq和IV等字段。

2. 加密

加密前会先封装一个加密请求,最终调用crypto_aead_encrypt()进行加密。
从组织加密请求到调用加密算法看着非常晦涩,我就先跳过了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int esp_output_tail(struct xfrm_state *x, struct sk_buff *skb, struct esp_info *esp)
{
struct crypto_aead *aead;
struct aead_request *req;

aead = x->data;

iv = esp_tmp_iv(aead, tmp, extralen);
req = esp_tmp_req(aead, iv);

aead_request_set_crypt(req, sg, dsg, ivlen + esp->clen, iv);
aead_request_set_ad(req, assoclen);

err = crypto_aead_encrypt(req);
return err;
}