openswan发送状态机分析

1. 函数调用关系

openresty源码 dysrv openswan源码分析_网络设备

2. 函数说明

如果按用户空间、内核空间划分的话,此部分代码更多是运行在内核空间的。

2.1 ipsec_tunnel_init_devices()

该函数主要用来初始化网络设备信息。

int
ipsec_tunnel_init_devices(void)
{
	int i;
	int error;
	/*打印调试信息*/
	KLIPS_PRINT(debug_tunnel & DB_TN_INIT,
		    "klips_debug:ipsec_tunnel_init_devices: "
		    "creating and registering IPSEC_NUM_IF=%u devices, allocating %lu per device, IFNAMSIZ=%u.\n",
		    IPSEC_NUM_IF,
		    (unsigned long) (sizeof(struct net_device) + IFNAMSIZ),
		    IFNAMSIZ);
	/*依次创建接口信息*/
	for(i = 0; i < IPSEC_NUM_IF; i++) {
		error = ipsec_tunnel_createnum(i);

		if(error) break;
	}
	return 0;
}

2.2 ipsec_tunnel_createnum()

该函数主要的功能为:实现一个网络设备。 主要包含如下流程:

  • 申请网络设备资源
  • 绑定网络设备的操作方法集(驱动层的常见用法)
  • 注册网络设备到内核
/*实现一个网络设备:申请资源、绑定操作方法集、注册网络设备*/
int
ipsec_tunnel_createnum(int ifnum)
{
	char name[IFNAMSIZ];
	struct net_device *dev_ipsec;
	int vifentry;

	/*不得超过最大接口数*/
	if(ifnum >= IPSEC_NUM_IFMAX) {
		return -ENOENT;
	}
	/*网络设备是否已经存在,如果存在则错误退出*/
	if(ipsecdevices[ifnum]!=NULL) {
		return -EEXIST;
	}

	/* no identical device *//*记录当前设备接口数*/
	if(ifnum > ipsecdevices_max) {
		ipsecdevices_max=ifnum;
	}
	vifentry = ifnum;

	KLIPS_PRINT(debug_tunnel & DB_TN_INIT,
		    "klips_debug:ipsec_tunnel_init_devices: "
		    "creating and registering IPSEC_NUM_IF=%u device\n",
		    ifnum);

	sprintf(name, IPSEC_DEV_FORMAT, ifnum);
	/*根据内核版本来获取网络设备结构体*/
    #ifdef ALLOC_NETDEV4
        dev_ipsec = alloc_netdev(sizeof(struct ipsecpriv),name,
                                     NET_NAME_UNKNOWN,ipsec_tunnel_netdev_setup);
    #elif defined(alloc_netdev)
	dev_ipsec = alloc_netdev(sizeof(struct ipsecpriv), name, ipsec_tunnel_netdev_setup);
    #else/*最低档次的版本,使用kmalloc分配空间*/
        dev_ipsec = (struct net_device*)kmalloc(sizeof(struct net_device), GFP_KERNEL);
    #endif

	if (dev_ipsec == NULL) {
		printk(KERN_ERR "klips_debug:ipsec_tunnel_init_devices: "
		       "failed to allocate memory for device %s, quitting device init.\n",
		       name);
		return -ENOMEM;
	}
	/*如果内核版本太低,则需要手动将申请的内存清空*/
    #ifndef alloc_netdev
        memset((caddr_t)dev_ipsec, 0, sizeof(struct net_device));
        strncpy(dev_ipsec->name, name, sizeof(dev_ipsec->name));
        #ifdef PAUL_FIXME
            dev_ipsec->next = NULL;
        #endif
    #endif /* alloc_netdev */

/*由于我选用的内核是4.19.y, 因此USE_NETDEV_OPS是已经定义了,我们走else流程*/
/*用来绑定操作方法集*/
    #ifndef USE_NETDEV_OPS
        dev_ipsec->init = &ipsec_tunnel_probe;
    #else
        dev_ipsec->netdev_ops = &klips_device_ops;/*网络设备操作方法集*/
    #endif
	KLIPS_PRINT(debug_tunnel & DB_TN_INIT,
		    "klips_debug:ipsec_tunnel_init_devices: "
		    "registering device %s\n",
		    dev_ipsec->name);

	/* reference and hold the device reference */
	ipsec_dev_hold(dev_ipsec);
	ipsecdevices[vifentry]=dev_ipsec;

	/*注册网络设备*/
	if (register_netdev(dev_ipsec) != 0) {
		KLIPS_PRINT(1 || debug_tunnel & DB_TN_INIT,
			    "klips_debug:ipsec_tunnel_init_devices: "
			    "registering device %s failed, quitting device init.\n",
			    dev_ipsec->name);
		return -EIO;
	} else {
		KLIPS_PRINT(debug_tunnel & DB_TN_INIT,
			    "klips_debug:ipsec_tunnel_init_devices: "
			    "registering device %s succeeded, continuing...\n",
			    dev_ipsec->name);
	}
	return 0;
}

