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

9. 协商总体概述

9.1 概述

pluto启动时,whack将ipsec.conf中的连接信息发送给pluto(rcv_whack.c, whack_handle()函数)进
行连接的初始化(connection.c, add_connection()函数,initiate_connection()函数)。如果连接
是静态的,也就是连接双方的地址是固定好的,则启动IKE协商过程(ipsec_doi.c,
ipsecdoi_initiate()函数);如果是动态的,即一方地址没固定,则只是进入监听状态,监听UDP500
、4500端口。进入协商状态时,接收到对方数据包时(demux.c, process_packet()函数),双方的状态
转换通过demux.c中的state_microcode_table[]数组进行转换,各状态下的处理函数主要在
ipsec_doi.c中定义, 描述如下:
 
第一阶段:
主模式:

        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)
 
野蛮模式:

        Initiator                          Responder
       -----------                        -----------
        HDR, SA, KE, Ni, IDii       -->
aggr_outI1(发送初始化数据1)
                                    <--    HDR, SA, KE, Nr, IDir,
                                                [ CERT, ] SIG_R
                           aggr_inI1_outR1_psk(接收到初始化数据1, 发送响应数据1)
                           aggr_inI1_outR1_rsasig(根据认证方式分别用不同函数处理)
        HDR, [ CERT, ] SIG_I        -->
aggr_inR1_outI2(接收到响应数据1,发送初始化数据2)
                           aggr_inI2(接收到初始化数据2,完成野蛮模式协商, 建立ISAKMP SA)
 
第2阶段:
快速模式:

        Initiator                        Responder
       -----------                      -----------
        HDR*, HASH(1), SA0, SA1, Ni,
          [, KE ] [, IDci, IDcr ] -->
qouck_outI1(发送初始化数据1)
                                  <--    HDR*, HASH(2), SA0, SA1, Nr,
                                            [, KE ] [, IDci, IDcr ]
                                quick_inI1_outR1(接收到初始化数据1, 发送响应数据1)
        HDR*, HASH(3)             -->
quick_inR1_outI2(接收到响应数据1,发送初始化数据2)
                                quick_inI2 (接收到初始化数据2,完成快速模式协商,
                                            建立IPSec SA)

XAUTH, MODE_CFG在第一阶段完成,第2阶段前进行。

为了描述协商, 用到的最重要的数据结构就是连接(connection)和状态(state), 连接是通过配置文
件ipsec.conf中的连接定义的, 如果双方地址都非空, 就是一个真实连接, 可直接发起; 否则就是连
接模板, 只有真正连接发生时根据模板填充原来为空的参数(实体化)后生成新的连接实体,但总体说
是一种静态的概念。而状态是用来描述具体连接过程的各种中间状态,是动态概念,即使是相同的连
接也可能因为使用不同的加密认证算法,不同的协议而生成不同的状态,因此我觉得pluto的状态和
连接的关系就类似进程和程序的关系。

9.2 协商数据格式

为更好理解协商过程,对协商数据格式是需要大致有所了解,但不用每个结构都详细说明,只要了解
了基本结构,具体到各种详细类型的查询只要看RFC就行了,而pluto中对各种协商载荷的数据结构在
lib/libopenswan/packet.c中定义。

标准的协商数据格式和取值在FC2408, 2409等文件中定义, 包括一个ISAKMP数据头和若干载荷, 载荷
数为1个到多个:

ISAKMP数据头格式:
                         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
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    !                          Initiator                            !
    !                            Cookie                             !
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    !                          Responder                            !
    !                            Cookie                             !
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    !  Next Payload ! MjVer ! MnVer ! Exchange Type !     Flags     !
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    !                          Message ID                           !
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    !                            Length                             !
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
数据头包括:
8字节的发起方cookie, 不能都为0;
8字节的响应方cookie, 如果是初始协商, 为0;
1字节的下一个载荷类型, 取值如下, 当为0时表示是最后一个载荷:
                        Next Payload Type       Value
                    NONE                           0
                    Security Association (SA)      1
                    Proposal (P)                   2
                    Transform (T)                  3
                    Key Exchange (KE)              4
                    Identification (ID)            5
                    Certificate (CERT)             6
                    Certificate Request (CR)       7
                    Hash (HASH)                    8
                    Signature (SIG)                9
                    Nonce (NONCE)                 10
                    Notification (N)              11
                    Delete (D)                    12
                    Vendor ID (VID)               13
                    RESERVED                   14 - 127
                    Private USE               128 - 255
