快速模式第一包: quick_outI1()

## 快速模式第一包: quick_outI1()

1. 序言

openswan源码中有关隧道协商的文章已经比较久没有更新了,那么从这篇开始再重新回到更新流程上。这中间停了将近2个月,第一个月几乎没有更新任何博客,而第二个月主要整理翻译QAT相关的文章,接下来我将继续更新openswan源码相关的内容。初步计划按下表的顺序进行更新:

序号 内容 说明
1 快速模式三个报文流程 计划一周2篇文章
2 IKEv2协商流程 首先还是先整理协商流程接口和基本知识点
3 国密标准 国密标准存在多个,计划以2014标准为例进行说明
4 野蛮模式 目前接触的比较少,后续再进行补充
5

下面开始介绍IPSec 快速模式协商流程中的第①包,主要函数的入口为quick_outI1()

2. quick_outI1()流程图

在这里插入图片描述

3. quick_outI1()源码分析

quick_outI1()接口是第二阶段快速模式第一包的入口函数,它最主要的工作就是将第一阶段协商的ipsecsa状态信息转换为第二阶段的状态信息。通过duplicate_state实现状态的拷贝,然后将新的状态插入到全局的状态表中。之后就是根据隧道的配置信息(PFS, 算法信息)等做秘钥申请等准备工作。

  • 复制第一阶段ipsecsa状态,并将其插入全局状态表中
  • 显示第二阶段算法相关的debug信息
  • 根据配置做秘钥申请
stf_status
quick_outI1(int whack_sock
	    , struct state *isakmp_sa
	    , struct connection *c
	    , lset_t policy
	    , unsigned long try
	    , so_serial_t replacing
	    , struct xfrm_user_sec_ctx_ike * uctx UNUSED
	    )
{
   
    struct state *st = duplicate_state(isakmp_sa);/*复制第一阶段的状态,包括了所有的基本信息*/
    struct qke_continuation *qke;
    stf_status e;
    const char *pfsgroupname;
    char p2alg[256];

    st->st_whack_sock = whack_sock;
    st->st_connection = c;/*从这里可以看出每一个连接c可对应多个state结构,比如一个phase1,一个phase2.因此在隧道状态时需要特别注意*/
    passert(c != NULL);

    if(st->st_calculating) {
   
	return STF_IGNORE; 
    }

    set_cur_state(st);	/* we must reset before exit */
    st->st_policy = policy;
    st->st_try = try;

#ifdef HAVE_LABELED_IPSEC
    st->sec_ctx=NULL;
    if(uctx != NULL) {
   
    st->sec_ctx = clone_thing(*uctx, "sec ctx structure");
    DBG(DBG_CONTROL, DBG_log("pending phase 2 with security context %s, %d", st->sec_ctx->sec_ctx_value, st->sec_ctx->ctx_len));
    }
#endif
	/*本端协议、对端协议、本端端口、对端端口*/
    st->st_myuserprotoid   = c->spd.this.protocol;
    st->st_peeruserprotoid = c->spd.that.protocol;
    st->st_myuserport       = c->spd.this.port;
    st->st_peeruserport     = c->spd.that.port;

    st->st_msgid = generate_msgid(isakmp_sa);/*随机生成唯一的msgid*/
    change_state(st, STATE_QUICK_I1);/*设置当前状态为STATE_QUICK_I1*/

    insert_state(st);	/* needs cookies, connection, and msgid */

    strcpy(p2alg, "defaults");
    if(st->st_connection->alg_info_esp) {
   /*将alg_info_esp中的算法(第二阶段算法信息)解析转换为字符串,存储在p2alg*/
	alg_info_snprint_phase2(p2alg, sizeof(p2alg)/*只是为了显示使用*/
				, (struct alg_info_esp *)st->st_connection->alg_info_esp);
    }

    pfsgroupname="no-pfs";
    /*
     * See if pfs_group has been specified for this conn,
     * if not, fallback to old use-same-as-P1 behaviour
     */
    if (st->st_connection) {
   /*获取pfs组*/
	st->st_pfs_group = ike_alg_pfsgroup(st->st_connection
					    , st->st_policy);

    }

    /* If PFS specified, use the same group as during Phase 1:
     * since no negotiation is possible, we pick one that is
     * very likely supported.
     */
    if (!st->st_pfs_group)
	    st->st_pfs_group = policy & POLICY_PFS? isakmp_sa->st_oakley.group : NULL;

    if(policy & POLICY_PFS && st->st_pfs_group) {
   
	pfsgroupname = enum_name(&oakley_group_names, st->st_pfs_group->group);
    }

    {
   
	char replacestr[32];

	replacestr[0]='\0';
	if(replacing != SOS_NOBODY)
	    snprintf(replacestr, 32, " to replace #%lu", replacing);

	openswan_log("initiating Quick Mode %s%s {using isakmp#%lu msgid:%08x proposal=%s pfsgroup=%s}"
		     , prettypolicy(policy)
		     , replacestr
		     , isakmp_sa->st_serialno, st->st_msgid, p2alg, pfsgroupname);
    }

    qke = alloc_thing(struct qke_continuation , "quick_outI1 KE");
    qke->replacing = replacing;
    pcrc_init(&qke->qke_pcrc);
    qke->qke_pcrc.pcrc_func = quick_outI1_continue;/*对于KE载荷、NONCE载荷的填充是在此回调函数中实现的*/

    if(policy & POLICY_PFS) {
   /*生成KE载荷*/
	e=build_ke(&qke->qke_pcrc, st, st->st_pfs_group, st->st_import);
    } else {
   /*生成NONCE载荷*/
	e=build_nonce(&qke->qke_pcrc, st, st->st_import);
    }

    reset_globals();

    return e;
}

