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)
return st;
}
return NULL;
}

5. quick_outI1_tail()源码分析

quick_outI1_tail()函数的作用:构造第二阶段首包报文,他包括:

  • 第二阶段的加解密算法、哈希(认证)算法、PFS等策略信息

    • 构造SA建议载荷out_sa()
  • 如果启动PFS,则重新进行秘钥交换,生成KE载荷
  • 生成Nonce载荷
  • 构造本端标识和对端标识载荷
  • NAT穿越中的 OA载荷
  • 计算报文的完整性(哈希算法)
  • 对报文进行加密

源码如下:

static stf_status
quick_outI1_tail(struct pluto_crypto_req_cont *pcrc
, struct pluto_crypto_req *r
, struct state *st)
{
struct qke_continuation *qke = (struct qke_continuation *)pcrc;
struct state *isakmp_sa = state_with_serialno(st->st_clonedfrom);
struct connection *c = st->st_connection;
pb_stream rbody;
u_char /* set by START_HASH_PAYLOAD: */
*r_hashval, /* where in reply to jam hash value */
*r_hash_start; /* start of what is to be hashed *//*用来记录需要计算hash的起始位置*/
bool has_client = c->spd.this.has_client || c->spd.that.has_client ||
c->spd.this.protocol || c->spd.that.protocol ||
c->spd.this.port || c->spd.that.port; if(isakmp_sa == NULL) {
/* phase1 state got deleted while cryptohelper was working */
loglog(RC_LOG_SERIOUS,"phase2 initiation failed because parent ISAKMP #%lu is gone", st->st_clonedfrom);
return STF_FATAL;
} #ifdef NAT_TRAVERSAL
if (isakmp_sa->hidden_variables.st_nat_traversal & NAT_T_DETECTED) {/*第一阶段协商过程中发现存在NAT设备*/
/* Duplicate nat_traversal status in new state *//*将NAT信息存储在新的state上*/
st->hidden_variables.st_nat_traversal = isakmp_sa->hidden_variables.st_nat_traversal;
if (isakmp_sa->hidden_variables.st_nat_traversal & LELEM(NAT_TRAVERSAL_NAT_BHND_ME)) {/*本端位于NAT之后*/
has_client = TRUE;
}/*确定端口浮动后的出接口*/
nat_traversal_change_port_lookup(NULL, st);
}
else {
st->hidden_variables.st_nat_traversal = 0;
}
#endif /* set up reply */
init_pbs(&reply_stream, reply_buffer, sizeof(reply_buffer), "reply packet"); /* HDR* out *//*填充第二阶段的ISAKMP的头部*/
{
struct isakmp_hdr hdr; hdr.isa_version = ISAKMP_MAJOR_VERSION << ISA_MAJ_SHIFT | ISAKMP_MINOR_VERSION;
hdr.isa_np = ISAKMP_NEXT_HASH;
hdr.isa_xchg = ISAKMP_XCHG_QUICK;
hdr.isa_msgid = st->st_msgid;
hdr.isa_flags = ISAKMP_FLAG_ENCRYPTION;
memcpy(hdr.isa_icookie, st->st_icookie, COOKIE_SIZE);
memcpy(hdr.isa_rcookie, st->st_rcookie, COOKIE_SIZE);
if (!out_struct(&hdr, &isakmp_hdr_desc, &reply_stream, &rbody))/*填充到报文中*/
{
reset_cur_state();
return STF_INTERNAL_ERROR;
}
}
/*填充hash载荷头部,数据部分全零,并记录下要填充hash的位置,最后填充*/
/* HASH(1) -- create and note space to be filled later */
START_HASH_PAYLOAD(rbody, ISAKMP_NEXT_SA); /* SA out */ /* Emit SA payload based on a subset of the policy bits.
* POLICY_COMPRESS is considered iff we can do IPcomp.
*/
{
lset_t pm = POLICY_ENCRYPT | POLICY_AUTHENTICATE; if (can_do_IPcomp)
pm |= POLICY_COMPRESS;
/*填充sa载荷: ESP AH IPCom*/
if (!out_sa(&rbody
, &ipsec_sadb[(st->st_policy & pm) >> POLICY_IPSEC_SHIFT]
, st, FALSE, FALSE, ISAKMP_NEXT_NONCE))
{
reset_cur_state();
return STF_INTERNAL_ERROR;
}
} {
int np; if(st->st_policy & POLICY_PFS) {/*如果使用PFS,则需要再次DH协商*/
np = ISAKMP_NEXT_KE;
} else {
if(has_client) {
np = ISAKMP_NEXT_ID;
} else {
np = ISAKMP_NEXT_NONE;
}
} /* Ni out *//*填充Nonce载荷,并将其存储在st_ni中*/
if (!ship_nonce(&st->st_ni, r, &rbody
, np
, "Ni"))
{
reset_cur_state();
return STF_INTERNAL_ERROR;
}
} /* [ KE ] out (for PFS) *//*填充KE载荷,并将其存储在st_gi*/
if (st->st_pfs_group != NULL)
{
if (!ship_KE(st, r, &st->st_gi
, &rbody
, has_client? ISAKMP_NEXT_ID : ISAKMP_NEXT_NONE))
{
reset_cur_state();
return STF_INTERNAL_ERROR;
}
} /* [ IDci, IDcr ] out */
if (has_client)/*填充的子网ID*/
{
/* IDci (we are initiator), then IDcr (peer is responder) */
if (!emit_subnet_id(&c->spd.this/*本端身份标识*/
, ISAKMP_NEXT_ID
, st->st_localaddr
, st->st_myuserprotoid
, st->st_myuserport, &rbody)
|| !emit_subnet_id(&c->spd.that/*对端身份标识*/
, ISAKMP_NEXT_NONE
, st->st_remoteaddr
, st->st_peeruserprotoid
, st->st_peeruserport, &rbody))
{
reset_cur_state();
return STF_INTERNAL_ERROR;
}
} #ifdef NAT_TRAVERSAL
if ((st->hidden_variables.st_nat_traversal & NAT_T_WITH_NATOA)
&& (!(st->st_policy & POLICY_TUNNEL))/*只有传输模式才需要OA载荷????*/
&& (st->hidden_variables.st_nat_traversal & LELEM(NAT_TRAVERSAL_NAT_BHND_ME))) {
/** Send NAT-OA if our address is NATed *//*填充OA载荷,这里需要修改上一个载荷的NP*/
if (!nat_traversal_add_natoa(ISAKMP_NEXT_NONE, &rbody, st, TRUE /* initiator */)) {
reset_cur_state();
return STF_INTERNAL_ERROR;
}
}
#endif #ifdef TPM
{
pb_stream *pbs = &rbody;
size_t enc_len = pbs_offset(pbs) - sizeof(struct isakmp_hdr); TCLCALLOUT_crypt("preHash",st,pbs,sizeof(struct isakmp_hdr),enc_len);
r_hashval = tpm_relocateHash(pbs);
}
#endif /* finish computing HASH(1), inserting it in output *//*计算整个载荷的哈希值*/
(void) quick_mode_hash12(r_hashval, r_hash_start, rbody.cur
, st, &st->st_msgid, FALSE); /* encrypt message, except for fixed part of header */
/*设置第二阶段的IV值*/ init_phase2_iv(isakmp_sa, &st->st_msgid);
st->st_new_iv_len = isakmp_sa->st_new_iv_len;
set_new_iv(st, isakmp_sa->st_new_iv); if (!encrypt_message(&rbody, st))/*加密除报文头部以外的所有载荷*/
{
reset_cur_state();
return STF_INTERNAL_ERROR;
} /* save packet, now that we know its size 保留数据包,超时重传会使用到*/
clonetochunk(st->st_tpacket, reply_stream.start, pbs_offset(&reply_stream)
, "reply packet from quick_outI1"); /* send the packet */
/*发送报文,如果使用了NAT-T,则会添加Non-ESP的封装*/
send_packet(st, "quick_outI1", TRUE); delete_event(st);/*设置超时重传事件*/
event_schedule(EVENT_RETRANSMIT, EVENT_RETRANSMIT_DELAY_0, st); if (qke->replacing == SOS_NOBODY)
whack_log(RC_NEW_STATE + STATE_QUICK_I1
, "%s: initiate"
, enum_name(&state_names, st->st_state));
else
whack_log(RC_NEW_STATE + STATE_QUICK_I1
, "%s: initiate to replace #%lu"
, enum_name(&state_names, st->st_state)
, qke->replacing); return STF_OK;
}