4位主版本, 对IKEv1, 取值为1;
4位次版本, 对IKEv1, 取值为0;
1字节交换类型, 取值如下:
                            Exchange Type      Value
                         NONE                    0
                         Base                    1
                         Identity Protection     2
                         Authentication Only     3
                         Aggressive              4
                         Informational           5
                         ISAKMP Future Use     6 - 31
                         DOI Specific Use     32 - 239
                         Private Use         240 - 255
1字节的标志;
     0 1 2 3 4 5 6 7
    +-+-+-+-+-+-+-+-+
    |E|C|A|         |
    +-+-+-+-+-+-+-+-+
 E: Encryption; C: Commit; A: AUthentication
4字节的消息ID;
4字节表示的数据包长度, 这是所有数据的总长, 包含ISAKMP数据头和所有载荷;
 
各种载荷的通用头结构为:
                            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        !
       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
1字节的下一个载荷类型, 取值如上;
1字节保留, 必须为0;
2字节的载荷长度, 包含通用头和载荷数据本身;

各种类型载荷的数据格式在RFC2408中有详细定义.

举例:
以下是pluto的日志记录的IKE协商初始包的情况,这个数据包是由一个Windows的IPSec客户端发出的
:
 | *received 183 bytes from 192.168.5.49:500 on eth2 (port=500)
 |   11 bc 56 f2  25 e9 ff 1c  00 00 00 00  00 00 00 00
 |   01 10 02 00  00 00 00 00  00 00 00 b7  0d 00 00 74
 |   00 00 00 01  00 00 00 01  00 00 00 68  01 01 00 03
 |   03 00 00 20  01 01 00 00  80 01 00 05  80 02 00 01
 |   80 03 fd e9  80 04 00 02  80 0b 00 01  80 0c 70 80
 |   03 00 00 20  02 01 00 00  80 01 00 05  80 02 00 02
 |   80 03 fd e9  80 04 00 02  80 0b 00 01  80 0c 70 80
 |   00 00 00 20  03 01 00 00  80 01 00 06  80 02 00 01
 |   80 03 fd e9  80 04 00 02  80 0b 00 01  80 0c 70 80
 |   0d 00 00 13  54 68 64 61  73 63 6f 6d  56 50 4e 30
 |   31 30 30 00  00 00 14 44  85 15 2d 18  b6 bb cd 0b
 |   e8 a8 46 95  79 dd cc
 | **parse ISAKMP Message:
 |    initiator cookie:
 |   11 bc 56 f2  25 e9 ff 1c
 |    responder cookie:
 |   00 00 00 00  00 00 00 00
 |    next payload type: ISAKMP_NEXT_SA
 |    ISAKMP version: ISAKMP Version 1.0
 |    exchange type: ISAKMP_XCHG_IDPROT
 |    flags: none
 |    message ID:  00 00 00 00
 |    length: 183
 |  processing packet with exchange type=ISAKMP_XCHG_IDPROT (2)
 | np=1 and sd=0x80d8fd0
 | ***parse ISAKMP Security Association Payload:
 |    next payload type: ISAKMP_NEXT_VID
 |    length: 116
 |    DOI: ISAKMP_DOI_IPSEC
 | np=13 and sd=0x80d95ec
 | ***parse ISAKMP Vendor ID Payload:
 |    next payload type: ISAKMP_NEXT_VID
 |    length: 19
 | np=13 and sd=0x80d95ec
 | ***parse ISAKMP Vendor ID Payload:
 |    next payload type: ISAKMP_NEXT_NONE
 |    length: 20

从数据包中先看ISAKMP头信息:
 |   11 bc 56 f2  25 e9 ff 1c  00 00 00 00  00 00 00 00
 |   01 10 02 00  00 00 00 00  00 00 00 b7
发起者cookie: 11 bc 56 f2  25 e9 ff 1c
响应者cookie: 00 00 00 00  00 00 00 00, 全0表示这是协商起始包
下一载荷: 01, 表示是SA载荷
主版本号: 1
次版本号: 0
交换类型: 02, 表示Identity Protection, 身份保护
标志: 0
消息ID: 00 00 00 00
数据长度: 0x00000b7, 183字节, 从长度不能看出到底有多少载荷, 必须一个一个分析直到遇到下一
载荷字段值为0的载荷, 表示是最后一个载荷

