0%

DPDK ZUC PMD代码笔记

1. 前言

最近看了流密码算法的Crypto PMD,稍微总结一下。

2. 介绍

Snow3g/Zuc PMD都是基于intel-ipsec-mb库实现的库。许多数据和操作都是公共的,代码都位于dpdk/drivers/crypto/ipsec_mb目录下。

intel-ipsec-mb库是为包处理提供的一个软件加密库,可以用在IPsec, TLS, Wireless等应用中。

ipsec_mb下还有多个pmd,对应不同的算法。不同pmd类型相关的数据会保存到ipsec_mb_pmds变量中,公共函数根据当前的pmd类型获取对应pmd的数据和函数处理数据。实现不同pmd的分离。PMD类型在PMD初始化时保存到dev的dev_private区域。

3. ZUC PMD

ZUC PMD的主要函数整理如下:

zuc_pmd_func

下面描述一下主要的功能实现:

3.1 PMD操作回调及数据设置

ipsec_mb_register_zuc函数在程序启动时设置ZUC PMD相关的配置,保存到全局数组变量ipsec_mb_pmds中。

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53

/* Constructor function to register zuc PMD */
RTE_INIT(ipsec_mb_register_zuc)
{
struct ipsec_mb_internals *zuc_data
= &ipsec_mb_pmds[IPSEC_MB_PMD_TYPE_ZUC];

zuc_data->caps = zuc_capabilities;
zuc_data->dequeue_burst = zuc_pmd_dequeue_burst;
zuc_data->feature_flags = RTE_CRYPTODEV_FF_SYMMETRIC_CRYPTO
| RTE_CRYPTODEV_FF_SYM_OPERATION_CHAINING
| RTE_CRYPTODEV_FF_NON_BYTE_ALIGNED_DATA
| RTE_CRYPTODEV_FF_OOP_SGL_IN_LB_OUT
| RTE_CRYPTODEV_FF_SYM_SESSIONLESS
| RTE_CRYPTODEV_FF_OOP_LB_IN_LB_OUT;
zuc_data->internals_priv_size = 0;
zuc_data->ops = &zuc_pmd_ops;
zuc_data->qp_priv_size = sizeof(struct zuc_qp_data);
zuc_data->session_configure = zuc_session_configure;
zuc_data->session_priv_size = sizeof(struct zuc_session);
}

// RTE_INIT的实现如下

#define RTE_PRIO(prio) \
RTE_PRIORITY_ ## prio

/**
* Run function before main() with high priority.
*
* @param func
* Constructor function.
* @param prio
* Priority number must be above 100.
* Lowest number is the first to run.
*/
#ifndef RTE_INIT_PRIO /* Allow to override from EAL */
#define RTE_INIT_PRIO(func, prio) \
static void __attribute__((constructor(RTE_PRIO(prio)), used)) func(void)
#endif

/**
* Run function before main() with low priority.
*
* The constructor will be run after prioritized constructors.
*
* @param func
* Constructor function.
*/
#define RTE_INIT(func) \
RTE_INIT_PRIO(func, LAST)


RTE_INIT使用的是编译器的constructor属性,使得该函数在main函数运行之前就调用,类似于静态执行的一种机制。

3.2 设备驱动发现及卸载

上一篇文章中讲过,PMD会调用RTE_PMD_REGISTER_VDEV注册虚拟设备到DPDK框架中,在应用启动之后主动把驱动的发现和卸载的接口注册上去,也是调用的RTE_INIT实现的。

ZUC PMD设备驱动发现及卸载函数分别调用ipsec_mb_create、ipsec_mb_remove。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
static int
zuc_probe(struct rte_vdev_device *vdev)
{
return ipsec_mb_create(vdev, IPSEC_MB_PMD_TYPE_ZUC);
}

static struct rte_vdev_driver cryptodev_zuc_pmd_drv = {
.probe = zuc_probe,
.remove = ipsec_mb_remove

};

static struct cryptodev_driver zuc_crypto_drv;