下面对quick_outI1_tail()中的几个重要函数做个简单说明:

5.1 out_sa()

这个函数在第一阶段的前两个报文中使用过,当时使用的第一阶段的SA载荷,现在使用第二阶段的SA载荷;out_sa同时实现了第一阶段和第二阶段SA载荷封装的功能,它通过bool oakley_mode参数来确定使用第一阶段还是第二阶段的封装流程。如果说out_struct等封装接口已经比较熟的话,那么这个函数可能会比较容易,否则基本流程看起来还是有点吃力。这里只简单说明out_struct各个参数的作用:

/****************************************************************
将struct_ptr按照sd的描述方式拷贝到outs中。同时如果obj_pbs存在,
则使obj_pbs指向outs数据部分,并更新obj_pbs的cur指针到新填充的位置,
然后将outs的cur设置到最大,其他函数不得再操作outs,除非使用close_output_pbs更新才行
****************************************************************/
bool
out_struct(const void *struct_ptr, struct_desc *sd, pb_stream *outs, pb_stream *obj_pbs)

openswan源码在对齐上做的不敢恭维,而代码是不忍卒读(看不懂)。

bool
out_sa(pb_stream *outs
, struct db_sa *sadb
, struct state *st
, bool oakley_mode
, bool aggressive_mode UNUSED
, u_int8_t np)
{
pb_stream sa_pbs;
unsigned int pcn;
bool ret = FALSE;
bool ah_spi_generated = FALSE
, esp_spi_generated = FALSE
, ipcomp_cpi_generated = FALSE;
struct db_sa *revised_sadb; if(oakley_mode) {
/* Aggr-Mode - Max transforms == 2 - Multiple transforms, 1 DH group */
/*根据配置的秘钥算法信息重新生成一个sadb信息*/
/*传入的sadb应该为固定的秘钥算法信息,因此需要根据策略来重新生成一个新的sadb*/
revised_sadb = oakley_alg_makedb(st->st_connection->alg_info_ike/*第一阶段算法*/
, sadb
, aggressive_mode ? 2 : -1);
} else {/*根据配置生成第二阶段的算法信息*/
revised_sad = kernel_alg_makedb(st->st_connection->policy
, st->st_connection->alg_info_esp/*第二阶段算法*/
, TRUE);
/*IPComp代码略*/
} /* more sanity */
if(revised_sadb != NULL) {
sadb = revised_sadb;
} /* SA header out */
{/*添加SA头部*/
/* 1 2 3
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* ! Next Payload ! RESERVED ! Payload Length !
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* ! Domain of Interpretation (DOI) !
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* ! !
* ~ Situation ~
* ! !
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
struct isakmp_sa sa; sa.isasa_np = np;
st->st_doi = sa.isasa_doi = ISAKMP_DOI_IPSEC; /* all we know */
if (!out_struct(&sa, &isakmp_sa_desc, outs, &sa_pbs))
return_on(ret, FALSE);
} /* within SA: situation out *//*填充上图中的Situation字段*/
st->st_situation = SIT_IDENTITY_ONLY;
if (!out_struct(&st->st_situation, &ipsec_sit_desc, &sa_pbs, NULL))
return_on(ret, FALSE); /* within SA: Proposal Payloads 建议载荷
*
* Multiple Proposals with the same number are simultaneous
* (conjuncts) and must deal with different protocols (AH or ESP).
* Proposals with different numbers are alternatives (disjuncts),
* in preference order.
* Proposal numbers must be monotonic.
* See RFC 2408 "ISAKMP" 4.2
*/ for (pcn = 0; pcn < sadb->prop_conj_cnt; pcn++)
{
struct db_prop_conj *pc;
unsigned int pn;
int valid_prop_cnt; pc = &sadb->prop_conjs[pcn];/*遍历建议载荷*/
valid_prop_cnt = pc->prop_cnt;
DBG(DBG_EMITTING,
DBG_log("out_sa pcn: %d has %d valid proposals",
pcn, valid_prop_cnt)); for (pn = 0; pn < pc->prop_cnt; pn++)/*遍历建议载荷*/
{
struct db_prop *p;
pb_stream proposal_pbs;
struct isakmp_proposal proposal;
struct_desc *trans_desc;
struct_desc *attr_desc;
enum_names **attr_val_descs;
unsigned int tn;
bool tunnel_mode; /*
* set the tunnel_mode bit on the last proposal only, and
* only if we are trying to negotiate tunnel mode in the first
* place.
*/
tunnel_mode = (valid_prop_cnt == 1)
&& (st->st_policy & POLICY_TUNNEL); /*
* pick the part of the proposal we are trying to work on
*/
/* 1 2 3
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* ! Next Payload ! RESERVED ! Payload Length !
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* ! Proposal # ! Protocol-Id ! SPI Size !# of Transforms!
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* ! SPI (variable) !
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/ p = &pc->props[pn]; proposal.isap_proposal = pcn;
proposal.isap_protoid = p->protoid;
proposal.isap_spisize = oakley_mode ? 0
: p->protoid == PROTO_IPCOMP ? IPCOMP_CPI_SIZE
: IPSEC_DOI_SPI_SIZE; /* but, skip things if the transform count is zero */
if(p->trans_cnt == 0) continue; /* Proposal header */
if(--valid_prop_cnt > 0) {
proposal.isap_np = ISAKMP_NEXT_P;
} else {
proposal.isap_np = ISAKMP_NEXT_NONE;
} proposal.isap_notrans = p->trans_cnt;/*变换载荷的个数*/
if (!out_struct(&proposal, &isakmp_proposal_desc
, &sa_pbs, &proposal_pbs))
return_on(ret, FALSE); /* Per-protocols stuff:
* Set trans_desc.
* Set attr_desc.
* Set attr_val_descs.
* If not oakley_mode, emit SPI.
* We allocate SPIs on demand.
* All ESPs in an SA will share a single SPI.
* All AHs in an SAwill share a single SPI.
* AHs' SPI will be distinct from ESPs'.
* This latter is needed because KLIPS doesn't
* use the protocol when looking up a (dest, protocol, spi).
* ??? If multiple ESPs are composed, how should their SPIs
* be allocated?
*/
{
struct ipsec_proto_info *pi = NULL;
int proto = 0;
bool *spi_generated; spi_generated = NULL; switch (p->protoid)
{
case PROTO_ISAKMP:
passert(oakley_mode);
trans_desc = &isakmp_isakmp_transform_desc;
attr_desc = &isakmp_oakley_attribute_desc;
attr_val_descs = oakley_attr_val_descs;
/* no SPI needed */
break;
/*第二阶段时,在kernel_alg_db_new中根据策略配置选择采用的封装方式*/
case PROTO_IPSEC_AH:
passert(!oakley_mode);
trans_desc = &isakmp_ah_transform_desc;
attr_desc = &isakmp_ipsec_attribute_desc;
attr_val_descs = ipsec_attr_val_descs;
pi = &st->st_ah;
spi_generated = &ah_spi_generated;
proto = IPPROTO_AH;
break; case PROTO_IPSEC_ESP:
passert(!oakley_mode);
trans_desc = &isakmp_esp_transform_desc;
attr_desc = &isakmp_ipsec_attribute_desc;
attr_val_descs = ipsec_attr_val_descs;
pi = &st->st_esp;
spi_generated = &esp_spi_generated;
proto = IPPROTO_ESP;
break; case PROTO_IPCOMP:
passert(!oakley_mode);
trans_desc = &isakmp_ipcomp_transform_desc;
attr_desc = &isakmp_ipsec_attribute_desc;
attr_val_descs = ipsec_attr_val_descs; /* a CPI isn't quite the same as an SPI
* so we use specialized code to emit it.
*/
if (!ipcomp_cpi_generated)
{
st->st_ipcomp.our_spi = get_my_cpi(st, tunnel_mode);
if (st->st_ipcomp.our_spi == 0)
return_on(ret, FALSE); /* problem generating CPI */ ipcomp_cpi_generated = TRUE;
}
/* CPI is stored in network low order end of an
* ipsec_spi_t. So we start a couple of bytes in.
*/
if (!out_raw((u_char *)&st->st_ipcomp.our_spi
+ IPSEC_DOI_SPI_SIZE - IPCOMP_CPI_SIZE
, IPCOMP_CPI_SIZE
, &proposal_pbs, "CPI"))
return_on(ret, FALSE);
break; default:
bad_case(p->protoid);
} if (pi != NULL)
{
if (spi_generated != NULL && !*spi_generated)
{
if (!get_ipsec_spi(pi
, proto
, st
, tunnel_mode)) {
return FALSE;
}
*spi_generated = TRUE;
}
if (!out_raw((u_char *)&pi->our_spi, IPSEC_DOI_SPI_SIZE
, &proposal_pbs, "SPI"))
return_on(ret, FALSE);
}
} /* 填充变换载荷 within proposal: Transform Payloads */
for (tn = 0; tn != p->trans_cnt; tn++)
{
struct db_trans *t = &p->trans[tn];
pb_stream trans_pbs;
struct isakmp_transform trans;
unsigned int an; trans.isat_np = (tn == p->trans_cnt - 1)
? ISAKMP_NEXT_NONE : ISAKMP_NEXT_T;
trans.isat_transnum = tn;
trans.isat_transid = t->transid;
if (!out_struct(&trans, trans_desc, &proposal_pbs, &trans_pbs))
return_on(ret, FALSE); /* Within tranform: Attributes. */ /* For Phase 2 / Quick Mode, GROUP_DESCRIPTION is
* automatically generated because it must be the same
* in every transform. Except IPCOMP.
*/
if (p->protoid != PROTO_IPCOMP
&& st->st_pfs_group != NULL)/*添加PFS组属性信息*/
{
passert(!oakley_mode);
passert(st->st_pfs_group != &unset_group);
out_attr(GROUP_DESCRIPTION, st->st_pfs_group->group
, attr_desc, attr_val_descs
, &trans_pbs);
} /* automatically generate duration
* and, for Phase 2 / Quick Mode, encapsulation.
*/
if (oakley_mode)/*第一阶段*/
{
out_attr(OAKLEY_LIFE_TYPE, OAKLEY_LIFE_SECONDS
, attr_desc, attr_val_descs
, &trans_pbs);
out_attr(OAKLEY_LIFE_DURATION
, st->st_connection->sa_ike_life_seconds
, attr_desc, attr_val_descs
, &trans_pbs);
}
else/*第二阶段*/
{
/* RFC 2407 (IPSEC DOI) 4.5 specifies that
* the default is "unspecified (host-dependent)".
* This makes little sense, so we always specify it.
*
* Unlike other IPSEC transforms, IPCOMP defaults
* to Transport Mode, so we can exploit the default
* (draft-shacham-ippcp-rfc2393bis-05.txt 4.1).
*/
if (p->protoid != PROTO_IPCOMP
|| st->st_policy & POLICY_TUNNEL)
{
/*隧道模式? 传输模式*/
out_attr(ENCAPSULATION_MODE
#ifdef NAT_TRAVERSAL
#ifdef I_KNOW_TRANSPORT_MODE_HAS_SECURITY_CONCERN_BUT_I_WANT_IT
, NAT_T_ENCAPSULATION_MODE(st,st->st_policy)
#else
/* If NAT-T is detected, use UDP_TUNNEL as long as Transport
* Mode has security concerns.
*
* User has been informed of that
*/
, NAT_T_ENCAPSULATION_MODE(st,POLICY_TUNNEL)
#endif
#else /* ! NAT_TRAVERSAL */
, st->st_policy & POLICY_TUNNEL
? ENCAPSULATION_MODE_TUNNEL : ENCAPSULATION_MODE_TRANSPORT
#endif
, attr_desc, attr_val_descs
, &trans_pbs);
}
out_attr(SA_LIFE_TYPE, SA_LIFE_TYPE_SECONDS /*单位:秒*/
, attr_desc, attr_val_descs
, &trans_pbs);
out_attr(SA_LIFE_DURATION
, st->st_connection->sa_ipsec_life_seconds /*生存时间从连接上获取*/
, attr_desc, attr_val_descs
, &trans_pbs); } /* spit out attributes from table */
for (an = 0; an != t->attr_cnt; an++)
{
struct db_attr *a = &t->attrs[an]; if(oakley_mode) {
out_attr(a->type.oakley, a->val
, attr_desc, attr_val_descs
, &trans_pbs);
} else {
out_attr(a->type.ipsec, a->val , attr_desc, attr_val_descs , &trans_pbs); } } close_output_pbs(&trans_pbs);
}
close_output_pbs(&proposal_pbs);
}
/* end of a conjunction of proposals */
}
close_output_pbs(&sa_pbs);
ret = TRUE; return_out: #if defined(KERNEL_ALG) || defined(IKE_ALG)
if (revised_sadb)
free_sa(revised_sadb);
#endif
return ret;
}