以下是各级载荷的数据信息:
第1载荷: SA载荷
 |                                          0d 00 00 74
 |   00 00 00 01  00 00 00 01  00 00 00 68  01 01 00 03
 |   03 00 00 20  01 01 00 00  80 01 00 05  80 02 00 01
 |   80 03 fd e9  80 04 00 02  80 0b 00 01  80 0c 70 80
 |   03 00 00 20  02 01 00 00  80 01 00 05  80 02 00 02
 |   80 03 fd e9  80 04 00 02  80 0b 00 01  80 0c 70 80
 |   00 00 00 20  03 01 00 00  80 01 00 06  80 02 00 01
 |   80 03 fd e9  80 04 00 02  80 0b 00 01  80 0c 70 80

SA载荷结构定义如下(RFC2408):
                          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                           ~
     !                                                               !
     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

SA的详细格式说明在RFC2407中定义:

    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 (IPSEC)               |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   !                       Situation (bitmap)                      !
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   !                    Labeled Domain Identifier                  !
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   !  Secrecy Length (in octets)   !           RESERVED            !
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   ~                        Secrecy Level                          ~
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   ! Secrecy Cat. Length (in bits) !           RESERVED            !
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   ~                    Secrecy Category Bitmap                    ~
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   ! Integrity Length (in octets)  !           RESERVED            !
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   ~                       Integrity Level                         ~
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   ! Integ. Cat. Length (in bits)  !           RESERVED            !
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   ~                  Integrity Category Bitmap                    ~
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

保留字段必须为0

下一载荷: 0x0d, Vendor ID, 提供者ID
长度: 0x0074, 116字节
DOI: 0x00000001, 表示是IPSec DOI, 为0表示ISAKMP DOI
状况(situation):  00 00 00 01 , 表示是SIT_IDENTITY_ONLY, 只包含标志信息,, 由于没支持
SIT_SECRECY(0x02)和 SIT_INTEGRITY(0x04), 因此不包含Labeled Domain Identifier域
Labeled Domain Identifier: 空
秘密长度: 0x00 00 00 68,  十进制104, 表示ID部分104字节, 含4字节长度
后面100字节应该是秘密信息, 不过似乎pluto没有定义situation更多信息,只分析到DOI:
/* lib/libopenswan/packet.c */
static field_desc isasa_fields[] = {
    { ft_enum, 8/BITS_PER_BYTE, "next payload type", &payload_names },
    { ft_mbz, 8/BITS_PER_BYTE, NULL, NULL },
    { ft_len, 16/BITS_PER_BYTE, "length", NULL },
    { ft_enum, 32/BITS_PER_BYTE, "DOI", &doi_names },
// 没继续解析situation信息
    { ft_end, 0, NULL, NULL }
};
 

第2载荷: Vendor ID
 |   0d 00 00 13  54 68 64 61  73 63 6f 6d  56 50 4e 30
 |   31 30 30
IPSec提供者ID载荷格式如下:
                          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        !
     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     !                                                               !
     ~                        Vendor ID (VID)                        ~
     !                                                               !
     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
保留字段必须为0

下一载荷: 0x0d, Vendor ID, 提供者ID
长度: 0x0013, 19字节(含通用载荷头4字节)
VID: 54 68 64 61  73 63 6f 6d  56 50 4e 30 31 30 30

第3载荷: Vendor ID

 |            00  00 00 14 44  85 15 2d 18  b6 bb cd 0b
 |   e8 a8 46 95  79 dd cc

下一载荷: 0x00, 表示已经是最后一个载荷了
长度: 0x0014, 20字节(含通用载荷头4字节)
VID:  44  85 15 2d 18  b6 bb cd 0b  e8 a8 46 95  79 dd cc

只要了解了协商数据的结构格式,就可以自己分析其他协商数据的格式,打开pluto的日志全记录,
pluto会把接收和发送出的所有数据都打印到日志中供分析。
 
9.3 载荷字段的读取和构造

由前面介绍IKE协商数据的载荷是一个一个顺序读取的, 而不是一下子全部读出为一个结构, 而发出
过程则是反向过程, 因此pluto需要的是循环顺序读取或构造载荷结构, 定义的统一的库函数来实现
,具体代码在lib/libopenswan/packet.c中。

9.3.1 读取

/* "parse" a network struct into a host struct.
 *
 * This code assumes that the network and host structure
 * members have the same alignment and size!  This requires
 * that all padding be explicit.
 *
 * If obj_pbs is supplied, a new pb_stream is created for the
 * variable part of the structure (this depends on their
 * being one length field in the structure).  The cursor of this
 * new PBS is set to after the parsed part of the struct.
 *
 * This routine returns TRUE iff it succeeds.
 */
