本文档的Copyleft归yfydz所有,使用GPL发布,可以自由拷贝,转载,转载时请保持文档的完整性,严禁用于任何商业用途。
msn: yfydz_no1@hotmail.com
来源:http://yfydz.cublog.cn

13. 主模式协商

IKE协商处理函数基本都在programs/pluto/ipsec_doi.c中定义。

13.1 过程

主模式:

        Initiator                          Responder
       -----------                        -----------
        HDR, SA                     -->
main_outI1(发送初始化数据1)
                                    <--    HDR, SA
                                     main_inI1_outR1(接收到初始化数据1, 发送响应数据1)
        HDR, KE, Ni                 -->
main_inR1_outI2(接收到响应数据1,发送初始化数据2)
                                    <--    HDR, KE, Nr
                                     main_inI2_outR2(接收到初始化数据2, 发送响应数据2)
        HDR*, IDii, [ CERT, ] SIG_I -->
main_inR2_outI3(接收到响应数据2,发送初始化数据3)
                                    <--    HDR*, IDir, [ CERT, ] SIG_R
                                     main_inI3_outR3(接收到初始化数据3, 发送响应数据3)
main_inR3(接收到响应数据3, 完成主模式协商, 建立ISAKMP SA)

连接发起函数为ipsecdoi_initiate()函数, 在其中调用了main_outI1(), aggr_outI1()和quick_outI1()函数分别发起主模式,野蛮模式和快速模式的协商。
 
13.2 发起方的第一个初始包
 
/* Initiate an Oakley Main Mode exchange.
 * --> HDR;SA
 * Note: this is not called from demux.c
 */