一般而言,比较关心我们配置的参数在哪里生效? 例如加密算法、认证算法、隧道模式or传输模式都是在out_sa()中通过属性载荷封装在报文中的。下图为属性载荷结构:

属性类型的最高比特位AF指定数据为定长还是变长,如果为0表示定长;如果为1表示变长。

具体属性类型有以下几种(全是定长类型):

属性类型 属性类型取值 属性值说明
SA生存周期 1 0: 保留 1:秒 2:千字节
SA生存期 2 0: 保留 1:秒 2:千字节
组描述 3
封装模式 4 0:保留 1:隧道模式 2:传输模式
认证算法 5 0:RESERVED 1:HMAC-MD5 2:HMAC-SHA …
密钥长度 6
密钥轮数 7
压缩字典长度 8
私有压缩算法 9

注:加密算法不适用属性载荷进行封装。

5.2 emit_subnet_id()

第二阶段除了协商加解密算法信息,还会对双方的保护子网进行匹配。而保护子网是通过ID载荷来传输的。在第一阶段中使用build_id_payload()接口将我们在配置隧道的“身份标识”发送对方以供双方认证,第二阶段使用emit_subnet_id()来协商两端的保护子网信息。

每一条隧道有本端和对端两个节点,这两个节点都是用struct end结构描述,而两端的保护子网使用struct end中的ip_subnet client;描述,ip_subnet结构如下:

typedef struct {
ip_address addr;
int maskbits;
} ip_subnet;
/* Initiate quick mode.
* --> HDR*, HASH(1), SA, Nr [, KE ] [, IDci, IDcr ]
* (see RFC 2409 "IKE" 5.5)
* Note: this is not called from demux.c
*/
/*填充的是隧道端口IP还是子网的信息?
*保护子网是如何协商的???
*/
static bool
emit_subnet_id(struct end *e
, u_int8_t np
, ip_address endpoint
, u_int8_t protoid
, u_int16_t port
, pb_stream *outs)
{
struct isakmp_ipsec_id id;
pb_stream id_pbs;
ip_address ta;
unsigned char *tbp;
size_t tal;
const struct af_info *ai;
bool usehost = FALSE;
ip_subnet clientnet; clientnet = e->client; if(!e->has_client) {
/* we propose the IP address of the interface that we are using. */
/*
* we could instead propose 0.0.0.0->255.255.255.255 and let the other
* end narrow the TS, but if one wants that, it is easy to just specify
* in the configuration file: rightsubnet=0.0.0.0/0.
*
* When there is NAT involved, we may really want a tunnel to the
* address that this end point thinks it is. That works only when
* virtual_ip includes the IP involved.
*
*/
addrtosubnet(&endpoint, &clientnet);
} ai = aftoinfo(subnettypeof(&clientnet));
passert(ai != NULL); id.isaiid_np = np;
id.isaiid_idtype = (usehost ? ai->id_addr : ai->id_subnet);/*确定使用主机ID还是子网ID;由于usehost===FALSE,因此这里使用子网ID*/
id.isaiid_protoid = protoid;
id.isaiid_port = port; if (!out_struct(&id, &isakmp_ipsec_identification_desc, outs, &id_pbs))
return FALSE; networkof(&clientnet, &ta);/*获取保护子网*/
tal = addrbytesptr(&ta, &tbp);
if (!out_raw(tbp, tal, &id_pbs, "client network"))/*填充保护子网信息*/
return FALSE; if(!usehost)
{
maskof(&clientnet, &ta);/*获取保护子网掩码*/
tal = addrbytesptr(&ta, &tbp);
if (!out_raw(tbp, tal, &id_pbs, "client mask"))/*填充保护子网掩码信息*/
return FALSE;
} close_output_pbs(&id_pbs);
return TRUE;
}