RTE_PMD_REGISTER_VDEV(CRYPTODEV_NAME_ZUC_PMD, cryptodev_zuc_pmd_drv);

设备发现实现函数ipsec_mb_create,主要内容:

1)获取pmd操作函数及数据
2)解析参数,创建pmd
3)在dev的私有数据部分,保存pmd类型及队列数目配置
4)获取驱动id, 驱动id在注册时确定,跟设备id不一样,驱动存放在cryptodev_driver_list
5)设置出入队函数、特性等数据到dev

设备卸载实现函数ipsec_mb_remove

  1. 释放队列对资源
  2. 释放pmd

3.3 能力定义

ZUC PMD顾名思义就是只支持ZUC算法了。

zuc_capacity

3.4 队列对配置

实现函数为ipsec_mb_qp_setup:
1)为dev->data->queue_pairs[qp_id]申请空间,并初始化相关内容。
2)保存mp_pool, mp_pool_private。
3)创建队列对的软件队列rte_ring
4)队列对相关的统计数据结构初始化

3.5 Session处理

rte_cryptodev_sym_session_create从队列对对应的mp_pool申请一个rte_cryptodev_sym_session。调用rte_cryptodev_sym_session_init初始化,从mp_pool_private申请PMD的私有session结构,调用sys_session_configure把xform数据转换为私有的session数据。

看了许多DPDK的示例代码,默认一个vdev申请一个session, 每个session对应一种算法,也就对应到一个chain类型(CIPHER, AUTH, CIPHER_AUTH, AUTH_CIPHER和AEAD)。在出队入队时,同一批的ops,默认会是同一个session的,当然,具体看那个应用的实现了,也是可以支持不同算法类型的ops的,框架并没有对此限定。

ZUC pmd实现函数zuc_session_configure:
1)解析xform链,获取operation对应的类型及相关的cipher_xform, auth_xform, aead_xform
2)从xform中获取数据信息填充到私有session结构

3.6 Operation管理

用户根据情况自行创建op_mpool,调用rte_crypto_op_pool_create创建,op_mpool池分为对称和非对称两种类型。后面可以追加用户自定义的数据。
struct rte_mempool *
rte_crypto_op_pool_create(const char *name, enum rte_crypto_op_type type,
unsigned nb_elts, unsigned cache_size, uint16_t priv_size,
int socket_id)

大致结构如下:

zuc_operation

然后rte_crypto_op_alloc申请对应的operation,把session跟operation关联起来,把数据相关的信息填充到operation中。
一般把iv放到priv data区域。

3.7 入队出队

入队统一使用公共的ipsec_mb_enqueue_burst,实现比较简单,直接把相关的ops添加到ingress_queue中,增加入队计数。
出队列实现函数zuc_pmd_dequeue_burst:

  • 从ingress_queue获取nu_ops个ops
  • 获取ops管理的session数据, 得到ops对应的算法类型。
  • 获取相同算法类型的ops集中处理,最大16个。前N个类型相同,则最多处理16个,如果N小于16,则先处理N个,再处理下一种类型数据。
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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94

