1 项目概况
域名劫持大家并不陌生, 从 PC 时代到移动互联时代, 网络安全愈发重要, 劫持方式更是层出不穷. 现在到了智能客厅时代(意淫一下), 如果说移动互联时代由于开放性和竞争性, 大的厂商还是有良知的, 比较注重口碑二字, 但客厅由于其封闭性, 无良厂商只手遮天, 各类监控, 各类弹窗广告, 各类精简系统, 各类系统级封杀等, 导致客厅开发异常复杂, 只有把主动权把握在自己手中, 才能感觉到一丝丝安全.
传统的域名解析是一个系统调用, 解析的结果可以说控制在运营商手中, 也可能控制在厂商手中(无良厂商改系统), 现在我们要把域名解析的主动权收到自己的手中, 于是 HttpDNS 技术应运而生.
1.1 数据会说话
我们把客厅接口数据分为两部分: 一部分是客厅业务接口, 一部分是客厅 CDN 图片接口. 因为作为一个视频 APP, 图片量是极大的, 图片的请求量要远超业务侧接口的请求量, 请求量越大, 接入 HttpDNS 技术的效果应该更好, 话不多说看结论.
1.1.1 客厅业务接口数据分析
图 1 展示了客厅业务接口返回码分布图, 其中左图为未接入 HttpDNS 技术的接口返回码, 右图为接入 HttpDNS 技术之后的接口返回码. 我们重点关注错误码 6 和错误码 7, 错误码 6 是由于域名解析失败导致的接口错误返回码, 错误码 7 是解析出的 ip 无法连接到主机导致的接口错误返回码, 可以看出在接入了 HttpDNS 技术之后, 由域名解析问题导致的错误(错误码 6,7 总和) 降低了 61% , 效果非常明显.
图 1 客厅业务接口返回码分布图
再来看一下接口耗时, 图 2 展示了客厅业务接口耗时分布图, 其中左图为未接入 HttpDNS 技术的接口耗时, 右图为接入 HttpDNS 技术之后的接口耗时. 可以看出在接入了 HttpDNS 技术之后, 接口耗时降低了 40%, 效果非常明显.
图 2 客厅业务接口耗时分布图
1.1.2 客厅 CDN 图片接口数据分析:
图 3 展示了客厅 CDN 图片接口返回码分布图, 其中左图为未接入 HttpDNS 技术的接口返回码, 右图为接入 HttpDNS 技术之后的接口返回码. 可以看出在接入了 HttpDNS 技术之后, 由域名解析问题导致的错误 (错误码 6,7 总和) 降低了 82%, 比业务接口的接入效果更加明显.
图 3 客厅 CDN 图片接口返回码分布图
再来看一下接口耗时, 图 4 展示了客厅 CDN 图片接口耗时分布图, 其中左图为未接入 HttpDNS 技术的接口耗时, 右图为接入 HttpDNS 技术之后的接口耗时. 可以看出在接入了 HttpDNS 技术之后, 接口耗时降低了 43%, 比业务接口的接入效果更加明显.
图 4 客厅 CDN 图片接口耗时分布图
1.2 什么是 HttpDNS 技术?
首先看一下什么是 域名劫持 . 图 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 的选择均由后台控制, 增加了可控性.
2 实施方案
说起来容易做起来难! 任何一种技术的尝鲜都不是一蹴而就的, 我们也是从无数的坑里面爬出来, 才最终打造了一套适合客厅 TV 的 HttpDNS 技术架构. 曾几何时在第一版上线的时候就遇到了线上问题, 也经历了暂停升级, 通宵处理, 历经多个版本的不断完善, 才从带血的坑里爬出来.
这儿分成三部分来介绍客厅 TV-APPHttpDNS 技术的接入过程: HttpDNS 技术核心架构层, HttpDNS 技术业务逻辑层和 HttpDNS 技术客户端容错处理.
2.1 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 这个过程. 那如何给我一个域名呢? 这也是一件很有趣的事, 详见业务逻辑层分析.
2.2 HttpDNS 技术业务逻辑层
一切抛弃业务谈技术的都是耍流氓. 几乎所有的 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 技术接入.
2.3 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 服务器进行解析, 转而使用系统原生解析流程, 从而消减解析耗时.
来源: http://www.tuicool.com/articles/Mj6niaY