这个函数中应该注意到一点:就是一个connection(隧道)可以对应多个state结构。这有什么影响呢?

我们在查询隧道状态时,是通过查询该隧道(connection)对应的state来获取到协商的阶段,但是我们在遍历全局state表时只有全部遍历一遍才能查到最新的协商阶段,否则可能只是查询其中的一个state,这个可能不是最新的state。这样的话如果不采用效率高的数据结构存储状态,随着state增多,遍历的效率会很低。

**PFS(Perfect Forward Secrecy,完善的前向安全性)**是一种安全特性,指一个密钥被破解(例如说协商的第一阶段秘钥被破解),并不影响第二阶段密钥的安全性,因为这些密钥间没有派生关系。此特性是通过在IKE第二阶段的协商中增加密钥交换来实现的,因此源码实现中,如果策略启动了PFS,则再次增加一个KE载荷进行秘钥交换。

4. quick_outI1_continue()源码分析

这个continue函数与之前的函数功能基本一致,通过pcrc中的状态序号获取到相应的状态,然后调用后续的函数进行报文封装操作。

static void
quick_outI1_continue(struct pluto_crypto_req_cont *pcrc
		     , struct pluto_crypto_req *r
		     , err_t ugh)
{
   
    struct qke_continuation *qke = (struct qke_continuation *)pcrc;
    struct state *const st = state_with_serialno(qke->qke_pcrc.pcrc_serialno);/*一个效率比较低的接口*/
    stf_status e;

    DBG(DBG_CONTROLMORE
	, DBG_log("quick outI1: calculated ke+nonce, sending I1"));

    if (st == NULL) {
   
	loglog(RC_LOG_SERIOUS, "%s: Request was disconnected from state",
		__FUNCTION__);
	if (qke->md)
	    release_md(qke->md);
	return;
    }

    st->st_calculating = FALSE;

    /* XXX should check out ugh */
    passert(ugh == NULL);
    passert(cur_state == NULL);
    passert(st != NULL);

    set_cur_state(st);	/* we must reset before exit */
    set_suspended(st, NULL);
    e = quick_outI1_tail(pcrc, r, st);
    if (e == STF_INTERNAL_ERROR)
	loglog(RC_LOG_SERIOUS, "%s: quick_outI1_tail() failed with STF_INTERNAL_ERROR", __FUNCTION__);

    reset_globals();
}

这个有一个需要说明的地方,state_with_serialno函数需要遍历全局state哈希表,虽然O(n)的时间复杂度,但是如果state结构非常多的情况下,效率很低。因此如果应用场景中可添加的隧道比较多(成百上千条),那么需要对该接口进行优化。

state_with_serialno()源码实现如下:

/* Find the state object with this serial number.
 * This allows state object references that don't turn into dangerous
 * dangling pointers: reference a state by its serial number.
 * Returns NULL if there is no such state.
 * If this turns out to be a significant CPU hog, it could be
 * improved to use a hash table rather than sequential seartch.
 */
struct state *
state_with_serialno(so_serial_t sn)
{
   
    if (sn >= SOS_FIRST)
    {
   
	struct state *st;
	int i;

	for (i = 0; i < STATE_TABLE_SIZE; i++)
	    for (st = statetable[i]; st != NULL; st = st->st_hashchain_next)
		if (st->st_serialno == sn)
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

叨陪鲤

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值