轻松搞定云下虚拟化网络流量
- 2020 年 3 月 3 日
- 笔记
概述
虚拟交换机是在虚拟化场景下提供报文交换的虚拟网络设备,它可以提供像物理交换机一样的报文交换及流量镜像功能。在虚拟化环境下,需要同时支持IDS和IPS时,这种旁路的流量镜像的方式则无法达到要求。因此,该发明方法用来解决非旁路(阻断式)处理虚拟交换机报文的问题,该方法可以完成对进入虚拟交换机的报文进行劫持,支持分析模块多线程并行处理报文,并支持再次送回虚拟交换机继续交换流程,或直接将报文丢弃,达到阻断网络通信的目的。
现有技术的技术方案
在虚拟交换机中加入NetFilter处理框架,使用NFQUEUE将虚拟交换机的流量通过套接字传送到应用层处理。
如上图,在交换机转发过程中,加入NetFilter框架的NFQUEUE处理逻辑。网络报文将会在NFQUEUE提供的队列中进行排队,等待通过套接字逐次发送到应用层。应用层通过Netlink套接字接口逐次读取报文,并逐次将处理结果返回给NFQUEUE模块。NFQUEUE模块根据应用层的决策,决定是转发还是丢弃报文。
现有技术的缺点主要有两点,一是在虚拟交换机加入Netfilter处理框架,对虚拟交换机侵入性较大,增加了不必要的技术复杂度,技术成本较高(常用的虚拟交换机并非原生支持NetFilter处理框架)。二是NFQUEUE使用单队列缓存网络报文,并使用netlink套接字通信,因此应用层的网络报文分析处理模块为了保证报文时序有效性,不利于多线程并发处理,不能够充分利用处理器资源并发处理网络报文,导致网络报文在单一队列中积压,并导致后续的网络报文直接丢弃,丢包率较高,严重降低了虚拟交换机的网络报文交换性能。在本技术方案中,将会解决这两个问题,对虚拟交换机最低的侵入性,技术复杂度低,并达到高并发处理网络报文的目的。
本技术方案的详细阐述
在产品中应用该方案时,提供的文件包括一个内核模块和一个应用层动态库及API。网络报文分析处理模块首先利用命令将内核模块加载起来,然后用提供的API,调用一系列的操作方法。
关键操作包括:
1. 设置支持最大并发线程数目(非必须,可以使用默认值)。
2. 设置网络报文分发到多线程的策略(非必须,可以使用默认策略)。
3. 设置报文处理函数。
4. 在报文处理函数中,可以决定放行还是丢弃当前报文或延迟处理。
该技术是网络报文分析处理的基础技术,在之上可以构建应用层防火墙、Web检测、入侵检测等任何基于流量分析处理的安全防护检测功能。
整体架构图
如上图,网络报文进入虚拟交换机后,在收包入口处,首先检查是否有注册的收包处理函数,如果没有,则继续交换机的处理流程。当存在注册的处理函数时,则调用该收包处理函数,并不再继续后续的交换机流程,网络报文被收包处理函数劫持。
注册的收包处理函数拿到网络报文后,首先从共享内存队列中申请一个内存块,将网络报文拷贝到内存块,然后将内存块索引通知到应用层处理。应用层的网络报文分析模块可以根据内存块的索引,访问到内存块及网络报文的内容,分析完毕后将该报文索引重新返回到共享队列,通知内核态处理。内核态线程收到通知后,再次从共享队列中获取网络报文的裁决信息,如果是放行,则调用交换机的发送函数,将数据包发送给虚拟交换机继续执行后续流程,否则直接释放网络报文和内存块,达到阻断网络报文的目的。
方案中主要包括四部分,分别是基于内存映射的共享收发队列、注册的收包处理函数、多线程报文处理接口、内核态报文处理线程。
【基于内存映射的共享收发队列】
在该方案中,由于在应用层处理报文更方便,有更多的分析工具可以利用,因此需要将网络报文传送到应用层。为了提高传输网络报文性能,避免经过冗长的网络协议栈路径,使用共享内存的方式提供应用层和内核态高速通信,在内核态和应用层可以同步读写数据,使用单生产者单消费者队列模型,因此不需要任何互斥锁就可以保证操作一致性,大大提高了内核态和应用层的同步效率。
将共享内存分为两个部分:一、队列区域;二、存储区域;队列区域由若干个队列组成;存储区域是存放数据的空间。而队列中只是存放数据在存储区域的索引偏移地址。所以通过队列元素中的偏移值可以在存储区域中找到数据的存储空间。
共享内存初始化是由内核的字符设备驱动负责处理的,由应用层处理接口负责开启创建共享内存管理功能。具体步骤为:
Ø 应用层处理接口通过字符设备的ioctl调用,通知内核申请“共享内存”,并告知需要的共享内存大小,队列的个数,队列的大小信息。
Ø 内核接收到用户空间的请求,首先判断当前是否已经创建过共享内存信息。如果已经创建,判断当前的共享内存信息是否与用户空间的传递的信息一致。如果不一致,删除以前的共享内存,并按照要求重新创建。
Ø 应用层处理接口调用mmap系统调用,将内核创建的共享内存映射到用户空间中。
Ø 应用层处理接口从共享内存中提取必要的队列信息,并进行保存。
存储区域是一块固定空间大小的内存区域。将空间分为若干个固定的block,每个block的大小为2048字节。
每个block中分为两个部分:block head信息和数据内容信息。
block head的内容:
Ø 报文长度:当前报文内容的大小。
Ø 裁决结果:报文是放行还是丢弃。
Ø 状态信息:当前内存块的使用状态。
通信队列分为发送队列和接收队列,分别用RX和TX表示,TX用于发包,RX用于收包。RX和TX总是成对的,支持多少并发处理线程,就有多少对TX和TX通道,每个处理线程使用一对通信通道。
队列设计为环形队列,环形队列具有简单和高效的作用,并且使用单一的生产者和消费者模型,避免了因同步而产生的开销。
将队列划分为多个frame,frame是一个拥有固定大小的空间。
每个frame都一个状态用来表示“使用者”,如上图所示其中“绿色的”frame表示内核可以使用,而用户空间程序不可以操作这些frame;其中“黄色的”frame表示用户空间可以使用,而内核空间不可以操作这些frame。对队列的操作,即是通信的过程。
每个frame中都存放着数据的索引(在存储区域的位置)。另外预留一部空间用于存储队列的相关信息,如队列对数,队列长度,各个队列信息(入队偏移,出队偏移)等。
存储区域是将整块内存分割成相同大小的内存块的集合。初始化时,根据指定的共享内存块大小及设置的并发线程数目确定队列个数及存储区域的内存块数目。
将存储空间中的可用block用链表进行串联,形成可用空间链表,然后分配到每对收发队列中。
【收包处理函数】
网络报文进入虚拟交换机后,首先检查是否存在收包处理函数,如果存在,则调用该函数,将该报文的后续行为都交给收包处理函数处理,如果收包处理函数返回错误,则继续交换机后续处理。进入收包处理函数,则达到了劫持的目的,下一步则需要将该网络报文通过以上建立的队列,将该报文发送到应用层。
【应用层多线程处理接口】
该接口由应用层的网络报文分析处理模块调用,提供了接收网络报文的接口和对网络报文进行裁决的功能。首先通过open函数打开共享内存,通过mmap函数进行映射共享内存地址,从共享内存中获取队列相关信息并存储,创建接收数据线程,用于收取共享内存队列中的报文所在内存块的索引,并进一步取得内存块中的报文内容,在线程中调用报文分析处理函数,完成判定逻辑。
在接收网络报文的线程中,不断从队列中读取网络报文,并调用报文处理函数,根据报文处理函数的返回值,决定是否将报文放行。
【内核态报文处理线程】
创建内核态线程,用于处理应用层对网络报文的裁决结果。裁决结果如果是丢弃,内核线程则将报文释放掉,将内存块归还给所在的队列,如果是放行,则进一步调用虚拟交换机的发送接口,将报文再次送入交换机,执行后续的交换流程。
内核线程由应用层调用ioctl进行唤醒,内核处理线程的处理函数为:
int receive_data_from_user_thread(void *data)
此函数的主要功能就是处理队列中的数据(将队列中的数据发送给虚拟交换机)。
内核线程的唤醒采用了等待队列的机制,可以提高唤醒的性能。
内核线程创建的模型为:
DECLARE_WAITQUEUE(wait,current); //申请局部的等待队列元素 add_wait_queue(&rvshmem_rx_waitqueue,&wait);将等待队列元素添加到全局的等待对中 set_current_state(TASK_INTERRUPTIBLE);设置当前的线程为睡眠 for(;;) { schedule();//调度,当前的线程会进入睡眠等待再次的被调度或者是被唤醒 if(kthread_should_stop())//当检测到线程需要退出的时候, 会直接退出 break; ... ... //中间的处理业务 set_current_state(TASK_INTERRUPTIBLE); //设置当前的任务为可睡眠的 } remove_wait_queue(&rvshmem_rx_waitqueue,&wait); //当线程退出的时候,将等待队列进行移除
可以启动一个或者多个内核线程处理报文,这里采用一个内核线程处理多个队列的,所以在内核线程中我们循环处理每个队列,每个队列处理10个报文,并且只要有一个队列中有数据,就会一直循环处理下去,当报文处理完毕后进入睡眠,等待再次唤醒。
/*这里循环处理所有的队列中的数据*/ for(flag = 0,i = 0; i < NET_QUEUE_NUM;i++) { /*每次处理10个报文*/ for(j = 0; j < 10;j++) { /*将报文发送给虚拟交换机进行处理*/ ret = dequeue_to_kernel(i); if(ret != SHMEM_QUEUE_EMPTY) flag = 1; //*只要有一个队列中不为空,则不退出处理 } if(!flag) { /*当队列中的所有数据都已经被处理,队列中为空的时候 * 则退出处理,将将线程进行睡眠处理,等待下一次的调度*/ break; } }
处理内核数据的的函数为:
int dequeue_to_kernel(int queue_index)
此函数的主要功能就是从发送队列中获取数据,并将数据发送给虚拟交换机。
1. 实现了虚拟网络环境下的非旁路抓取网络流量功能,可实现阻断式网络检测,技术复杂度低。
2. 可以支持应用层多线程并发执行网络报文分析处理,提高了网络检测效率。
3. 支撑上层应用提供更丰富的安全能力。