ID载荷(标识载荷)包含以下几种类型:

ID类型 描述 取值
ID_NONE 未使用 0
ID_IPV4_ADDR 单独的一个IPv4地址 1
ID_FQDN 全域名字符串,如topsec.com.cn 2
ID_USER_FQDN 用户名字符串,如li_si@topsec.com.cn 3
ID_RFC822_ADDR 同ID_USER_FQDN ID_USER_FQDN
ID_IPV4_ADDR_SUBNET IPv4类子网地址,如192.168.1.1 255.255.255.0 4
ID_IPV6_ADDR 单独IPv6地址 5
ID_IPV6_ADDR_SUBNET IPv6子网地址 6
ID_IPV4_ADDR_RANGE IPv4地址范围区间, 如192.168.2.3 192.168.2.200 7
ID_IPV6_ADDR_RANGE IPv6地址范围区间 8
ID_DER_ASN1_DN x.500编码格式 9
ID_DER_ASN1_GN x.500编码格式 10
ID_KEY_ID 传递特定厂商信息的字节流 11

5.3 encrypt_message()

报文的加密范围:除了ISAKMP头部之外都需要进行加密。加密使用第一阶段协商的加密秘钥(报文认证时同时也会用到认证密钥)。

/* encrypt message, sans fixed part of header
* IV is fetched from st->st_new_iv and stored into st->st_iv.
* The theory is that there will be no "backing out", so we commit to IV.
* We also close the pbs.
*/
bool
encrypt_message(pb_stream *pbs, struct state *st)
{
const struct encrypt_desc *e = st->st_oakley.encrypter;
u_int8_t *enc_start = pbs->start + sizeof(struct isakmp_hdr);/*加密的内容为ISAKMP头部之后*/
size_t enc_len = pbs_offset(pbs) - sizeof(struct isakmp_hdr);/*加密内容的长度*/ DBG_cond_dump(DBG_CRYPT | DBG_RAW, "encrypting:\n", enc_start, enc_len);
DBG_cond_dump(DBG_CRYPT | DBG_RAW, "IV:\n"
, st->st_new_iv
, st->st_new_iv_len);
DBG(DBG_CRYPT, DBG_log("unpadded size is: %u", (unsigned int)enc_len)); /* Pad up to multiple of encryption blocksize.
* See the description associated with the definition of
* struct isakmp_hdr in packet.h.
*/
{
/*确定需要填充的长度*/
size_t padding = pad_up(enc_len, e->enc_blocksize); if (padding != 0)/*如果填充的长度不为0,则需要进行填充数据*/
{
if (!out_zero(padding, pbs, "encryption padding"))/*在输出流上进行报文填充*/
return FALSE;
enc_len += padding;
}
} DBG(DBG_CRYPT
, DBG_log("encrypting %d using %s"
, (unsigned int)enc_len
, enum_show(&oakley_enc_names, st->st_oakley.encrypt))); TCLCALLOUT_crypt("preEncrypt", st, pbs,sizeof(struct isakmp_hdr),enc_len);/*非TPM未做任何处理*/ /* e->crypt(TRUE, enc_start, enc_len, st); */
crypto_cbc_encrypt(e, TRUE, enc_start, enc_len, st);/*使用CBC算法进行加密:使用连接上第一阶段协商的加密算法和加密密钥信息*/ TCLCALLOUT_crypt("postEncrypt", st,pbs,sizeof(struct isakmp_hdr),enc_len); update_iv(st);
DBG_cond_dump(DBG_CRYPT, "next IV:", st->st_iv, st->st_iv_len);
close_message(pbs);
return TRUE;
}

