openswan中的in_struct和out_struct函数

openswan中的in_struct和out_struct函数

文章目录

    • openswan中的in_struct和out_struct函数
      • 1. 花絮
      • 2. in_struct代码实现分析
      • 3. 它到底几个意思?
        • 3.1 为什么这么做?
        • 3.2 它的实现原理
          • 3.2.1 sakmp头部描述说明
          • 3.2.2 sakmp头部载荷取值范围
          • 3.2.3 isakmp头部中标记位处理
          • 3.2.4 参数obj_pbs干什么的?

1. 花絮

​ 有什么比openswan中的in_struct和out_struct更让人难以理解的呢??? 如果存在的话,那就是:女朋友为啥又生气了?

openswan中的in_struct和out_struct函数_第1张图片

算了,不管了,今天我是找不到她生气的原因了;还是把openswan中的in_struct()函数整理下吧,感觉这个更容易些
openswan中的in_struct和out_struct函数_第2张图片

在学习openswan源码的过程中,有两个函数是想逃不能逃的,但是有很难看懂它是怎么个原理,它的功能很明确,但是就算如此,还是看不懂它的原理。我看这个接口花了很长的时间。哎,没办法,基础有点菜。下面就把个人的心得分享下:

2. in_struct代码实现分析

在源代码中in_struct和out_struct都有一段较为详细的注释:

/* "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.
 */

这里对in_struct进行描述:它的功能也比较明确:

将网络字节序结构体转换为主机字节序结构体

这里假设网络结构体和主机结构体拥有相同的对齐方式和大小, 这要求他们所有的填充字节都是明确的。

如果参数obj_pbs是非空,则会创建一个新的pb_stream结构。它用来描述inssd的部分,但是cur指针是指向已经解析完毕的数据后后一个字节。

操作成功返回TRUE

是的,它转换的是结构体,而不是单个的short类型或者int类型。这样处理更加方便,模块化。简单的说:

htonl(), htons()分别转换的为四个字节,两个字节类型的变量,而in_struct(), out_struct()一次性转换一个结构体。只要你传入的结构体描述信息(struct_desc)正确就可以


也许我们对这个种方式不习惯,但是它的好处大大滴。既然使用了人家代码,那就接收人家的规则。


很多刚开始看的人不明白为什么要使用“obj_pbs”形参,既然解析完毕了为什么还要保存该信息呢???

openswan中的in_struct和out_struct函数_第3张图片

实际上这个操作在变长的载荷中是必须的,因为变长部分数据并没有被转换到struct_ptr中。下面慢慢说明:

我是直接在源码上进行的注释:

/****************************************************
* 功能: 将网络字节序结构体转化为主机字节序结构体
* struct_ptr:  解析后的输出数据
* sd        :  描述信息			
* ins       :  输入的网络字节序数据流
* obj_pbs   :  指向该结构后面尚未解析的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;/*待解析数据开始*/
	/*确保有待解析的数据大小足够,否则无法成功解析为sd描述的大小*/
    if (ins->roof - cur < (ptrdiff_t)sd->size)
    {
        ugh = builddiag("not enough room in input packet for %s"
                        " (remain=%li, sd->size=%zu)"
                        , sd->name, (long int)(ins->roof - cur), sd->size);

    }
    else
    {
    	/*root指向待解析数据的末尾*/
	u_int8_t *roof = cur + sd->size;    /* may be changed by a length field *//*未考虑变长属性*/
	u_int8_t *outp = struct_ptr;/*outp指向输出结构体*/
	bool immediate = FALSE;
	field_desc *fp;

	for (fp = sd->fields; ugh == NULL; fp++)
	{
	    size_t i = fp->size;
	    /*passert条件成立,继续执行;不成立,直接退出*/
	    passert(ins->roof - cur >= (ptrdiff_t)i);
	    passert(cur - ins->cur <= (ptrdiff_t)(sd->size - i));
	    passert(outp - (cur - ins->cur) == struct_ptr);/*解析长度相同*/

	    switch (fp->field_type)
	    {
	    case ft_mbz:	/* must be zero *//*全零域,*/
		for (; i != 0; i--)
		{
		    if (*cur++ != 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_zig:	/* should be zero, ignore if not *//*全零域,如果不是可以忽略其数值*/
		for (; i != 0; i--)
		{
		    if (*cur++ != 0)
		    {
			openswan_log("byte %d of %s should have been zero, but was not"
			    , (int) (cur - ins->cur), sd->name);
			/*
			 * We cannot zeroize it, it would break our hash calculation
			 * *cur = '\0';
			 */
		    }
		    *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_np:	        /* value from an enumeration */
	    case ft_np_in:      /* 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;

			/* Reportedly fails on arm, see bug #775 */
			for (; i != 0; i--)
			    n = (n << BITS_PER_BYTE) | *cur++;/*根据该属性的长度来获取值: 单字节、双字节、四字节*/
											   /*此处已经实现了字节序的转换(ntohl, ntohs)*/

			switch (fp->field_type)/*ikev2_trans_attr_fields*/
			{
				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)/*通过最高位判断该载荷类型,0:变长,1:定长*/
					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))/*判断报文中某些bit标志位是否合法*/
				    {
					ugh = builddiag("bitset %s of %s has unknown member(s): %s"
					    , fp->name, sd->name, bitnamesof(fp->desc, n));
				    }
				    break;
				default:
					break;
			}
			
			i = fp->size;
			switch (i)	/*将转后字节序后的报文中的数据存储到输出结构struct_ptr中*/
			{
			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数据的开始和结束位置,
		    *然后更新obj_pbs中cur指针为已经解析的位置(确切的说是已经成功解析部分的下一个字节)
		    *但是如果有变长部分,则cur指向的为变长部分第一个字节;它没有拷贝到传入的struct_ptr中
		    ******************************************************************************/
		    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;/*更新cur指针到数据的sd结构之后,如果不存在变长载荷应该与cur指的位置相同;存在的话不相同*/
		/*注意: 变长载荷数据部分没有继续解析且不能通过ins来获取,但是可以通过obj_pbs来获取变长载荷的数据部分*/
		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, "%s", ugh);
    return FALSE;
}

3. 它到底几个意思?

​ 其实就算看懂了我上面的注释,也许还有有很多疑问:

  • 它为什么这么实现?向通常的处理方式那样不行吗?它的目的是什么
  • 它这样实现的原理是什么?

要理解几个问题,还真的花费点时间。如果说你对ISAKMP报文格式比较熟悉的话,那么看起来会容易很多;相反如果还不是那么清楚,就有点吃力。而我属于后者,花费了很长时间才略有眉目,因此抓紧记录下来:

3.1 为什么这么做?

它最大的优点我认为是模块化处理,方便维护、扩展。举个例子:

我想在ISAKMP载荷中添加一种新的载荷类型(这是平常的需求),如果我们采用常用的那种网络数据包的解析方式:解析二层头–>解析三层头–>解析四层头—>解析四层数据。这并没有问题,但是如果我要添加一个四层协议呢,是不是需要在所有的四层处理流程中进行修改、添加参数是否合法的判断、解析报文等等。

而采用in_struct这种方式就不需要,只需要按格式添加相应的协议结构体描述信息(该信息中包括了参数取值范围,字段长度,类型等等),它会自动完成参数合法性检验、字节序转换等等(当然正常报文的处理都需要添加)。而不需要像普通的那样每次都得判断参数是否合法、逐个进行字节序转换,极大的提高了代码的利用率

当然代价也很明显:我等菜鸡们看不懂!!!

3.2 它的实现原理

这个目前我只能把自己的理解分享出来,也许比较浅,但是可以节省点时间。

首先说struct_desc这个结构体,它是解析的关键:

openswan中的in_struct和out_struct函数_第4张图片

序号 结构体名称 作用
1 struct desc 用来描述一个载荷信息:如isakmp头部、sa载荷等等
2 field_desc 用来描述载荷中的一个成员信息(某一个自字段)
3 field_type 用来描述载荷中某个成员的类型(与实际的字段类型有关)
4 enum_names 该字段的取值范围以及说明(用来判断该成员取值是否合法)
序号 枚举类型 说明
1 ft_mbz 全0类型,比如某一保留位添加0
2 ft_nat 自然数
3 ft_len 长度字段
4 ft_lv 长度/值字段(主要用于变长载荷), 这个处理又有点特殊
5 ft_raw 原始报文,例如cookie,没有字节序问题,直接拷贝即可
6 ft_np 下一个载荷
7 ft_end 结构体描述结束标记
8

3.2.1 sakmp头部描述说明

ISAKMP头部格式如下:
openswan中的in_struct和out_struct函数_第5张图片

它的结构体描述信息如下:

static field_desc isa_fields[] = {
    { ft_raw, COOKIE_SIZE, "initiator cookie", NULL },/*无字节序取值范围问题,可以直接拷贝*/
    { ft_raw, COOKIE_SIZE, "responder cookie", NULL },/*无字节序取值范围问题,可以直接拷贝*/
    { ft_np_in,8/BITS_PER_BYTE, "next payload type", &payload_names },
    { ft_enum, 8/BITS_PER_BYTE, "ISAKMP version", &version_names },
    { ft_enum, 8/BITS_PER_BYTE, "exchange type", &exchange_names },
    { ft_set, 8/BITS_PER_BYTE, "flags", flag_bit_names },
    { ft_raw, 32/BITS_PER_BYTE, "message ID", NULL },
    { ft_len, 32/BITS_PER_BYTE, "length", NULL },
    { ft_end, 0, NULL, NULL }
};

struct_desc isakmp_hdr_desc = { "ISAKMP Message", isa_fields, sizeof(struct isakmp_hdr) };

说明:

可以看出isa_fields中的每一个成员都对应ISAKMP头部中的一个成员

  • Initiator cookie : 无字节序问题、没有取值范围的要求, 类型为ft_raw, 长度为8 (COOKIE_SIZE);
  • Responder cookie : 无字节序问题、没有取值范围的要求, 类型为ft_raw, 长度为8 (COOKIE_SIZE);
  • Next Payload: 下一载荷,有取值范围要求,因此使用payload_names来说明取值范围
  • 版本号:ISAKMP的主次版本号也有要求,因此使用version_names来说明取值范围
  • flags: 标记位:加密、认证、提交。有比特位的要求,因此使用flag_bit_names来说明bit位是否使用
  • MessageID: 一串字符串,无字节序和取值问题,类型为ft_raw
  • Length: 报文长度,为正整数,有字节序问题,因此需要进行字节序转换
3.2.2 sakmp头部载荷取值范围

在上面我们可以知道,ISAKMP头部载荷使用payload_names结构体来说明,下面我们先找出

ISAKMP头部载荷都包括哪些,然后再分析payload_names

  • ISAKMP头部载荷
    openswan中的in_struct和out_struct函数_第6张图片
    openswan中的in_struct和out_struct函数_第7张图片
  • payload_names结构体
enum_names payload_names =
{ ISAKMP_NEXT_NONE, ISAKMP_NEXT_NATOA_RFC, payload_name, &payload_names_ikev2_main};

static enum_names payload_names_ikev2_main =
{ ISAKMP_NEXT_v2SA, ISAKMP_NEXT_v2EAP, payload_name_ikev2_main, &payload_names_nat_d};

static enum_names payload_names_nat_d =
{ ISAKMP_NEXT_NATD_DRAFTS, ISAKMP_NEXT_NATOA_DRAFTS, payload_name_nat_d, NULL};

实际上这三个变量定义了一个有三个节点的链表,头节点为payload_names:第一个节点是payload_names,第二根节点为payload_names_ikev2_main, 第三个节点为payload_name_nat_d。他们是按功能将上述ISAKMP头部载荷区分划分成三类的。

enum next_payload_types {
	ISAKMP_NEXT_NONE     =  0,	/* No other payload following */
	ISAKMP_NEXT_SA       =  1,	/* Security Association */
	ISAKMP_NEXT_P        =  2,	/* Proposal */
	ISAKMP_NEXT_T        =  3,	/* Transform */
	ISAKMP_NEXT_KE       =  4,	/* Key Exchange */
	ISAKMP_NEXT_ID       =  5,	/* Identification */
	ISAKMP_NEXT_CERT     =  6,	/* Certificate */
	ISAKMP_NEXT_CR       =  7,	/* Certificate Request */
	ISAKMP_NEXT_HASH     =  8,	/* Hash */
	ISAKMP_NEXT_SIG      =  9,	/* Signature */
	ISAKMP_NEXT_NONCE    =  10,	/* Nonce */
	ISAKMP_NEXT_N        =  11,	/* Notification */
	ISAKMP_NEXT_D        =  12,	/* Delete */
	ISAKMP_NEXT_VID      =  13,	/* Vendor ID */
	ISAKMP_NEXT_ATTR     =  14,       /* Mode config Attribute */
	ISAKMP_NEXT_NATD_BADDRAFTS =15, /* NAT-Traversal: NAT-D (bad drafts) */
                                /* !!! Conflicts with RFC 3547 */
	ISAKMP_NEXT_NATD_RFC  = 20,       /* NAT-Traversal: NAT-D (rfc) */
	ISAKMP_NEXT_NATOA_RFC = 21,       /* NAT-Traversal: NAT-OA (rfc) */
-----------------------------------------------------------------------------
	ISAKMP_NEXT_v2SA  = 33,          /* security association */
	ISAKMP_NEXT_v2KE  = 34,          /* key exchange payload */
	ISAKMP_NEXT_v2IDi = 35,          /* Initiator ID payload */
	ISAKMP_NEXT_v2IDr = 36,          /* Responder ID payload */
	ISAKMP_NEXT_v2CERT= 37,          /* Certificate */
	ISAKMP_NEXT_v2CERTREQ= 38,       /* Certificate Request */
	ISAKMP_NEXT_v2AUTH= 39,          /* Authentication */
	ISAKMP_NEXT_v2Ni  = 40,          /* Nonce - initiator */
	ISAKMP_NEXT_v2Nr  = 40,          /* Nonce - responder */
	ISAKMP_NEXT_v2N   = 41,          /* Notify */
	ISAKMP_NEXT_v2D   = 42,          /* Delete */
	ISAKMP_NEXT_v2V   = 43,          /* Vendor ID */
	ISAKMP_NEXT_v2TSi = 44,          /* Traffic Selector, initiator */
	ISAKMP_NEXT_v2TSr = 45,          /* Traffic Selector, responder */
	ISAKMP_NEXT_v2E   = 46,          /* Encrypted payload */
	ISAKMP_NEXT_v2CP  = 47,          /* Configuration payload (MODECFG) */
	ISAKMP_NEXT_v2EAP = 48,          /* Extensible authentication*/
----------------------------------------------------------------------------
	/* SPECIAL CASES */
	ISAKMP_NEXT_NATD_DRAFTS  = 130,   /* NAT-Traversal: NAT-D (drafts) */
	ISAKMP_NEXT_NATOA_DRAFTS = 131   /* NAT-Traversal: NAT-OA (drafts) */
};

在代码中检验枚举类型是否有效的接口为enum_name()

const char *
enum_name_default(enum_names *ed, unsigned long val, const char *def)
{
    enum_names	*p;

    for (p = ed; p != NULL; p = p->en_next_range)/*通过en_next_range访问下一个节点*/
	if (p->en_first <= val && val <= p->en_last)/*取值在正常范围内,则返回name信息*/
	    return p->en_names[val - p->en_first];
    return def;/*否则返回NULL*/
}

const char *
enum_name(enum_names *ed, unsigned long val)
{
	return enum_name_default(ed, val, NULL);
}
3.2.3 isakmp头部中标记位处理

ISAKMP头部Flags标志位中,正常只有低三位是有效的(代码中实现了6位),其他bit位如果被设置那么就是错误的报文。

openswan中的in_struct和out_struct函数_第8张图片
in_struct中使用了testset()接口在判断是否有其他无效的bit位被设置,如果被设置则反会对应的错误信息。只是他的处理方式风格依然比较奇特:

Flags是否有效是通过flag_bit_names来描述的:

const char *const flag_bit_names[] = {
    "ISAKMP_FLAG_ENCRYPTION",         /* bit 0 */
    "ISAKMP_FLAG_COMMIT",             /* bit 1 */
    "bit 2",                          /* bit 2 */
    "ISAKMP_FLAG_INIT",               /* bit 3 */
    "ISAKMP_FLAG_VERSION",            /* bit 4 */
    "ISAKMP_FLAG_RESPONSE",           /* bit 5 */
    NULL
    };

好吧,这段代码有点费解,看了很久。。。

也没有那么难,就是通过n是否为NULL,来判断有效的bit位是否已经结束。如果n已经为NULL,但是val却是非零,那么肯定的是val的bit位已经超出,此时返回FALSE; 如果val为0后,n依然不为NULL,则说明val的bit位是合法的。

bool
testset(const char *const table[], lset_t val)
{
    lset_t bit;
    const char *const *tp;

    for (tp = table, bit = 01; val != 0; bit <<= 1, tp++)
    {
	const char *n = *tp;

	if (n == NULL || ((val & bit) && *n == '\0'))/*n指向table成员的第一个字符*/
	    return FALSE;
	val &= ~bit;/*已检测的位清零*/
    }
    return TRUE;
}

自己写了个例子测试了下:(只能说他们的想法很别致,只要别乱写描述信息就行)

#include 

void testset(int value)
{
	char *table[]={
		"AAAAAAAAAAAAAAAA",
		"BBBBBBBBBBBBBBBB",
		"CCCCCCCCCCCCCCCC",
		"DDDDDDDDDDDDDDDD",
		NULL,
	};
	printf("value = %d\n", value);
	int bit = 1;
	char **tp = table;
	for(bit=1; value !=0; tp++, bit<<=1){

		char *n = *tp;

		if( n == NULL || (value & bit) && *n=='0'){
			printf("bit out of range!!!\n");
			return;
		}
		printf("%c---%d\n", *n, *n);
		value &= ~bit;
	}
	printf("value's bit is valid\n\n\n\n");
}
3.2.4 参数obj_pbs干什么的?

obj_pbs如果我没有理解错,是为了保留当前结构体的信息(未转换时的指针,大小信息), 同时将cur指针更新到已经解析完毕的位置;这个主要是为了处理有变长载荷的情形,由于变长载荷数据信息没有解析,因此将obj_pbs->cur指向变长载荷数据部分。
openswan中的in_struct和out_struct函数_第9张图片
代码中ft_af_loose_enumft_af_enum的处理便是来源于此。
openswan中的in_struct和out_struct函数_第10张图片

最高位AF如果为1,则是定长;如果AF为0,则为变长,此时需要在ft_lv时更新长度信息。

		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 */

注意: 这里并没有将变长信息解析拷贝,而是将其头部指针保留下来,供后续再次解析处理。


用一张图片结束吧:

openswan中的in_struct和out_struct函数_第11张图片

你可能感兴趣的:(openswan源码分析,in_struct,out_struct,openswan,ipsec)