static stf_status
main_outI1(int whack_sock
    , struct connection *c
    , struct state *predecessor
    , lset_t policy
    , unsigned long try
    , enum crypto_importance importance)
{
// 生成新状态空间
    struct state *st = new_state();
    pb_stream reply; /* not actually a reply, but you know what I mean */
    pb_stream rbody;

// 以下设置状态参数
    /* set up new state */
// 状态相关连接
    st->st_connection = c;
// 设置状态中的本地和对端地址端口信息和网卡位置信息
    set_state_ike_endpoints(st, c);
    set_cur_state(st); /* we must reset before exit */
// IPSEC策略合法策略范围
    st->st_policy = policy & ~POLICY_IPSEC_MASK;
// 状态和whack通信的域套接口
    st->st_whack_sock = whack_sock;
// 尝试次数
    st->st_try = try;
// 状态的类型: STATE_MAIN_I1
    st->st_state = STATE_MAIN_I1;
    st->st_import = importance;
// 初始化本地cookie,8字节的随机数
    get_cookie(TRUE, st->st_icookie, COOKIE_SIZE, &c->spd.that.host_addr);
// 将新状态插入到状态哈希表
    insert_state(st); /* needs cookies, connection, and msgid (0) */

// 如果有IPSEC策略, 增加pending结构, pending结构的作用以后再详细分析
    if (HAS_IPSEC_POLICY(policy))
 add_pending(dup_any(whack_sock), st, c, policy, 1
     , predecessor == NULL? SOS_NOBODY : predecessor->st_serialno);

// 是完全新的协商还是更新的协商
    if (predecessor == NULL)
 openswan_log("initiating Main Mode");
    else
 openswan_log("initiating Main Mode to replace #%lu", predecessor->st_serialno);
    /* set up reply */
// 初始化响应包
    init_pbs(&reply, reply_buffer, sizeof(reply_buffer), "reply packet");
    /* HDR out */
    {
// 填充ISAKMP头
 struct isakmp_hdr hdr;
 zero(&hdr); /* default to 0 */
// 版本
 hdr.isa_version = ISAKMP_MAJOR_VERSION << ISA_MAJ_SHIFT | ISAKMP_MINOR_VERSION;
// 下一个头: SA
 hdr.isa_np = ISAKMP_NEXT_SA;
// 交换类型
 hdr.isa_xchg = ISAKMP_XCHG_IDPROT;
// cookie
 memcpy(hdr.isa_icookie, st->st_icookie, COOKIE_SIZE);
 /* R-cookie, flags and MessageID are left zero */
// 填充到reply结构到rbody, reply这里只是起个中介作用
 if (!out_struct(&hdr, &isakmp_hdr_desc, &reply, &rbody))
 {
     reset_cur_state();
     return STF_INTERNAL_ERROR;
 }
    }
    /* SA out */
    {
// 填充SA信息
 u_char *sa_start = rbody.cur;
// 策略索引
 int    policy_index = POLICY_ISAKMP(policy
         , c->spd.this.xauth_server
         , c->spd.this.xauth_client);
 /* if we  have an OpenPGP certificate we assume an
  * OpenPGP peer and have to send the Vendor ID
  */
 int np = (SEND_PLUTO_VID || c->spd.this.cert.type == CERT_PGP) ?
     ISAKMP_NEXT_VID : ISAKMP_NEXT_NONE;
// 填写SA结构到rbody
 if (!out_sa(&rbody
      , &oakley_sadb[policy_index], st, TRUE, FALSE, np))
 {
     openswan_log("outsa fail");
     reset_cur_state();
     return STF_INTERNAL_ERROR;
 }
 /* save initiator SA for later HASH */
 passert(st->st_p1isa.ptr == NULL); /* no leak!  (MUST be first time) */
 clonetochunk(st->st_p1isa, sa_start, rbody.cur - sa_start
     , "sa in main_outI1");
    }
    if (SEND_PLUTO_VID || c->spd.this.cert.type == CERT_PGP)
    {
 char *vendorid = (c->spd.this.cert.type == CERT_PGP) ?
     pgp_vendorid : pluto_vendorid;
// 填充提供者ID结构
 if (!out_generic_raw(ISAKMP_NEXT_NONE, &isakmp_vendor_id_desc, &rbody
 , vendorid, strlen(vendorid), "Vendor ID"))
     return STF_INTERNAL_ERROR;
    }
    /* ALWAYS Announce our ability to do Dead Peer Detection to the peer */
// 填充DPD
    if (!out_modify_previous_np(ISAKMP_NEXT_VID, &rbody))
        return STF_INTERNAL_ERROR;
// 填充DPD提供者信息
    if( !out_generic_raw(ISAKMP_NEXT_NONE, &isakmp_vendor_id_desc
    , &rbody, dpd_vendorid, dpd_vendorid_len, "V_ID"))
        return STF_INTERNAL_ERROR;

#ifdef NAT_TRAVERSAL
    DBG(DBG_NATT, DBG_log("nat traversal enabled: %d", nat_traversal_enabled));
    if (nat_traversal_enabled) {
// 如果NAT穿越有效, 添加NAT穿越VID信息
 /* Add supported NAT-Traversal VID */
 if (!nat_traversal_add_vid(ISAKMP_NEXT_NONE, &rbody)) {
     reset_cur_state();
     return STF_INTERNAL_ERROR;
 }
    }
#endif

#ifdef XAUTH
    if(c->spd.this.xauth_client || c->spd.this.xauth_server)
    {
// 如果是XAUTH的客户端或服务器, 填充XAUTH的VID信息
 if(!out_vendorid(ISAKMP_NEXT_NONE, &rbody, VID_MISC_XAUTH))
 {
     return STF_INTERNAL_ERROR;
 }
    }
#endif
 
// 结构收尾
    close_message(&rbody);
    close_output_pbs(&reply);
    clonetochunk(st->st_tpacket, reply.start, pbs_offset(&reply)
 , "reply packet for main_outI1");
    /* Transmit */
// 发送数据包
    send_packet(st, "main_outI1", TRUE);
    /* Set up a retransmission event, half a minute henceforth */
// 删除状态结构原来的事件(insert_state时初始化的事件)
    delete_event(st);
// 重新设置状态的超时事件: 10秒无响应时重新发送
    event_schedule(EVENT_RETRANSMIT, EVENT_RETRANSMIT_DELAY_0, st);
    if (predecessor != NULL)
    {
// 更新pending结构
 update_pending(predecessor, st);
 whack_log(RC_NEW_STATE + STATE_MAIN_I1
     , "%s: initiate, replacing #%lu"
     , enum_name(&state_names, st->st_state)
     , predecessor->st_serialno);
    }
    else
    {
 whack_log(RC_NEW_STATE + STATE_MAIN_I1
     , "%s: initiate", enum_name(&state_names, st->st_state));
    }
// 复位当前状态
    reset_cur_state();
    return STF_OK;
}
 