5.4 out_modify_previous_np()

此函数的作用在于修改前一个载荷头部中的下一个载荷字段。它在填充NAT-T相关的OA载荷时用到。基本原理是从头部开始向后遍历每一个载荷,直到找到最后一个载荷的头部(尚未填充新的载荷,因此它还是最后一个载荷)。

bool
out_modify_previous_np(u_int8_t np, pb_stream *outs)
{
u_int8_t *pl = outs->start;
size_t left = outs->cur - outs->start; passert(left >= NSIZEOF_isakmp_hdr); /* not even room for isakmp_hdr! */
if (left == NSIZEOF_isakmp_hdr) {
/* no payloads, just the isakmp_hdr: insert np here */
passert(pl[NOFFSETOF_isa_np] == ISAKMP_NEXT_NONE ||
pl[NOFFSETOF_isa_np] == ISAKMP_NEXT_HASH);
pl[NOFFSETOF_isa_np] = np;
} else {
pl += NSIZEOF_isakmp_hdr; /* skip over isakmp_hdr */
left -= NSIZEOF_isakmp_hdr;
for (;;) {
size_t pllen; passert(left >= NSIZEOF_isakmp_generic);
pllen = (pl[NOFFSETOF_isag_length] << 8)/*payload 一般为两个字节*/
| pl[NOFFSETOF_isag_length + 1];
passert(left >= pllen);
if (left == pllen) {/*当前载荷长度和剩余长度相同时,说明已经找到上一个载荷*/
/* found last top-level payload */
pl[NOFFSETOF_isag_np] = np;
break; /* done */
} else {
/* this payload is not the last: scan forward */
pl += pllen;
left -= pllen;
}
}
}
return TRUE;
}

