1. 背景介绍
1.1 业务背景
由于公司对域名的访问有要求,不允许使用未备案的域名提供服务,但是对于云服务而言,仅只是通过口头或者文本的规章制度难以进行有效控制。对于面向外部开放式环境的用户来说,更加不可能通过简单的规则来进行限制,因此必须有一个比较有效的措施可以检测并且阻止这种使用未备案域名提供服务的行为。
针对公司内部,我们可以通过流量分析,解析请求包的内容来发现未备案的域名,然后通过域名的归属找到使用服务的部门进行规范的修改。但如果是公有云,这个方案不足以满足要求,因此需要有一个可以管控流量的节点来专门做这个事情,梳理下来虚拟化网关是最适合的节点。因此域名熔断网关就这么产生了。
1.2 业务场景
域名熔断网关就是一个布置在虚拟化网关之前,专门针对http,https服务进行域名拦截的一个基于内容的防护性质网关,其功能类似于WAF,由于我们只处理域名有没有备案的问题,因此其功能相比WAF更加单一。
2. 网关设计
2.1 总体架构
域名熔断网关和FIP网关一样,基于dpdk进行设计开发,利用dpdk的高性能特性来完成基础数据报文的收发。
系统总体分为控制层面和数据层面两个大的方面。
控制层面由上到下通过使用dpdk的无锁队列创建出消息队列与数据层面进行通信,主要是配置域名规则;网络设备通过dpdk的pmd驱动直接在用户空间与数据层面进行数据交互,报文收发。对于需要拦截的连接,数据层面通过dpdk的kni模块把需要拦截的服务连接转入内核,然后内核通过socket把数据交给nginx进行处理。
数据层面则由下到上网络报文通过dpdk的pmd驱动进入数据层面处理,通过连接管理进行连接创建,然后分别对连接进行处理,解析数据报文得到应用层包含的域名信息。控制层面通过配置文件解析合法域名建立域名白名单,然后用解析到的域名去查白名单,当发现解析得到的域名不属于白名单中的合法域名就对当前连接进行拦截,同时返回拦截页面提示。
2.2 报文整体处理流程
系统处理流程很清晰,整体处理结构根据类似内核的netfilter框架而来,由于没有协议栈,不需要处理local_in和local_out,所以简化为三节点框架,又因为整体功能处理逻辑不复杂,故把所有逻辑都放在pre-routing节点处理完,报文不往后续节点交了。所以可以认为我们就使用了pre-routing一个钩子点完成了所有功能。
处理流程从报文从网卡被收上来开始,经过创建连接,建立必要的连接追踪信息,然后开始从以太网头部开始逐层向内进行剥离,业务逻辑只需要处理tcp的80和443端口的连接即可,其余的报文全部放过,通过路由模块查询路由之后发出。只有tcp的80和443两个目的端口的流量会进行应用层解析。
解析逻辑分两类,一个是80端口的http服务,默认不进行协议识别,默认认为80端口的访问就是http协议,然后按照http协议规范解析协议头部,只获取头部包含的host字段,通常这个字段就是主机域名,也就是我们要找的东西。由于http是个明文传输的文本协议,因此没什么特别的,字符串匹配查询即可。
另一个是443的https服务,事实上由于https使用了ssl协议,它会先于http数据传输开始之前就进行ssl握手协商过程,因此对于443端口的流量我们是默认认为它是ssl协议的某个版本,而不是认为它是http协议。ssl握手协商过程中,client端会在首包里包含一个client hello的消息,该消息体的extension里会包含有server name,这个就是我们需要解析的域名信息。因此实际上不只是https可以这么做,而是所有通过ssl协议提供的服务都可以在这个里面获取到对应的域名信息,只是我们在这个场景下只关注https了。
在得到域名以后,我们需要通过对比已备案的域名白名单来确定该域名访问是否被允许。为了查找效率,我们引入了acbm算法进行多模式的快速匹配(本文不讨论这个算法,就不展开说了,大家只需要知道这是一个效率极高的字符串多模匹配算法,有多高呢?高到几乎所有安全厂商涉及到应用层数据检测的基本都会用)。对于不允许的连接,我们会进行相应的处理,正常的连接在这里会做一个标记,然后把该连接上的所有报文,包括当前和后续的所有报文全部交给路由转发模块进行转发。
2.3 域名熔断处理流程
这是域名熔断网关的核心,一个网关想要阻断某个连接其实很容易,只需要简单粗暴的把那个连接的报文直接drop掉就行了,如果想要优雅一点儿的话,可以给对端回一个rst报文就能彻底断掉一个tcp连接了。但是域名网关需要给client端返回一个阻断页面,明确的告知是因为域名未备案导致的,并且还需要给出一个如何进行域名备案的方法,这就不能简单的drop报文了。如果是http这种明文协议的话还好说,在rst报文之前给一个http响应即可,问题是如果是https就没办法这么简单了,在一个ssl连接里我们连client何时给了http请求,这个请求是什么都不知道,就算知道了,我们也没办法在连接里插入一个我们需要返回的页面,因为它的连接是加密的。怎么办呢?
这就需要第一个核心技术了——tcp连接劫持。
这个是对内核syn proxy技术的一个定制化修改,原理一样,细节不同。下图分别是http和https的交互过程。
因为我们必须要拿到http请求或者ssl client hello才能得到域名信息,也就是这时候才能知道这个连接究竟是需要直接放过还是要把它代理到响应拦截页面的服务上。syn proxy由于三次握手之后就把client端的窗口置0了,所以正常的syn proxy得不到首包数据。这里我们做了一个改动,不把client的窗口置0,让它正常发送数据。后面通过调整tcp的seq和ack_seq让client和server正常传输数据即可。这种做法相当于我们把网关当作中间人对tcp连接进行了劫持,把需要拦截的连接劫持到拦截响应页面的服务上,以达到可以给https也返回拦截页面的目的。
从上面的交互可以看出,要想实现这个功能,除了需要有可以基于内容劫持tcp连接的网关以外,还需要有一个专门响应劫持页面的web服务才行。就为了返回一个http页面就单独部署一个服务器,而且这个服务器还需要和网关同在一个三层交换机下(跟我们的fip网关部署有关系)。这显然是一个极大的浪费。那么有没有办法不单独去部署这个服务器呢?
这就需要用到第二个技术了——dpdk kni。
由于dpdk是在用户空间进行收发包处理,内核被bypass,因此,一旦报文被dpdk收到,本地的其他服务例如nginx就肯定收不到这个包了。好在dpdk因为自身不带有协议栈,因此需要做报文转发的场景都需要自己去做协议栈的事情,这个开发代价很大,因此dpdk提供了一个方案可以让部分需要用到内核协议栈的场景可以把报文通过某种方式交给内核,然后再从内核收回来。这个就是kni(Kernel NIC Interface),是DPDK允许用户态和内核态交换报文的解决方案,模拟了一个虚拟的网口,提供dpdk的应用程序和linux内核之间通讯。kni接口允许报文从用户态接收后转发到linu协议栈去。
有了这个,我们就可以在本地启动一个nginx服务来担任拦截页面响应者的角色,当发现连接需要拦截,那我们就把这个连接的目的地址改为本地地址,然后通过kni交给内核,内核会根据路由表把报文交给用户空间正在监听的那个socket,也就是nginx服务,然后nginx响应的报文又会通过kni回到我们的处理流程,我们把响应报文的源地址改回去再发回给客户端(其实就是做了个DNAT)。于是流程就变成了下面这样。(http和https原理一样,下图仅给出http的情况)
3. 优点和不足
通过tcp连接劫持,我们完美的绕过了ssl加密连接处理的问题,又通过kni实现了单台机器部署,简化了系统结构和网络部署,节约了服务器资源。但是由于kni本身的性能不高,甚至要低于常规的直接访问,所以没办法承受高并发和大流量。好在域名拦截本身需要响应的数据量很小(响应页面就一个报文,1KB的页面),而且未备案域名一般都是少数,即使出现大量的恶意未备案域名访问我们也可以通过其他手段去禁止。比如可以考虑联动交换机或者网关对恶意访问的未备案域名服务直接进行阻断;或者对同一个服务返回几次拦截页面之后就直接drop报文进行阻断。这些就需要后续使用中我们不断地持续优化了。
推荐阅读:
基础设施即代码初探-开发Terraform Provider管理私有云MySQL实例
更多技术和产品文章,请关注👆
如果您对哪个产品感兴趣,欢迎留言给我们,我们会定向邀文~
360智汇云是以"汇聚数据价值,助力智能未来"为目标的企业应用开放服务平台,融合360丰富的产品、技术力量,为客户提供平台服务。
目前,智汇云提供数据库、中间件、存储、大数据、人工智能、计算、网络、视联物联与通信等多种产品服务以及一站式解决方案,助力客户降本增效,累计服务业务1000+。
智汇云致力于为各行各业的业务及应用提供强有力的产品、技术服务,帮助企业和业务实现更大的商业价值。
官网:https://zyun.360.cn 或搜索“360智汇云”
客服电话:4000052360
欢迎使用我们的产品!😊