2.3 ipsec_tunnel_probe()

这个函数,如果再较高的内核版中(2.6.30以后)是在驱动安装加载过程中最先执行的(作为net_device_ops的回调函数执行的)。

主要功能:

完成网络设备的初始化。 实际上网络设备的初始化最主要的工作就是:实现若干个函数指针。这也是其他驱动设备、网络设备的都需要的操作。

int
ipsec_tunnel_probe(struct net_device *dev)
{
	ipsec_tunnel_init(dev);//隧道初始化
	return 0;
}
int
ipsec_tunnel_init(struct net_device *dev)
{
	int i;
	struct ipsecpriv *iprv;

	... ...
	#ifdef alloc_netdev
		dev->destructor         = free_netdev;
	#endif
	... ...

    #ifdef HAVE_NETDEV_HEADER_OPS
        dev->header_ops		= NULL;
    #else
        dev->hard_header	= NULL;
        dev->rebuild_header 	= NULL;
        dev->header_cache_update= NULL;
    #endif
    
    #ifdef HAVE_NET_DEVICE_OPS
        dev->netdev_ops         = &klips_device_ops;  /**/
    #else
        dev->open               = ipsec_tunnel_open;
        dev->stop               = ipsec_tunnel_close;
        dev->hard_start_xmit    = ipsec_tunnel_start_xmit;/*发送数据时的函数接口*/
        dev->get_stats          = ipsec_tunnel_get_stats;
    
        #ifdef HAVE_SET_MAC_ADDR
                dev->set_mac_address    = NULL;
        #endif
        dev->do_ioctl           = ipsec_tunnel_ioctl;
        dev->neigh_setup        = ipsec_tunnel_neigh_setup_dev;
	#endif

        dev->hard_header_len 	= 0;
        dev->mtu				= 0;
        dev->addr_len			= 0;
        dev->type		= ARPHRD_VOID; /* ARPHRD_TUNNEL; */ /* ARPHRD_ETHER; */
        dev->tx_queue_len		= 10;		/* Small queue */
    #ifdef IFF_XMIT_DST_RELEASE
        dev->priv_flags	       &= ~IFF_XMIT_DST_RELEASE;
    #endif
	memset((caddr_t)(dev->broadcast),0xFF, ETH_ALEN);	/* what if this is not attached to ethernet? */

	/* New-style flags. */
	dev->flags		= IFF_NOARP /* 0 */ /* Petr Novak */;

	/* We're done.  Have I forgotten anything? */
	return 0;
}

这里可能有疑问:为什么只实现了发送函数指针,而没有接口函数指针。这是因为数据的接收采用的硬件中断的方式(最原始的时候)进行统一的接收处理,而不再单独实现。


以上为IPSec模块在驱动加载过程中的部分处理流程。它实现了一个操作方法集,其中就包括网络设备的方法函数hard_start_xmit, 接下来主要说明发送数据时的处理过程。


3.0 网络设备发送报文处理流程

3.1 ipsec_tunnel_start_xmit()

在2.3节已经知道,在申请网络设备时,会注册实现多个函数指针,其中一个便是dev->hard_start_xmit,这个函数指针是注册到驱动中的,经由该网口转发的报文都会调用到该函数指针。IPSec数据报文便是在这个函数中进行的封装。

/*
 *	This function assumes it is being called from dev_queue_xmit()
 *	and that skb is filled properly by that function.
 */