// 该函数从网络数据包中读取一个结构
// struct_ptr是目标结构, sd是对该结构的描述, ins是输入数据
bool
in_struct(void *struct_ptr, struct_desc *sd
, pb_stream *ins, pb_stream *obj_pbs)
{
    err_t ugh = NULL;
// 当前输入数据位置
    u_int8_t *cur = ins->cur;
    if (ins->roof - cur < (ptrdiff_t)sd->size)
    {
// 输入数据的长度不足以构造一个目标结构, 失败
// pluto经常使用这样的错误处理,不是用整数的错误号来标识错误,而是用错误描述字符串来
// 描述错误, 如果是空指针, 就说明操作成功了, 非空, 字符串就是说明错误原因, 还是面向
// 人而不是机器进行错误处理
        ugh = builddiag("not enough room in input packet for %s"
                        " (remain=%d, sd->size=%d)"
                        , sd->name, ins->roof - cur, sd->size);
    }
    else
    {
// 最大可能结束位置, 由于可能有填充数据, 所以实际结束位置可能会变化
 u_int8_t *roof = cur + sd->size;    /* may be changed by a length field */
// 输出的数据的结构起点
 u_int8_t *outp = struct_ptr;
 bool immediate = FALSE;
 field_desc *fp;
// 循环对目标结构的每个域的描述
 for (fp = sd->fields; ugh == NULL; fp++)
 {
// 该域的长度
     size_t i = fp->size;
     passert(ins->roof - cur >= (ptrdiff_t)i);
     passert(cur - ins->cur <= (ptrdiff_t)(sd->size - i));
     passert(outp - (cur - ins->cur) == struct_ptr);
#if 0
     DBG(DBG_PARSING, DBG_log("%d %s"
  , (int) (cur - ins->cur), fp->name == NULL? "" : fp->name));
#endif
// 域类型
     switch (fp->field_type)
     {
     case ft_mbz: /* must be zero */
// 必须为0的域, 如保留(RESERVED)域
  for (; i != 0; i--)
  {
      if (*cur++ != 0)
// 如果这些域中有非0值存在, 生成错误
      {
   ugh = builddiag("byte %d of %s must be zero, but is not"
       , (int) (cur - ins->cur), sd->name);
   break;
      }
      *outp++ = '\0'; /* probably redundant */
  }
  break;
// 自然数类型
     case ft_nat: /* natural number (may be 0) */
// 长度类型
     case ft_len: /* length of this struct and any following crud */
// 长度/值类型
     case ft_lv:  /* length/value field of attribute */
// 枚举类型
     case ft_enum: /* value from an enumeration */
// 宽松枚举类型
     case ft_loose_enum: /* value from an enumeration with only some names known
*/
// 属性格式/值类型
     case ft_af_enum: /* Attribute Format + value from an enumeration */
// 宽松属性格式/值类型
     case ft_af_loose_enum: /* Attribute Format + value from an enumeration
*/
     case ft_set: /* bits representing set */
     {
  u_int32_t n = 0;
// 一个字节一个字节的转换, 相当于完成了htons(l)的功能, 很多情况是长度/类型值
  for (; i != 0; i--)
      n = (n << BITS_PER_BYTE) | *cur++;

// 以下检查一下值的合法性和一些标志情况
  switch (fp->field_type)
  {
  case ft_len: /* length of this struct and any following crud */
  case ft_lv: /* length/value field of attribute */
  {
// 读取长度值
// 有的属性长度值包含长度字段本身,有的不包含
      u_int32_t len = fp->field_type == ft_len? n
   : immediate? sd->size : n + sd->size;
// 检查长度值是否合法
      if (len < sd->size)
      {
   ugh = builddiag("%s of %s is smaller than minimum"
       , fp->name, sd->name);
      }
// 检查输入数据的实际长度和标称的长度是否匹配
      else if (pbs_left(ins) < len)
      {
   ugh = builddiag("%s of %s is larger than can fit"
       , fp->name, sd->name);
      }
      else
      {
// 定义新的数据结束位置
   roof = ins->cur + len;
      }
      break;
  }
  case ft_af_loose_enum: /* Attribute Format + value from an enumeration
*/
      if ((n & ISAKMP_ATTR_AF_MASK) == ISAKMP_ATTR_AF_TV)
   immediate = TRUE;
      break;
  case ft_af_enum: /* Attribute Format + value from an enumeration
*/
      if ((n & ISAKMP_ATTR_AF_MASK) == ISAKMP_ATTR_AF_TV)
   immediate = TRUE;
      /* FALL THROUGH */
  case ft_enum: /* value from an enumeration */
// 检查类型值是否合法
      if (enum_name(fp->desc, n) == NULL)
      {
   ugh = builddiag("%s of %s has an unknown value: %lu"
       , fp->name, sd->name, (unsigned long)n);
      }
      /* FALL THROUGH */
  case ft_loose_enum: /* value from an enumeration with only some
names known */
      break;
  case ft_set: /* bits representing set */
// 检查标志是否正确
      if (!testset(fp->desc, n))
      {
   ugh = builddiag("bitset %s of %s has unknown member(s): %s"
       , fp->name, sd->name, bitnamesof(fp->desc, n));
      }
      break;
  default:
   break;
  }
// 根据域长度获取实际的数值,分8/16/32位数
  i = fp->size;
  switch (i)
  {
  case 8/BITS_PER_BYTE:
      *(u_int8_t *)outp = n;
      break;
  case 16/BITS_PER_BYTE:
      *(u_int16_t *)outp = n;
      break;
  case 32/BITS_PER_BYTE:
      *(u_int32_t *)outp = n;
      break;
  default:
      bad_case(i);
  }
// 跳到下一域字段
  outp += i;
  break;
     }
// 原始字段, 直接复制域长度的数据
     case ft_raw: /* bytes to be left in network-order */
  for (; i != 0; i--)
  {
      *outp++ = *cur++;
  }
  break;
// 结束标识
     case ft_end: /* end of field list */
  passert(cur == ins->cur + sd->size);
  if (obj_pbs != NULL)
  {
      init_pbs(obj_pbs, ins->cur, roof - ins->cur, sd->name);
      obj_pbs->container = ins;
      obj_pbs->desc = sd;
      obj_pbs->cur = cur;
  }
  ins->cur = roof;
// 打印解析的DEBUG信息, 返回成功
  DBG(DBG_PARSING
      , DBG_prefix_print_struct(ins, "parse ", struct_ptr, sd, TRUE));
  return TRUE;
     default:
  bad_case(fp->field_type);
     }
 }
    }
// 错误记录, 返回错误
    /* some failure got us here: report it */
    openswan_loglog(RC_LOG_SERIOUS, ugh);
    return FALSE;
}
 
