App域名劫持之DNS高可用开源版HttpDNS方案详解

原标题:App域名劫持之DNS高可用开源版HttpDNS方案详解

鹅厂往事中提到

HttpDNS主要解决三类问题:

  1. LocalDNS劫持: 由于HttpDNS是通过ip直接请求http获取服务器A记录地址,不存在向本地运营商询问domain解析过程,所以从根本避免了劫持问题。 (对于http内容tcp/ip层劫持,可以使用验证因子或者数据加密等方式来保证传输数据的可信度)
  2. 用户连接失败率下降: 通过算法降低以往失败率过高的服务器排序,通过时间近期访问过的数据提高服务器排序,通过历史访问成功记录提高服务器排序。如果ip(a)访问错误,在下一次返回ip(b)或者ip(c) 排序后的记录。(LocalDNS很可能在一个ttl时间内(或多个ttl)都是返回记录
  3. HttpDNSLib库主要由三个模块组成,查询模块,缓存模块,评估模块。
  4. 检查本地是否有对应的 domain 缓存
  5. 如果没有 则从本地LocalDNS获取然后从httpdns更新domain记录
  6. 有数据则检测是否过期 已过期则更新记录返回 LocalDNS 记录, 未过期则直接返回缓存层数据。
  7. 从HttpDNS 接口查询本次app开启后使用过的domain 记录定时访问,更新内存缓存,数据库缓存等记录
  • 根据SP(或Wifi名)缓存域名信息
  • 更具SP(或Wifi名)缓存服务器ip信息、优先级
  • 记录服务器ip每次请求成功数、错误数
  • 记录服务器ip最后成功访问时间、最后测速
  • 添加 内存 》数据库 之间的缓存层
  • 根据本地数据,对一组IP排序
  • 处理用户反馈回来的请求明细,入库
  • 针对用户反馈是失败请求,进行分析上报预警
  • 给HttpDns服务端智能分配A记录提供数据依据

HttpDNS交互流程图:

从这张图中可以看出来 整个业务的交互流程,用户向查询模块传入一个URL地址,然后查询模块会检查缓存是否存在,不存在从httpdnsapi接口查询, 然后经过评估模块返回。在用户请求URL过程完毕时,需要将这次请求的结果反馈给 lib库的评估模块由评估模块入库记录本次质量数据。

左部分是app主线程操作的事情,中间部分是app调用者线程中处理lib库事件逻辑的事情,右面部分是新线程独立处理事件的逻辑。

lib模块设定定时器,根据ttl过期时间来检查domain是否需要更新。 定时器是独立线程, 不会影响app主线程。 httpdns api请求数据, 先从自己配置的 httpdns api接口获取数据,如果获取不到会从 dnspod api接口获取如果也获取不到 直接从本地 localDNS获取数据,(从本地localDNS获取数据后期会改为发送UDP包封装dns协议从公共dns服务器直接获取,比如114dns等。dns服务器地址可自行设定。 )获取到数据后进入测速模块。 测速模块最新版本可以配置两种方式,一种是http空请求。 两个http头的交互,类似tcp首保耗时时间原理 ,用来测试链路最快。 另一种是ping命令,(icmp协议)来尽量最小化流量的消耗,考虑倒可能有的服务器禁ping就使用空http测速即可。 测速后将数据插入本地 cache 即可。

工程代码一共有八个主要package包,分别是cache、httpdns、log、model、query、score、speedtest、networktype。

IDnsCache是该包的对外主要接口。DnsCacheManager 实现该接口,封装了管理该包的所有逻辑调度,ConcurrentHashMap是内存缓存层的介质,当初使用过非线程安全的hashMap遇到了很多线程锁的问题,没有更好的办法自己控制锁管理,就替换成线程安全的concurrenthashmap对象了。DBConstants 设定了数据库名字表名字以及表字段,包含全部sql语句。 DNSCacheDatabaseHelper 用来操作数据库,所有和数据库交互的逻辑都在该类。

Constants 设定了网络状态的相关常量。 NetworkManager类也是这个包的主入口类所有网络状态的获取都是通过这个类来获取。 NetworkStateReceiver用来注册网络广播来接受网络发生变化的事件 。

IHttpDNS接口定义了该包和外部交互的所有数据格式,HttpDnsManager 实现了IHttpDNS接口。 HttpDnsConfig定义了使用到的常量配置, 以及dns api接口的开关,和顺序。 requests包里INetworkRequests接口轻量级的定义了 网络请求的实现, 目前使用ApacheHttp实现的该接口,如果用户有需求更换网络实现方式实现INetworkRequests 接口即可。 IJsonParser 接口定义了 httpdns api返回数据解析json的方式, 目前使用 android jsonObject实现。 如果需要扩展直接实现该接口即可。

IDnsLog约定了写log和获取log的方法。 HttpDnsLogManager实现该接口,并管理log模块。该模块还有一个写文件的工具类。

DomainModel对应数据库domain表, IpModel对应数据库ip表。 HttpDnsPack是获取httpdns api接口数据的模型 。 ConnectFailModel用来记录所有异常错误。

IQuery定义了该包对外的协议接口。 QueryManager实现该接口封装了所有查询相关操作。

IScore定义该包对外实现的接口,ScoreManager实现该接口。 PlugInManager用来管理所有评估插件。 所有的评估插件均实现 IPlugIn接口协议,规定输入输出。使用者可以自行添加评估插件。

ISpeedtest规定该包对外的接口协议, SpeedtestManager实现ISpeedtest接口。 封装了测速相关逻辑, 包括空http请求,以及ping命令测速。

由于内部model数据过于复杂,为用户专门封装DomainInfo模型。 该类仅返回用户使用的相关数据。

代码结构如下图所示:

在编写该库的时候遇到最头疼的问题可能就是多线程同时访问导致遇到的数据异常错误。比如用户访问 api.weibo.cn 域名该域名目前数据库中没有缓存,内存中也没有缓存。在同时有多个请求以来来获取该域名的ip的时候, 因为没有数据会去请求api接口获取数据, 导致同时开启多个线程访问数据。 解决办法在请求api接口前增加正在请求队列,

以上为冯老师的分享,接下来是星宇跟大家分享下项目从研发倒现在所遇到的一些主要问题和大家有疑问的点。

1、手机网络从3G 切换到 Wifi下处理了什么?

2、网络发生变化后,返回的A记录还一样么?

3、怎样进行测速?

4、域名ttl刚刚过期,库还没有从HttpDNS拉取回来数据怎么办?

5、lib库目前只能使用 dnspod 服务商么? 支持dnspod 企业版本么?

6、使用这个库会不会降低应用请求网络的访问速度?

一旦从LocalDNS获取后,会缓存倒内存中,在HttpDNS获取数据后会更新内存中得domain记录。 从库中获取a记录会比从LocalDNS获取a记录快一些。 在访问网络的时候由于是使用ip直链,可以起到一些加速效果,lib库获取domainA记录 + ip直接访问服务器 耗时小于 直接域名请求服务器。相关数据图片

考虑到该库的轻量级,使用的是android系统的org.apache.http.client.HttpClient库访问网络,如果需要切换到工程在使用的网络库可以实现 INetworkRequests 接口即可切换网络库。 json解析使用的也是android系统自带的org.json.JSONObject 如有需求切换json解析库,可直接实现IJsonParser接口即可切换。

目前该lib库没有引用任何外部的库文件。一切本着使用系统自身的api为原则,来保证库的轻量级,和兼容性。 目前lib库打包后70多k,代码在5千行左右。 测试工程代码在6千行左右。

任何参数都可以在库调用方配置,DNSCacheConfig 类是整个库的配置文件。 并且支持云端动态更新配置 需要实现DNSCacheConfig.ConfigText_API 更新地址。 具体配置api接口请参考 dome工程中设置库的方式。

lib缓存数据是通过数据库存储的。SQLiteDatabase, 具体的表接口和sql语句请参考 DBConstants 类文件。

评估模块目前由五个插件组成, 速度插件、推荐优先级插件、历史成功次数插件、历史错误数插件、最近一次成功时间插件 。 每一个a记录服务器ip,都会经过这五个插件进行评估排序后返回给使用者。 所有插件评估分值比重可以配置, 根据自己的需求以及不同的使用场景,调整出最合理的权重分配。

比如速度插件评分体系, 满分100分, 那么有3个服务器ip, 1号服务器http请求耗时10毫秒, 2号服务器20毫秒, 3号服务器30毫秒。 那么经过插件计算后 1号服务器100分, 2号服务器50分, 3号服务器25分。

如果是自定义的服务器,可以返回服务器优先级字段,该的字段代表推荐使用该服务器的权重, 比如该字段服务端可以和监控系统结合起来,甚至是用来分流。 相应的权重值, 也会算出来不同的分值。

在当前sp当前链路下, 会记录访问过的该服务器ip的成功次数, 成功次数越多认为该服务器相对稳定。 会在排序的时候根据权重比值进行影响最终排序结果, 该插件权重不建议过高。 同理历史错误插件也是记录当前链路下服务器出过错误的次数,次数越高认为越不稳定。 排序尽量靠后。 同样该插件权重不建议过高。

如果该服务器在 很近的时间内访问过,那么评估系统则认为他链路是通的,则会给一个分值, 越接近现在的时间的服务器 分值越高。 24小时以前访问的分值为0 。

该五个插件属于抛砖引玉, 可以自定义插件 只需要实现 IPlugIn 接口即可。 所有的插件启用和停止都在 配置文件中可以修改,以及配置每个插件的权重比。

首先httpdns返回的a记录已经是 经过当前地域和当前sp返回的最优记录结果集, 至少不会降低效率。

可以的在配置文件中关掉智能评估即可,具体代码参照demo即可。 关掉智能评估模块后,会在多个a记录中随机排序返回。

使用testin兼容性测试 测试兼容性结果:99.49%。 Android平台全部由java代码开发,没有使用任何特殊特性,覆盖全部系统版本。

  • 模拟了客户端访问http请求,分别标识了每个任务的详细信息。
  • 这个页面全都是数据库相关配置,在代码中可以直接找到具体设置库文件的接口。
  • 数据报表入口,包含全部任务加速效果延迟效果数据记录, lib库耗时走向,每个ip直接访问请求和domain访问请求速度对比, 统计了服务器平局速度。
  • 缓存数据标签中包含了 当前库的所有状态, 能实时的看到内存缓存层的所有数据状态,包括数据库中得所有数据状态。 每秒钟刷新一次。 在这里可以清空缓存层数据、数据层数据、已经当前测试工程的数据。 在这里你可以清楚的看到 ip 和 domain的对应关系, 以及数据库表中 每项的关系。 和所有的domain 以及 ip 的状态。
  • Q&A
  • 不需要每次都ping的, 测试链路是否通畅 会在 从 httpdns api接口获取数据后, 在测试链路是否通畅, 每次请求 httpdns api间隔是一个 domain 设置的 ttl时间
  • 检测链路 如果配置成 http 空的请求, 也是同理 在 httpdns api请求结束后, 才会检测链路是否畅通。
  • 并发请求是说 客户端请求 HttpDNS lib库 ,同时发 api.weibo.cn 的请求么?
  • Q3、南北网络之间请求有特别处理么?
  • 首先 HttpDNS 返回的a记录会根据你的出口ip 来从权威的 dns 服务器问出来结果。 如果你是南方的ip 肯定给你的a记录 也是南方的, httpdns 返回的记录理论应该是和 传统的 dns 返回的 a 记录是一样的。 而去问 httpdns 的api 地址 是 bgp的机房。 所以 也是 兼容多链路 多地域。有遇见过 传统 dns 出口可能是 电信的, 但业务访问的 ip 出口是联通的情况。 所以 HttpDNS 访问 a记录 也能避免这类一部分错误。
  • 会的。 这个依赖dnspod的返回结果, 同时也支持 cname 的返回结果。
  • Q5、数据库中记录的是cname,还是cname解析出的ip?
  • 因为测试过, 从一栋大楼走到另外一个大楼 里面 访问的最终ip可能都不相同。 所以如果返回的是cname 则直接存储cname 。 网络环境发生变化, 会重新拉取, 不会使用缓存的cname 。
  • 不会的, 一般劫持的都是 业务的主要域名, 而cname域名的劫持相对较少, 从我们公司的业务来看啊。 而且 dnspod 返回cname 的情况 我目前还没看到。 都是解析倒ip 。 而我们自己做的 httpdns 服务器, 第一期目前会解析倒 cname 的节点。 跨域的ip解析 还没做 会放到二期。
  • 有这个可能,我觉得可以把你们的domain放到dnspod里面试下解析出来的是不是cname如果是直接的ip应该没问题。后期我们有计划加上udp直接发送dns协议包到公共的dns服务器节点来获取数据,也支持设置自己家的权威dns服务器。

返回搜狐,查看更多

责任编辑:

平台声明:该文观点仅代表作者本人,搜狐号系信息发布平台,搜狐仅提供信息存储空间服务。
阅读 ()
大家都在看
推荐阅读