int
ipsec_tunnel_start_xmit(struct sk_buff *skb, struct net_device *dev)
{
	struct ipsec_xmit_state *ixs = NULL;
	enum ipsec_xmit_value stat;

	KLIPS_PRINT(debug_tunnel & DB_TN_XMIT,
		    "\n\nipsec_tunnel_start_xmit: STARTING");

	stat = IPSEC_XMIT_ERRMEMALLOC;
	/*创建一个新的发送状态对象*/
	ixs = ipsec_xmit_state_new(dev);
	if(ixs == NULL)
		return NETDEV_TX_BUSY;

	ixs->dev = dev;
	ixs->skb = skb;

	/*网络设备正确性检查*/
	stat = ipsec_xmit_sanity_check_ipsec_dev(ixs);
	if(stat != IPSEC_XMIT_OK) {
		goto cleanup;
	}
	/*网络数据包(skb)的正确性检查:1.是否支持V6, 是否需要支持IP选项字段*/
	stat = ipsec_xmit_sanity_check_skb(ixs);
	if(stat != IPSEC_XMIT_OK) {
		goto cleanup;
	}
	/*检测并标识报文中是否包含二层头部*/
	stat = ipsec_tunnel_strip_hard_header(ixs);
	if(stat != IPSEC_XMIT_OK) {
		goto cleanup;
	}
	/*查询IPSec SA*/
	stat = ipsec_tunnel_SAlookup(ixs);
	if(stat != IPSEC_XMIT_OK) {
		KLIPS_PRINT(debug_tunnel & DB_TN_XMIT,
			    "klips_debug:ipsec_tunnel_start_xmit: SAlookup failed: %d\n",
			    stat);
		goto cleanup;
	}
	/*报文封装完毕后(ipsec_xsm状态机),将报文发送出去*/
	ixs->xsm_complete = ipsec_tunnel_xsm_complete;

	ipsec_xsm(ixs);/*发送状态机*/
	return 0;

 cleanup:
	ipsec_xmit_cleanup(ixs);
	ipsec_xmit_state_delete(ixs);
	return 0;
}

3.2 ipsec_tunnel_xsm_complete()

void
ipsec_tunnel_xsm_complete(
	struct ipsec_xmit_state *ixs,
	enum ipsec_xmit_value stat)
{
	unsigned char nexthdr;
	int nexthdroff;
	if(stat != IPSEC_XMIT_OK) {
		if(stat == IPSEC_XMIT_PASS) {
			goto bypass;
		}

		KLIPS_PRINT(debug_tunnel & DB_TN_XMIT,
				"klips_debug:ipsec_tunnel_start_xmit: encap_bundle failed: %d\n",
				stat);
		goto cleanup;
	}
	... ...
	{
		nexthdr = osw_ip4_hdr(ixs)->protocol;
		nexthdroff = 0;
		if ((ntohs(osw_ip4_hdr(ixs)->frag_off) & IP_OFFSET) == 0)
			nexthdroff = (ixs->iph + (osw_ip4_hdr(ixs)->ihl<<2)) -
				(void *)ixs->skb->data;
		ixs->matcher.sen_type = SENT_IP4;
		ixs->matcher.sen_ip_src.s_addr = osw_ip4_hdr(ixs)->saddr;
		ixs->matcher.sen_ip_dst.s_addr = osw_ip4_hdr(ixs)->daddr;
		ixs->matcher.sen_proto = nexthdr;
	}
	/*提取封装后的端口信息*/
	ipsec_extract_ports(ixs->skb, nexthdr, nexthdroff, &ixs->matcher);

	spin_lock_bh(&eroute_lock);
	/*重新查找eroute路由表*/
	ixs->eroute = ipsec_findroute(&ixs->matcher);
	if(ixs->eroute) {
		ixs->outgoing_said = ixs->eroute->er_said;
		ixs->eroute_pid = ixs->eroute->er_pid;
		ixs->eroute->er_count++;
		ixs->eroute->er_lasttime = jiffies/HZ;
	}
	spin_unlock_bh(&eroute_lock);

	/*ipsec_xmit_init1中实现ixs->orgeds的初始化*/
	if (/*((ixs->orgdst != ixs->newdst) || (ixs->orgsrc != ixs->newsrc))*/
			ip_address_cmp(&ixs->orgedst, &ixs->outgoing_said.dst) != 0 &&
			!ip_address_isany(&ixs->outgoing_said.dst) &&
			ixs->eroute) {
		KLIPS_PRINT(debug_tunnel & DB_TN_XMIT,
			"klips_debug:ipsec_tunnel_start_xmit: "
			"We are recursing here.\n");
		ipsec_xsm(ixs);/*如果需要(再次匹配到其他的eroute),再次进入状态机进行封装*/
		return;
	}
	
#ifdef NAT_TRAVERSAL
	stat = ipsec_nat_encap(ixs);/*如果中间经过了NAT,需要NAT-T封装*/
	if(stat != IPSEC_XMIT_OK) {
		goto cleanup;
	}
#endif

	stat = ipsec_tunnel_restore_hard_header(ixs);/*添加二层头*/
	if(stat != IPSEC_XMIT_OK) {
		goto cleanup;
	}

bypass:
	stat = ipsec_tunnel_send(ixs);/*发送报文*/

cleanup:
	ipsec_xmit_cleanup(ixs);
	ipsec_xmit_state_delete(ixs);
}