static uint16_t
zuc_pmd_dequeue_burst(void *queue_pair,
struct rte_crypto_op **c_ops, uint16_t nb_ops)
{

struct rte_crypto_op *curr_c_op;

struct zuc_session *curr_sess;
struct zuc_session *sessions[ZUC_MAX_BURST];
struct rte_crypto_op *int_c_ops[ZUC_MAX_BURST];
enum ipsec_mb_operation prev_zuc_op = IPSEC_MB_OP_NOT_SUPPORTED;
enum ipsec_mb_operation curr_zuc_op;
struct ipsec_mb_qp *qp = queue_pair;
unsigned int nb_dequeued;
unsigned int i;
uint8_t burst_size = 0;
uint8_t processed_ops;

nb_dequeued = rte_ring_dequeue_burst(qp->ingress_queue,
(void **)c_ops, nb_ops, NULL); // 出队指定个数的ops


for (i = 0; i < nb_dequeued; i++) {
curr_c_op = c_ops[i];

curr_sess = (struct zuc_session *)
ipsec_mb_get_session_private(qp, curr_c_op); // 获取私有session
if (unlikely(curr_sess == NULL)) {
curr_c_op->status =
RTE_CRYPTO_OP_STATUS_INVALID_SESSION;
break;
}

curr_zuc_op = curr_sess->op; // 得到相同算法类型的操作

/*
* Batch ops that share the same operation type
* (cipher only, auth only...).
*/
if (burst_size == 0) {
prev_zuc_op = curr_zuc_op;
int_c_ops[0] = curr_c_op;
sessions[0] = curr_sess;
burst_size++;
} else if (curr_zuc_op == prev_zuc_op) {
int_c_ops[burst_size] = curr_c_op; // 保存ops到临时数组中
sessions[burst_size] = curr_sess; // 同步保存私有的session
burst_size++;
/*
* When there are enough ops to process in a batch,
* process them, and start a new batch.
*/
if (burst_size == ZUC_MAX_BURST) { // 按16个批量处理
processed_ops = process_ops(int_c_ops, curr_zuc_op,
sessions, qp, burst_size);
if (processed_ops < burst_size) {
burst_size = 0;
break;
}

burst_size = 0;
}
} else {
/*
* Different operation type, process the ops
* of the previous type.
*/
processed_ops = process_ops(int_c_ops, prev_zuc_op, // 遇到有不同算法的op,先处理之前的op
sessions, qp, burst_size);
if (processed_ops < burst_size) {
burst_size = 0;
break;
}

burst_size = 0;
prev_zuc_op = curr_zuc_op;

int_c_ops[0] = curr_c_op;
sessions[0] = curr_sess;
burst_size++;
}
}

if (burst_size != 0) { //处理达不到批量数量的op
/* Process the crypto ops of the last operation type. */
processed_ops = process_ops(int_c_ops, prev_zuc_op,
sessions, qp, burst_size);
}

qp->stats.dequeued_count += i;
return i;
}

3.8 Session池创建

每个numa节点分别分配一个mp_pool和一个mp_pool_private。
mp_pool的成员为rte_cryptodev_sym_session, 元素个数为设备数目乘以队列数,也就是让每个队列都能分配到一个session。
mp_pool使用接口rte_cryptodev_sym_session_pool_create创建

struct rte_mempool *
rte_cryptodev_sym_session_pool_create(const char *name, uint32_t nb_elts,
uint32_t elt_size, uint32_t cache_size, uint16_t user_data_size,
int socket_id)

mp_pool的元素为rte_cryptodev_sym_session

crypto_sym_session

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/** Cryptodev symmetric crypto session
* Each session is derived from a fixed xform chain. Therefore each session
* has a fixed algo, key, op-type, digest_len etc.
*/
struct rte_cryptodev_sym_session {
uint64_t opaque_data;
/**< Can be used for external metadata */
uint16_t nb_drivers;
/**< number of elements in sess_data array */
uint16_t user_data_sz;
/**< session user data will be placed after sess_data */
__extension__ struct {
void *data;
uint16_t refcnt;
} sess_data[];
/**< Driver specific session material, variable size */
};

根据代码注释,我们可以确定每个session对应固定的算法, key,操作类型等数据。
mp_pool_priveate的则是PMD定义的私有数据,大小通过PMD提供的sym_session_get_size接口获取。mp_pool_private使用rte_mempool_create创建。
这两个pool池会被设置到队列对的配置中去。调用rte_cryptodev_queue_pair_setup进行配置。

PMD初始化的时候会保存socket_id到dev结构中。

更多

大概梳理了一下ZUC PMD的接口,及其app调用的流程中的部分功能实现。


行动,才不会被动!

欢迎关注个人公众号 微信 -> 搜索 -> fishmwei,沟通交流。

博客地址: https://fishmwei.github.io

掘金主页: https://juejin.cn/user/2084329776486919