9.3.2 输出
 
/* "emit" a host struct into a network packet.
 *
 * This code assumes that the network and host structure
 * members have the same alignment and size!  This requires
 * that all padding be explicit.
 *
 * If obj_pbs is non-NULL, its pbs describes a new output stream set up
 * to contain the object.  The cursor will be left at the variable part.
 * This new stream must subsequently be finalized by close_output_pbs().
 *
 * The value of any field of type ft_len is computed, not taken
 * from the input struct.  The length is actually filled in when
 * the object's output stream is finalized.  If obj_pbs is NULL,
 * finalization is done by out_struct before it returns.
 *
 * This routine returns TRUE iff it succeeds.
 */
// 将实际内部结构转换为数据包中的字段
// struct_ptr是输入结构, sd是结构描述, outs是输出结果
bool
out_struct(const void *struct_ptr, struct_desc *sd
    , pb_stream *outs, pb_stream *obj_pbs)
{
    err_t ugh = NULL;
// 读取数据的起始位置
    const u_int8_t *inp = struct_ptr;
// 输出起始点
    u_int8_t *cur = outs->cur;
    DBG(DBG_EMITTING
 , DBG_prefix_print_struct(outs, "emit ", struct_ptr, sd, obj_pbs==NULL));
// 检查输出缓冲中是否有足空间
    if (outs->roof - cur < (ptrdiff_t)sd->size)
    {
 ugh = builddiag("not enough room left in output packet to place %s"
     , sd->name);
    }
    else
    {
 bool immediate = FALSE;
 pb_stream obj;
// 域描述指针
 field_desc *fp;
 obj.lenfld = NULL;  /* until a length field is discovered */
 obj.lenfld_desc = NULL;
// 遍历结构的所有域
 for (fp = sd->fields; ugh == NULL; fp++)
 {
// 域长度
     size_t i = fp->size;
     passert(outs->roof - cur >= (ptrdiff_t)i);
     passert(cur - outs->cur <= (ptrdiff_t)(sd->size - i));
     passert(inp - (cur - outs->cur) == struct_ptr);
#if 0
     DBG(DBG_EMITTING, DBG_log("%d %s"
  , (int) (cur - outs->cur), fp->name == NULL? "" : fp->name);
#endif
// 按域类型处理
     switch (fp->field_type)
     {
     case ft_mbz: /* must be zero */
// 全0域
  inp += i;
  for (; i != 0; i--)
      *cur++ = '\0';
  break;
     case ft_nat: /* natural number (may be 0) */
     case ft_len: /* length of this struct and any following crud */
     case ft_lv:  /* length/value field of attribute */
     case ft_enum: /* value from an enumeration */
     case ft_loose_enum: /* value from an enumeration with only some names known
*/
     case ft_af_enum: /* Attribute Format + value from an enumeration */
     case ft_af_loose_enum: /* Attribute Format + value from an enumeration */
     case ft_set: /* bits representing set */
     {
  u_int32_t n = 0;
// 长度/属性值的描述
  switch (i)
// 先根据字段长度获取具体的数值
  {
  case 8/BITS_PER_BYTE:
      n = *(const u_int8_t *)inp;
      break;
  case 16/BITS_PER_BYTE:
      n = *(const u_int16_t *)inp;
      break;
  case 32/BITS_PER_BYTE:
      n = *(const u_int32_t *)inp;
      break;
  default:
      bad_case(i);
  }
  switch (fp->field_type)
  {
  case ft_len: /* length of this struct and any following crud */
  case ft_lv: /* length/value field of attribute */
      if (immediate)
   break; /* not a length */
      /* We can't check the length because it will likely
       * be filled in after variable part is supplied.
       * We do record where this is so that it can be
       * filled in by a subsequent close_output_pbs().
       */
      passert(obj.lenfld == NULL); /* only one ft_len allowed */
      obj.lenfld = cur;
      obj.lenfld_desc = fp;
      break;
  case ft_af_loose_enum: /* Attribute Format + value from an enumeration
*/
      if ((n & ISAKMP_ATTR_AF_MASK) == ISAKMP_ATTR_AF_TV)
   immediate = TRUE;
      break;
  case ft_af_enum: /* Attribute Format + value from an enumeration
*/
      if ((n & ISAKMP_ATTR_AF_MASK) == ISAKMP_ATTR_AF_TV)
   immediate = TRUE;
      /* FALL THROUGH */
  case ft_enum: /* value from an enumeration */
      if (enum_name(fp->desc, n) == NULL)
      {
   ugh = builddiag("%s of %s has an unknown value: %lu"
       , fp->name, sd->name, (unsigned long)n);
      }
      /* FALL THROUGH */
  case ft_loose_enum: /* value from an enumeration with only some
names known */
      break;
  case ft_set: /* bits representing set */
      if (!testset(fp->desc, n))
      {
   ugh = builddiag("bitset %s of %s has unknown member(s): %s"
       , fp->name, sd->name, bitnamesof(fp->desc, n));
      }
      break;
  default:
      break;
  }
// 将每个字节填充到输出结构中
  while (i-- != 0)
  {
      cur[i] = (u_int8_t)n;
      n >>= BITS_PER_BYTE;
  }
// 长度统计增加
  inp += fp->size;
  cur += fp->size;
  break;
     }
     case ft_raw: /* bytes to be left in network-order */
// 原始数据, 直接拷贝
  for (; i != 0; i--)
      *cur++ = *inp++;
  break;
     case ft_end: /* end of field list */
// 结束处理
  passert(cur == outs->cur + sd->size);
// 填充这个数据包字节流结构信息
  obj.container = outs;
  obj.desc = sd;
  obj.name = sd->name;
  obj.start = outs->cur;
  obj.cur = cur;
  obj.roof = outs->roof; /* limit of possible */
  /* obj.lenfld and obj.lenfld_desc already set */
  if (obj_pbs == NULL)
  {
// 收尾操作
      close_output_pbs(&obj); /* fill in length field, if any */
  }
  else
  {
      /* We set outs->cur to outs->roof so that
       * any attempt to output something into outs
       * before obj is closed will trigger an error.
       */
      outs->cur = outs->roof;
// 将数据包字节流结构返回
      *obj_pbs = obj;
  }
// 返回成功
  return TRUE;
     default:
  bad_case(fp->field_type);
     }
 }
    }
// 错误, 记录日志, 返回错误
    /* some failure got us here: report it */
    loglog(RC_LOG_SERIOUS, ugh); /* ??? serious, but errno not relevant */
    return FALSE;
}

...... 待续 ......