6. 小结

略。

快速模式第一包: quick_outI1()的更多相关文章

  1. 韩顺刚-tcp报文头协议详细分析第一包数据:序号是0,发送数据的长度是0,因为没有收到对端的数据,所以确认号是0, Syn的标志位设置成1,这里没有发送的数据,只发送TCP的20个字节的头部

    TCP报文段首部格式 大部分TCP报文头部都是20个字节,有的数据包要加上选项. 上面一行代表4个字节,源端口和目的端口都是2个字节. TCP协议是面向字节流的协议 TCP是一段一段分块的发送数据的 ...

  2. IKEv2协议协商流程: (IKE-SA-INIT 交换)第一包

    文章目录 1. IKEv2 协商总体框架 2. 第一包流程图 3. openswan源码学习 3.1 ikev2parent_outI1() 3.2 ikev2parent_outI1_withsta ...

  3. 快速模式第二包: quick_inI1_ouR1()

    文章目录 1. 序言 2. quick_inI1_outR1()流程图 3. 快速模式消息②数据包格式 4. 源码分析 4.1 quick_inI1_outR1() 4.2 quick_inI1_ou ...

  4. ping第一包时间过长

    一.现象 公司互联网域(说白了就是可以连外网的vlan区域的主机)的几台主机在ping一个合作方提供的域名时,发现在ping 第一个包时,时间特别长,后面第2--N包开始正常.直接ping 该域名对应 ...

  5. openswan中ISAKMP交互过程关键函数接口

    1. ISAKMP交互过程中关键函数接口 下面分别说明不同的阶段和模式下的函数接口以及对应的报文. 2. 第一阶段(Phase I)主模式函数接口 发送端 响应端 main_outI1 主模式第一包 ...

  6. IPsec 9个包分析(主模式+快速模式)

    第一阶段:ISAKMP协商阶段 1.1 第一包 包1:发起端协商SA,使用的是UDP协议,端口号是500,上层协议是ISAKMP,该协议提供的是一个框架,里面的负载Next payload类似模块,可 ...

  7. 快速模式第三包:quick_inR1_outI2()

    快速模式第三包:quick_inR1_outI2() 文章目录 快速模式第三包:quick_inR1_outI2() 1. 序言 2. quick_inR1_outI2()的处理流程 3. 快速模式第 ...

  8. mysql交互协议解析——mysql包基础数据、mysql包基本格式

    mysql交互协议是开发mysql周边组件常用的协议,如JDBC,libmysql等等. 在此我们要认识到mysql交互协议其实是半双工的交互协议,至于为什么,这里就先挖个小坑,以后再填. 在探讨my ...

  9. NALU数据打RTP包流程详解

    最近在看RTP发送H264数据的文章,感觉很乱,没有比较清晰易懂的教程,自己整理了一下各种资料,备忘! --------Part A  ---- 先说说H264数据,H264在网络传输的是NALU(N ...

随机推荐

  1. 数据库比对工具SQL(表、字段、触发器、索引、视图、存储过程)

    做一个数据库比对小工具,把SQL做一个笔记 SELECT object_id AS ID --表ID,'表' sType,Name --表名FROM sys.tablesORDER BY Name-- ...

  2. Salesforce Integration 概览(四) Batch Data Synchronization(批量数据的同步)

    本篇参考:https://resources.docs.salesforce.com/sfdc/pdf/integration_patterns_and_practices.pdf 前两篇博客讲了一下 ...

  3. spring学习05(代理模式)

    8.代理模式 为什么要学习代理模式,因为AOP的底层机制就是动态代理! 代理模式: 静态代理 动态代理 8.1 静态代理 静态代理角色分析 抽象角色 : 一般使用接口或者抽象类来实现 真实角色 : 被 ...

  4. 2021 年 iOS 应用程序开发七种最佳语言

    移动应用程序现在几乎是每个在线业务的必备品.最新的 StatCounter 数据显示,多达56% 的在线连接是通过移动设备建立的,这使它们高于平板电脑和计算机.更重要的是,同一个消息来源说,其中27% ...

  5. 徒手打造基于Spark的数据工厂(Data Factory):从设计到实现

    在大数据处理和人工智能时代,数据工厂(Data Factory)无疑是一个非常重要的大数据处理平台.市面上也有成熟的相关产品,比如Azure Data Factory,不仅功能强大,而且依托微软的云计 ...

  6. S3C2440—7.存储控制器访问外设

    文章目录 一.内存接口的概念 二.存储控制器(内存控制器) 2.1 什么是存储控制器? 2.2 S3C2440存储控制器介绍 2.3 存储控制器如何处理不同位宽的外设 2.4 怎么确定芯片的访问地址? ...

  7. WPF在圆上画出刻度线

    思路 我们可以使用Ellipse先画出一个圆当背景,然后用Canvas再叠加画上刻度线,就能得到如下的效果 我们先用Ellipse画一个橙色的圆,然后将Canvas的宽度和高度绑定到Ellipse的宽 ...

  8. SQL 练习26

    查询 1990 年出生的学生名单 --方式1 SELECT * FROM Student WHERE Sage BETWEEN '1990-01-01' AND '1990-12-31' --方式2 ...

  9. 【阿菜Writeup】Security Innovation Smart Contract CTF

    赛题地址:https://blockchain-ctf.securityinnovation.com/#/dashboard Donation 源码解析 我们只需要用外部账户调用 withdrawDo ...

  10. 使用SuperSocket实现自定义协议C/S设计

    一.简介: 21世纪是出于互联网+的时代,许多传统行业和硬件挂钩的产业也逐步转向了系统集成智能化,简单来说就是需要软硬件的结合.这时,软硬件通讯便是这里面最主要的技术点,我们需要做到的是让硬件能够听懂 ...