域名劫持大家并不陌生,从PC时代到移动互联时代,网络安全愈发重要,劫持方式更是层出不穷。现在到了智能客厅时代(意淫一下),如果说移动互联时代由于开放性和竞争性,大的厂商还是有良知的,比较注重口碑二字,但客厅由于其封闭性,无良厂商只手遮天,各类监控、各类弹窗广告、各类精简系统、各类系统级封杀等,导致客厅开发异常复杂,只有把主动权把握在自己手中,才能感觉到一丝丝安全。
传统的域名解析是一个系统调用,解析的结果可以说控制在运营商手中,也可能控制在厂商手中(无良厂商改系统),现在我们要把域名解析的主动权收到自己的手中,于是HttpDNS技术应运而生。
我们把客厅接口数据分为两部分:一部分是客厅业务接口,一部分是客厅CDN图片接口。因为作为一个视频APP,图片量是极大的,图片的请求量要远超业务侧接口的请求量,请求量越大,接入HttpDNS技术的效果应该更好,话不多说看结论。
图1展示了客厅业务接口返回码分布图,其中左图为未接入HttpDNS技术的接口返回码,右图为接入HttpDNS技术之后的接口返回码。我们重点关注错误码6和错误码7,错误码6是由于域名解析失败导致的接口错误返回码,错误码7是解析出的ip无法连接到主机导致的接口错误返回码,可以看出在接入了HttpDNS技术之后,由域名解析问题导致的错误(错误码6、7总和)降低了61%,效果非常明显。
图1 客厅业务接口返回码分布图
再来看一下接口耗时,图2展示了客厅业务接口耗时分布图,其中左图为未接入HttpDNS技术的接口耗时,右图为接入HttpDNS技术之后的接口耗时。可以看出在接入了HttpDNS技术之后,接口耗时降低了40%,效果非常明显。
图2 客厅业务接口耗时分布图
图3展示了客厅CDN图片接口返回码分布图,其中左图为未接入HttpDNS技术的接口返回码,右图为接入HttpDNS技术之后的接口返回码。可以看出在接入了HttpDNS技术之后,由域名解析问题导致的错误(错误码6、7总和)降低了82%,比业务接口的接入效果更加明显。
图3 客厅CDN图片接口返回码分布图
再来看一下接口耗时,图4展示了客厅CDN图片接口耗时分布图,其中左图为未接入HttpDNS技术的接口耗时,右图为接入HttpDNS技术之后的接口耗时。可以看出在接入了HttpDNS技术之后,接口耗时降低了43%,比业务接口的接入效果更加明显。
图4 客厅CDN图片接口耗时分布图
首先看一下什么是域名劫持。图5简单示意了域名劫持流程:当用户向Local DNS去请求某个域名的真实ip时,运营商的Local DNS服务器回复了一个假网站或内容缓存服务器的ip,最终导致用户访问无法访问到真实ip,从而出现异常。
图5 域名劫持流程图
是不是非常简单明了!而HttpDNS技术正是为了解决域名劫持应运而生的。下面就来看一下HttpDNS技术的实现原理。
图6 HttpDNS技术原理图
图6展示了HttpDNS技术的实现原理,主要分两步:
1.客户端向HttpDNS服务器发起请求(该请求为ip直连请求),获取与域名对应的一系列ip列表;
2.客户端从ip列表中选取访问延迟最优的ip,直接用此ip代替域名发送请求,这样就避免了Local DNS域名解析这一步骤。
总结HttpDNS技术的优势:
1.根治域名解析异常:由于绕过了运营商的LocalDNS解析,客户端的请求通过ip直连,彻底解决了域名劫持问题;
2.精准调度:HttpDNS能直接获取到用户ip,避免了DNS出口ip和业务出口ip不同网段问题;
3.减少网络延迟:通过本地缓存ip,可以有效减少域名解析时间,降低用户网络请求的平均耗时;
4.提升网络请求可控性和可靠性:我们采用的HttpDNS服务器依托腾讯庞大的ip地址库,利用腾讯公网交换平台的BGP Anycast网络,大大提高了网络请求的可靠性,同时是否走HttpDNS以及BGP IP的选择均由后台控制,增加了可控性。
说起来容易做起来难!任何一种技术的尝鲜都不是一蹴而就的,我们也是从无数的坑里面爬出来,才最终打造了一套适合客厅TV的HttpDNS技术架构。曾几何时在第一版上线的时候就遇到了线上问题,也经历了暂停升级、通宵处理,历经多个版本的不断完善,才从带血的坑里爬出来。
这儿分成三部分来介绍客厅TV-APPHttpDNS技术的接入过程:HttpDNS技术核心架构层、HttpDNS技术业务逻辑层和HttpDNS技术客户端容错处理。
无图无真相,图7展示了客厅HttpDNS技术核心实现流程图:
1.客户端接收到域名请求,查询是否已有该域名历史解析结果缓存,如果已有解析结果缓存则转步骤2,否则转步骤3;
2.检查该缓存是否过期,如果没有过期,则返回查询结果,域名解析成功,否则转步骤3;
3.如果没有解析结果或缓存已过期,则向HttpDNS服务器(119.29.29.29)发起域名查询请求,如果请求成功则转步骤4,否则赚步骤5;
4.域名查询请求成功,得到一组ip列表,通过ip优化选择最优ip并返回查询结果,同时更新解析结果缓存;
5.域名查询请求失败,为了容错,必须再用Local DNS请求一遍,无论是否成功均返回,完成整个查询流程。
图7 客厅HttpDNS技术核心实现流程图
整个查询流程定下之后,开始设计各个客户端模块,图8展示了客厅HttpDNS技术模块结构图,包括查询模块、数据模块、IP优选模块、BGP-IP更新模块以及其他模块等。
图8 客厅HttpDNS技术模块结构图
查询模块
主要包括本地缓存查询和网络查询。查询本地是否有相应的域名缓存,如果有缓存且缓存未过期则直接返回IP;如果本地没有缓存或缓存过期,则从HttpDNS服务器查询IP,并更新域名-IP对应关系记录;如果向HttpDNS服务器查询IP失败则采用LocalDNS解析域名并返回IP,不做域名-IP对应关系更新。
数据模块
主要包括两块数据:利用SharedPreferences缓存HttpDNS服务器的ip信息、优先级、标志位;记录每次请求的“域名-ipList”对应关系以及相关缓存数据。这儿建立的缓存非常重要,我们不可能每次都直接网络请求域名对应的ip列表,这样非常耗时。事实上,请求域名对应ip列表的接口返回数据会自带一个生存期(TTL),在该生存期内ip是有效的,可以直接访问http://119.29.29.29/d?dn=tv.aiseet.atianqi.com&ttl=1, 其中ttl=1代表返回带生存期字段,这样我们通过缓存“域名-ipList”对应数据,如果发现在生存期内再次请求该域名对应ip时,可以直接使用缓存,避免再次的网络请求。
ip优选模块
一个域名可能对应多个ip,提供ip优选是必要的。根据ipList中每个ip被选中次数,以及该ip连接耗时综合选择一个平均耗时最短的ip。图9示例了请求tv.aiseet.atianqi.com域名对应ipList的结果示例图。
图9 域名请求示意图
BGP-IP更新模块
即HttpDNS服务器ip更新模块,如果某一天119.29.29.29这个ip不可用了,换成了另外一个ip,则我们的APP也需要支持ip更换。图10为BGP-IP更新流程图,客户端提供一个默认BGP IP: 119.29.29.29,并通过全局配置下发更新的BGP IP和是否走HttpDNS的标志位。
图10 BGP-IP更新流程图
其他模块
由于篇幅有限,很多地方没能详述,如果你对某一块感兴趣,可以联系作者。
域名过滤功能:可以指定特定域名走HttpDNS;
日志与数据上报功能:分析相关数据,确定域名解析的正确性和有效性;
网络抖动监听:网络变化时需要刷新网络参数,清除内存缓存。
简而言之,上面描述了,给我一个域名,还你一个ip这个过程。那如何给我一个域名呢?这也是一件很有趣的事,详见业务逻辑层分析。
一切抛弃业务谈技术的都是耍流氓。几乎所有的APP都涉及网络数据传输,这就需要有多个业务接口进行网络请求,如请求首页数据、请求列表数据、请求图片资源等,无论你是采用系统网络请求或是建有自己的网络库进行网络请求,想把域名直接替换为ip,无外乎以下方式:
如果你的业务异常独立,都采用同一个域名,那你可以通过预埋ip的方式完成这一过程;
如果你是自建网络库,所有的网络请求都由同一网络库发出,那么你可以在网络库中集中处理,通过提取接口中的域名,利用前面介绍的HttpDNS技术,把域名转换为ip,利用ip替换接口中的域名进行请求;
如果你的业务比较分散,网络请求没有集中整理,那就很难统一给HttpDNS一个域名,于是就有了DNS HOOK技术。
DNS HOOK技术通过拦截Android系统域名解析调用getaddrinfo请求,将其拦截到我们的HttpDNS方案之中,利用HttpDNS技术解析域名对应ip,再把解析出的ip作为返回值返回给getaddrinfo系统调用,从而完成域名解析过程。
我们的客厅APP就采用了DNS HOOK技术,主要有以下原因:
视频APP是一个庞大而复杂的APP,除了我们的主业务之外,我们还要接入播放器jar包、下载组件jar包、广告jar包、MTA数据上报jar包等等一堆外部jar包,每一个外部jar包都有自己的网络请求,请求方式也比较分散;
客厅业务是要受牌照方管控的,意思就是你不能采用自己的.qq.com域名,必须采用牌照方的域名,目前我们与多个牌照方都有合作,域名都不一样,如”.gitv.tv”, “.ottcn.com”, “.cibntv.net”, “.atianqi.com”,对于同一份APP而言,很难进行拆解处理;
网络请求不一,最开始客厅APP有两套网络库,一套是java侧的采用Volley,一套是Native侧的采用CURL,逻辑分散。当然现在已经合一了,统一采用java侧请求。
这儿再单独介绍下客厅业务为啥如此复杂:
牌照管控:广电总局发布的181号文件,提出要对互联网电视进行管控:电视盒子、智能电视等产品所提供的内容,必须在CNTV、华数、上海文广(东方明珠)、南方传媒、湖南电视台、中国国际广播电台以及中央人民电台这7家国有广电系牌照商的集成播控平台上呈现,并接受上述机构的监管。我们客厅的产品就和南方传媒、CNTV、中国国际广播电台、中央人民电台4家牌照方建立了合作关系。既然要接受他们的监督,我们就无法直接使用自己的域名,必须经过他们的服务器进行中转,再通过我们的接入层接入到自己的后台之中。
合作厂商:客厅这个业务和移动端不一样,移动端的大头在于应用分发,而客厅的大头却要靠软件预装实现。与咱们有合作关系的厂商超过了25家,包括主流的电视厂商,如乐视、康佳、创维、TCL等,主流的智能盒子提供商,例如京东、泰捷、微鲸、VST等。这些合作厂商都有自己的特殊需求,例如京东想把京东商城通过单独的频道展现,接口数据通过他们的后台下发,这都对我们业务的多样性和包容性提出了更高的挑战。
于是Android侧发起的网络请求经过系统调用getaddrinfo,被拦截到HttpDNS技术方案之中,通过2.1介绍的HttpDNS技术核心架构层解析出域名对应的ip并返回给系统调用,从而完成整个HttpDNS技术的接入。
上面介绍了Android业务侧接口的域名解析实现部分,你可能会产生疑问,那WebView和CURL又该何去何从呢?
图11 三种域名解析过程对比图
图11展示了Android原生域名解析、WebWiew和CURL域名解析过程,从解析过程可以看出,Android原生域名解析和WebWiew域名解析,只是调用到的系统库不同,一个是Libjavacore.so库,一个是libchromium_net.so库,都可以通过DNS HOOK技术实现解析过程。
CURL方式比较尴尬,无法拦截到,可能水平有限没找到系统库。但方法是思考出来的,对于Native层的网络请求CURL,我们通过jni调用java侧域名解析方法InetAddress,该方法会调用到Android原生域名解析过程,通过DNS HOOK技术,采用自建的HttpDNS技术方案,把解析到的ip结果返回到Native层,再通过域名替换,从而完成CURL接入HttpDNS技术的方案。这种方案的优点是只用维护一份java侧的HttpDNS解析,不用在Native层又另外实现一套解析方案。
至此,我们解答了”如何给我一个域名?“这个问题,总结下这一实现方案的优点:
Java层利用DNS HOOK技术拦截域名解析请求,Http报文结构和不使用HttpDNS技术一样,对后台完全透明。透明这个词很关键,因为如果是通过域名替换这一方式,报文结构有所差别,可能造成部分请求失败。
不仅无缝完成了主业务的HttpDNS技术接入,相关依赖的业务,如播放器、广告等也都完成了无缝接入HttpDNS技术,只要是Android侧的请求,无论是自建网络库,还是系统网络调用,均直接支持。
CURL请求的HttpDNS技术接入也开辟了新的方式,Native层的网络请求通过jni调用Android原生域名解析来实现HttpDNS技术接入。
网络请求是APP的根本,一旦出现网络不通,整个APP就像失去了联系一样,无声的消失在开发人员的世界之中。而域名解析又是网络请求的一个根本所在,一旦域名解析出现问题,就会导致网络请求出现错误,从而失去一个用户。为此,在接入HttpDNS技术的过程中我们做了多重容错处理。
1. 全局配置是否走HttpDNS技术
这是整个HttpDNS技术是否接入的总开关,以防止出现特殊情况时我们可以整体关闭该技术的接入,走入系统处理流程之中。特殊情况如我们使用的腾讯公网BGP-IP出现不可用又没有备用IP时,这时候HttpDNS的解析一定是出错的,虽然技术本身可以保证最后一定走入到系统处理流程之中,但却浪费了一些解析的时间成本,故直接关闭整个HttpDNS技术的接入。
2.全局配置BGP-IP更新
一旦BGP-IP(HttpDNS服务器ip)不可用,HttpDNS的解析一定是出错的,这时候就需要有一个备用的BGP-IP来进行替换,从而走入正常流程之中。
3.预埋ip
通过预埋一些重要域名对应的ip列表,当解析出现错误的时候可以直接查找该域名是否有对应的预埋ip,如果查找到则采用预埋ip进行网络请求。预埋ip作为兜底处理,可以提高域名解析的正确率,但有可能出现该ip不可用或延时很长的情况。
4.域名自动过滤
我们的APP有需要外部依赖jar包,这些jar包中可能包含非常多的域名,然而有些域名我们可能不希望其也走入HttpDNS解析之中,于是提供了一个域名过滤的功能,只处理我们明确需要处理的域名解析。
5.特定域名解析失败一段时间内自动屏蔽功能
如果HttpDNS服务器出现对某个特定域名一直解析出错的情况,我们会缓存该域名的出错次数,一旦该域名解析出错三次,则禁止其在1个小时内再通过HttpDNS服务器进行解析,转而使用系统原生解析流程,从而消减解析耗时。