智营防劫持SDK
No Data
| 目录名称 | 说明 | 适用范围 | | ------------- |-------------| -------------| | HttpDNSLibs | HttpDNS Android SDK库目录 | 所有业务 | | HttpDNS Android接入文档.pdf | HttpDNS Android接入文档 | 所有业务 | | README.md | HttpDNS Android客户端接入文档 | 所有业务 | | CHANGELOG.md | HttpDNS Android SDK历史版本修改记录 | SDK开发维护人员 |
HTTPDNS服务的详细介绍可以参见文章全局精确流量调度新思路-HttpDNS服务详解
总的来说,HTTPDNS作为移动互联网时代DNS优化的一个通用解决方案,主要解决了以下几类问题:
HTTPDNS的Android SDK,主要提供了基于HTTPDNS服务的域名解析和缓存管理能力:
将HttpDnsLibs目录下的aar包及jar拷贝至项目工程中libs相应位置
HttpDnsLibs目录下包含两个包:
在App module的build.gradle文件中, 添加如下配置
android {// ... repositories { flatDir { dirs 'libs' } }
}
dependencies {
// ... implementation(name: 'HTTPDNS_Android_xxxx', ext: 'aar')
}
App targetSdkVersion >= 28(Android 9.0)情况下,系统默认不允许HTTP网络请求,详细信息参见Opt out of cleartext traffic,Protecting users with TLS by default in Android P
这种情况下,业务侧需要将HTTPDNS请求使用的IP配置到域名白名单中:
...
182.254.116.117 119.29.29.29
# 灯塔 -keep class com.tencent.beacon.** {*;}
以下仅提供简单的接入演示,SDK接口的具体说明请参考接口文档(HttpDnsDoc目录),使用请参考使用Sample(HttpDnsSample目录)
// 以下代码片段演示新版本接口的简单使用, SDK也初步兼容了之前版本的接口, 但仍需要进行少量改动, 具体说明请参考接口文档(HttpDnsDoc目录)// 初始化HTTPDNS SDK // NOTE: * 更多配置项及具体说明请参考文档(HttpDnsDoc目录)及使用Sample(HttpDnsSample目录) * DnsConfig .Builder() // 设置日志输出等级 .logLevel(LOG_LEVEL) // 设置AppId, 进行数据上报时用于区分业务 // 从腾讯云官网申请获得 .appId(APP_ID) // 设置UserId, 进行数据上报时区分用户, 出现问题时,依赖该Id进行单用户问题排查 .userId(USER_ID) // 自动初始化内置上报通道, 即灯塔 .initBuiltInReporters() // 设置DnsId, 即HTTPDNS服务的授权Id // 从腾讯云官网申请获得 .dnsId(DNS_ID) // 设置DnsKey, 即HTTPDNS服务的授权Id对应的加密密钥 // 从腾讯云官网申请获得 .dnsKey(DNS_KEY) // 设置域名解析请求超时时间, 单位ms // 默认为1000 .timeoutMills(TIMEOUT_MILLS) .build() .let { // 初始化HTTPDNS SDK DnsService.init(context, it) }
// 进行域名解析 // NOTE: ***** 域名解析接口是耗时同步接口,不应该在主线程调用 ***** // useHttp即是否通过HTTP协议访问HTTP服务进行域名解析 // useHttp为true时通过HTTP协议访问HTTP服务进行域名解析, 否则通过UDP协议访问HTTP服务进行域名解析 // ipSet即解析得到的IP集合 // ipSet.v4Ips为解析得到IPv4集合, 可能为null // ipSet.v6Ips为解析得到IPv6集合, 可能为null val ipSet = DnsService.getAddrsByName(/* hostname */hostname, /* useHttp */false) // NOTE: useHttp默认为false val ipSet = DnsService.getAddrsByName(/* hostname */hostname)
// 从缓存中获取域名解析的详细结果 // NOTE: ** 该接口不会触发域名解析的网络请求,只会从缓存中获取详细结果 ** // 所以该接口一般需要在调用DnsService.getAddrsByName发起网络请求获取结果后调用 // 返回的结果为json // { // "v4_ips":"1.1.1.1,2.2.2.2,3.3.3.3", /* ipv4的地址,逗号分隔 */ // "v4_ttl":"100", /* ipv4的解析有效期ttl */ // "v4_client_ip":"9.9.9.9" /* ipv4的客户端外网IP */ // "v6_ips":"1001::1,2002::2,3003::3", /* ipv6的地址,逗号分隔 */ // "v6_ttl":"100", /* ipv6的解析有效期ttl */ // "v6_client_ip":"9009::9" /* ipv6的客户端外网IP */ // } val dnsDetail = DnsService.getDnsDetail(/* hostname */hostname)
通过观察日志输出,可以确定域名解析接口具体的解析情况
初始化HTTPDNS SDK时,可以调用
DnsConfig.Builder /* DnsConfig.Builder. */logLevel(int logLevel);
接口来设置日志输出等级
模拟LocalDNS劫持情况下,如果App能够正常工作,可以证明HTTPDNS已经成功接入
注意:由于LocalDNS存在缓存机制,模拟LocalDNS进行接入验证时,请尽量保证LocalDNS的缓存已经被清理,可以通过重启机器,切换网络等方式,尽量清除LocalDNS的解析缓存;验证时,请注意对照启用LocalDNS和启用HTTPDNS的效果
以下以接入HTTP网络访问为例进行说明:
使用tcpdump进行抓包
通过WireShark观察抓包结果
如上图,从抓包上看,xw.qq.com的请求最终发往了IP为183.3.226.35的服务器 - 对于HTTPS请求,TLS的握手包实际上是明文包,在设置了SNI扩展(详见HTTPS兼容)情况下,通过对照日志和具体的抓包记录,可以确认最终发起请求时使用的IP是否和SDK返回的一致
如上图,从抓包上看,xw.qq.com的请求最终发往了IP为183.3.226.35的服务器
将HTTPDNS SDK的域名解析能力接入到业务的HTTP(HTTPS)网络访问流程中,总的来说可以分为两种方式:
不同网络库具体的接入方式,可以参见对应的接入文档(当前目录下)及参考使用Sample(HttpDnsSample目录)
如前文所述,对于需要使用到域名信息的网络请求(一般是多个域名映射到同一个IP的情况),我们需要进行额外兼容。以下从协议层面阐述具体的兼容方式,具体的实现方式需要视网络库的实现而定
对于HTTP请求,我们需要通过指定报文头中的Host字段来告知服务器域名信息。Host字段详细介绍参见Host
本地使用HTTP代理情况下,建议不要使用HTTPDNS进行域名解析 以下区分两种接入方式进行分析:
根据HTTP/1.1协议规定,在使用HTTP代理情况下,客户端侧将在请求行中带上完整的服务器地址信息。详细介绍可以参见origin-form 这种情况下(本地使用了HTTP代理,业务侧使用替换URL方式接入了HTTPDNS SDK,且已经正确设置了Host字段),HTTP代理接收到的HTTP请求中会包含服务器的IP信息(请求行中)以及域名信息(Host字段中),但具体HTTP代理会如何向真正的目标服务器发起HTTP请求,则取决于HTTP代理的实现,可能会直接丢掉我们设置的Host字段使得网络请求失败
以OkHttp网络库为例,在本地启用HTTP代理情况下,OkHttp网络库不会对一个HTTP请求URL中的Host字段进行域名解析,而只会对设置的HTTP代理的Host进行域名解析。这种情况下,启用HTTPDNS没有意义
判断代码如下:
val host = System.getProperty("http.proxyHost") val port = System.getProperty("http.proxyPort") if (null != host && null != port) { // 本地使用了HTTP代理 }