13.3 响应方收到发起方初始化包, 发送第一响应包

/* State Transition Functions.
 *
 * The definition of state_microcode_table in demux.c is a good
 * overview of these routines.
 *
 * - Called from process_packet; result handled by complete_state_transition
 * - struct state_microcode member "processor" points to these
 * - these routine definitionss are in state order
 * - these routines must be restartable from any point of error return:
 *   beware of memory allocated before any error.
 * - output HDR is usually emitted by process_packet (if state_microcode
 *   member first_out_payload isn't ISAKMP_NEXT_NONE).
 *
 * The transition functions' functions include:
 * - process and judge payloads
 * - update st_iv (result of decryption is in st_new_iv)
 * - build reply packet
 */
/* Handle a Main Mode Oakley first packet (responder side).
 * HDR;SA --> HDR;SA
 */
stf_status
main_inI1_outR1(struct msg_digest *md)
{
// 接收到的数据包的SA载荷
    struct payload_digest *const sa_pd = md->chain[ISAKMP_NEXT_SA];
    struct state *st;
    struct connection *c;
// 准备发送的SA载荷缓冲区
    pb_stream r_sa_pbs;
    /* we are looking for an OpenPGP Vendor ID sent by the peer */
// 对方是否是openpgp比标志
    bool openpgp_peer = FALSE;
/* Determin how many Vendor ID payloads we will be sending */
    int next;
// 发送的VID数量,至少会发送DPD的VID
    int numvidtosend = 1;  /* we always send DPD VID */
#ifdef NAT_TRAVERSAL
// NAT穿越有效, VID数量增加
    if (md->quirks.nat_traversal_vid && nat_traversal_enabled) {
 DBG(DBG_NATT, DBG_log("nat-t detected, sending nat-t VID"));
 numvidtosend++;
    }
#endif
#if SEND_PLUTO_VID || defined(openpgp_peer)
// 如果预定义了PLUUO VID或openpgp对端, 增加VID数量
    numvidtosend++;
#endif
#if defined(openpgp_peer)
    {
     struct payload_digest *p;
// 遍历接收到的数据包中的VID载荷链表
     for (p = md->chain[ISAKMP_NEXT_VID]; p != NULL; p = p->next)
  {
      int vid_len = sizeof(pgp_vendorid) - 1 < pbs_left(&p->pbs)
   ? sizeof(pgp_vendorid) - 1 : pbs_left(&p->pbs);
// 检查是否是pgp VID, 是则设置openpgp_peer标志为真     
      if (memcmp(pgp_vendorid, p->pbs.cur, vid_len) == 0)
   {
       openpgp_peer = TRUE;
       DBG(DBG_PARSING,
    DBG_log("we have an OpenPGP peer")
    )
    }
  }
    }
#endif
   
    /* random source ports are handled by find_host_connection */
// 根据接收到的数据包的目的地址,目的端口,源地址,源端口查找连接结构
    c = find_host_connection(&md->iface->ip_addr, pluto_port
        , &md->sender
        , md->sender_port);
    if (c == NULL)
    {
// 如果没找到
 /* See if a wildcarded connection can be found.
  * We cannot pick the right connection, so we're making a guess.
  * All Road Warrior connections are fair game:
  * we pick the first we come across (if any).
  * If we don't find any, we pick the first opportunistic
  * with the smallest subnet that includes the peer.
  * There is, of course, no necessary relationship between
  * an Initiator's address and that of its client,
  * but Food Groups kind of assumes one.
  */
 {
// 可能是那种未定义对方地址的动态连接, 将源地址条件置空,重新查找连接,
// 可能会找到多个连接结构,返回的是一个链表
     struct connection *d;
     d = find_host_connection(&md->iface->ip_addr, pluto_port
         , (ip_address*)NULL
         , md->sender_port);
// 遍历链表
     for (; d != NULL; d = d->hp_next)
     {
  if (d->kind == CK_GROUP)
  {
// GROUP类型连接不考虑
      /* ignore */
  }
  else
  {
// 如果连接类型为模板型而且没定义OE, 该连接可用于处理该数据包, 连接找到,中断循环
      if (d->kind == CK_TEMPLATE && !(d->policy & POLICY_OPPO))
      {
   /* must be Road Warrior: we have a winner */
   c = d;
   break;
      }
      /* Opportunistic or Shunt: pick tightest match */
// 比较数据包源地址是否匹配连接中对方地址的定义
      if (addrinsubnet(&md->sender, &d->spd.that.client)
// 而且目前可用连接为空,或目前可用连接的地址范围比新找到的连接范围大, 更新可用连接
      && (c == NULL || !subnetinsubnet(&c->spd.that.client, &d->spd.that.client)))
   c = d;
  }
     }
 }
// 如果没找到连接, 该数据包处理不了, 返回忽略该数据包
 if (c == NULL)
 {
     loglog(RC_LOG_SERIOUS, "initial Main Mode message received on %s:%u"
  " but no connection has been authorized"
  , ip_str(&md->iface->ip_addr), ntohs(portof(&md->iface->ip_addr)));
     /* XXX notification is in order! */
     return STF_IGNORE;
 }
 else if (c->kind != CK_TEMPLATE)
// 否则如果不是模板类型连接(动态连接), 返回忽略
 {
     loglog(RC_LOG_SERIOUS, "initial Main Mode message received on %s:%u"
  " but \"%s\" forbids connection"
  , ip_str(&md->iface->ip_addr), pluto_port, c->name);
     /* XXX notification is in order! */
     return STF_IGNORE;
 }
 else
 {
// 将模板连接进行实例化, 根据模板连接新生成一个新连接结构, 填充对方地址
     /* Create a temporary connection that is a copy of this one.
      * His ID isn't declared yet.
      */
     c = rw_instantiate(c, &md->sender
          , NULL, NULL);
 }
    }
// 到此处已经有了可用的连接结构
#ifdef XAUTH
    if(c->spd.this.xauth_server || c->spd.this.xauth_client)
    {
// 如果本地是XAUTH客户端或服务器,VID数量增加
        numvidtosend++;
    }
#endif
    /* Set up state */
// 新分配状态结构
    md->st = st = new_state();
#ifdef XAUTH
    passert(st->st_oakley.xauth == 0);
#endif
// 添加状态结构中的相关数据,下面的操作和main_outI1很类似了
// 连接
    st->st_connection = c;
// 对方地址,端口
    st->st_remoteaddr = md->sender;
    st->st_remoteport = md->sender_port;
// 本地地址,端口
    st->st_localaddr  = md->iface->ip_addr;
    st->st_localport  = md->iface->port;
// 网卡位置
    st->st_interface  = md->iface;
// 设置为当前状态
    set_cur_state(st); /* (caller will reset cur_state) */
// 尝试次数
    st->st_try = 0; /* not our job to try again from start */
// 策略掩码
    st->st_policy = c->policy & ~POLICY_IPSEC_MASK; /* only as accurate as connection */
// 状态类型为R0(接收到初始化包)
    st->st_state = STATE_MAIN_R0;
// 复制对方的cookie
    memcpy(st->st_icookie, md->hdr.isa_icookie, COOKIE_SIZE);
// 生成本地的cookie
    get_cookie(FALSE, st->st_rcookie, COOKIE_SIZE, &md->sender);
// 将新状态插入到状态哈希表
    insert_state(st); /* needs cookies, connection, and msgid (0) */
    st->st_doi = ISAKMP_DOI_IPSEC;
    st->st_situation = SIT_IDENTITY_ONLY; /* We only support this */
    /* copy the quirks we might have accumulated */
// 复制特殊标志: xauth_ack_msgid, modecfg_pull_mode, nat_traversal_vid
    copy_quirks(&st->quirks,&md->quirks);

// 记录一下主模式响应日志
    if ((c->kind == CK_INSTANCE) && (c->spd.that.host_port_specific))
    {
       openswan_log("responding to Main Mode from unknown peer %s:%u"
     , ip_str(&c->spd.that.host_addr), c->spd.that.host_port);
    }
    else if (c->kind == CK_INSTANCE)
    {
 openswan_log("responding to Main Mode from unknown peer %s"
     , ip_str(&c->spd.that.host_addr));
    }
    else
    {
 openswan_log("responding to Main Mode");
    }
    /* parse_isakmp_sa also spits out a winning SA into our reply,
     * so we have to build our md->reply and emit HDR before calling it.
     */
// 以下开始填充要发送的回应包信息
    /* HDR out.
     * We can't leave this to comm_handle() because we must
     * fill in the cookie.
     */
    {
 struct isakmp_hdr r_hdr = md->hdr;
// 填充ISAKMP头结构信息
 r_hdr.isa_flags &= ~ISAKMP_FLAG_COMMIT; /* we won't ever turn on this bit */
 memcpy(r_hdr.isa_rcookie, st->st_rcookie, COOKIE_SIZE);
 r_hdr.isa_np = ISAKMP_NEXT_SA;
 if (!out_struct(&r_hdr, &isakmp_hdr_desc, &md->reply, &md->rbody))
     return STF_INTERNAL_ERROR;
    }
    /* start of SA out */
    {
 struct isakmp_sa r_sa = sa_pd->payload.sa;
// 填充SA结构信息
 /* if we to send any VID, then set the NEXT payload correctly */
 r_sa.isasa_np = numvidtosend ? ISAKMP_NEXT_VID : ISAKMP_NEXT_NONE;
 if (!out_struct(&r_sa, &isakmp_sa_desc, &md->rbody, &r_sa_pbs))
     return STF_INTERNAL_ERROR;
    }
    /* SA body in and out */
// 解析收到的数据包中的SA信息,并生成回应SA
    RETURN_STF_FAILURE(parse_isakmp_sa_body(&sa_pd->pbs, &sa_pd->payload.sa
         , &r_sa_pbs, FALSE, st));
    if (SEND_PLUTO_VID || openpgp_peer)
    {
 char *vendorid = (openpgp_peer) ?
     pgp_vendorid : pluto_vendorid;
// 填充提供者ID
 next = --numvidtosend ? ISAKMP_NEXT_VID : ISAKMP_NEXT_NONE;
 if (!out_generic_raw(next, &isakmp_vendor_id_desc, &md->rbody
        , vendorid, strlen(vendorid), "Vendor ID"))
     return STF_INTERNAL_ERROR;
    }
    /*
     * NOW SEND VENDOR ID payloads
     */
      
    /* Announce our ability to do RFC 3706 Dead Peer Detection */
    next = --numvidtosend ? ISAKMP_NEXT_VID : ISAKMP_NEXT_NONE;
// 填充DPD提供者VID
    if( !out_generic_raw(next, &isakmp_vendor_id_desc
    , &md->rbody, dpd_vendorid
    , dpd_vendorid_len, "DPP Vendor ID"))
      return STF_INTERNAL_ERROR;
#ifdef XAUTH
    /* If XAUTH is required, insert here Vendor ID */
    if(c->spd.this.xauth_server || c->spd.this.xauth_client)
    {
// 填充XAUTH VID
     next = --numvidtosend ? ISAKMP_NEXT_VID : ISAKMP_NEXT_NONE;
     if (!out_vendorid(next, &md->rbody, VID_MISC_XAUTH))
        return STF_INTERNAL_ERROR;
    }
#endif
#ifdef NAT_TRAVERSAL
    DBG(DBG_CONTROLMORE, DBG_log("sender checking NAT-t: %d and %d"
    , nat_traversal_enabled
    , md->quirks.nat_traversal_vid));
    if (md->quirks.nat_traversal_vid && nat_traversal_enabled) {
// // 填充NAT穿越VID
        next = --numvidtosend ? ISAKMP_NEXT_VID : ISAKMP_NEXT_NONE;
 /* reply if NAT-Traversal draft is supported */
 st->hidden_variables.st_nat_traversal = nat_traversal_vid_to_method(md->quirks.nat_traversal_vid);
 if ((st->hidden_variables.st_nat_traversal) && (!out_vendorid(next,
     &md->rbody, md->quirks.nat_traversal_vid))) {
     return STF_INTERNAL_ERROR;
 }
    }
#endif

#ifdef DEBUG
    /* if we are not 0 then something went very wrong above */   
    if(numvidtosend != 0) {
 openswan_log("payload alignment problem please check the code in main_inI1_outR1 (num=%d)", numvidtosend);
    }
#endif
// 结束数据包
    close_message(&md->rbody);
    /* save initiator SA for HASH */
    clonereplacechunk(st->st_p1isa, sa_pd->pbs.start, pbs_room(&sa_pd->pbs), "sa in main_inI1_outR1()");
// 构造回应包成功, 返回
    return STF_OK;
}
 
...... 待续 ......