本文已参与「新人创作礼」活动,一起开启掘金创作之路
公司开发的APP最近网络请求总是报Failed to connect to xxx.com/127.0.0.1:443,经运维排查,是联通网络DNS解析域名错误,直接把我们的ip指向了127.0.0.1,没办法,只能投诉,但是联通网络不承认,说并没有恶意修改Dns解析,没办法,我们只能寻求代码上的解决方案。
首先贴图一则有趣的新闻!
呵呵,原来是惯犯了!
幸好我司与百度、google等各大搜索引擎有合作协议,搜索怎么解决移动端DNS解析的问题,有说直接在手机网络里设置的,有说需要服务端处理的等等等等,然后偶然发现可以在OkHttp网络请求中进行Dns解析,方法有如下两种:
- 通过拦截器,在发送请求之前,将域名替换为 IP 地址。
- 通过 OkHttp 提供的 .dns() 接口,配置 HTTPDNS。
讲这两种方法前我们先了解下Dns吧。
Dns
DNS(Domain Name System)即域名解析系统,这个东西说对于开发者来说,应该是没有不知道的。说简单点,这个系统的作用就是将域名解析成IP地址。我们的每一次网络请求,如果是使用域名,那么就是进行域名解析。
LocalDNS
一个DNS查询,会先从本地缓存查找,如果没有或者已经过期,就从DNS服务器查询,如果客户端没有主动设置DNS服务器,一般是从服务商DNS服务器上查找。这就出现了不可控。因为如果使用了IPS的LocalDNS域名服务器,那么基本都会或多或少地无法避免在有中国特色的互联网环境中遭遇到各种域名被缓存、用户跨网访问缓慢等问题。
我们先来看看普通域名服务会有什么问题:
1. 域名劫持:
一些小服务商以及小地方的服务商非常喜欢干这个事情。根据腾讯给出的数据,DNS劫持率7%,恶意劫持率2%。网速给的劫持率是10-15%。
把你的域名解析到竞争对手那里,然后哭死都不知道,为什么流量下降了。 在你的代码当中,插入广告或者追踪代码。这就是为什么在淘宝或者百度搜索一下东西,很快就有人联系你。 下载APK文件的时候,替换你的文件,下载一个其他应用或者山寨应用。 打开一个页面,先跳转到广告联盟,然后跳转到这个页面。无缘无故多花广告钱,以及对运营的误导。
2. 智能DNS策略失效
智能DNS,就是为了调度用户访问策略,但是这些因素会导致智能DNS策略失效。
小运营商,没有DNS服务器,直接调用别的服务商,导致服务商识别错误,直接跨网传输,速度大大下降。 服务商多长NAT,实际IP,获得不了,结果没有就近访问。 一些运营商将IP设置到开卡地,即使漫游到其他地方,结果也是没有就近访问。 目前国内大多数企业对于域名解析这块问题没有进行特殊处理,这导致了上述说的那些问题,其中域名劫持的问题相当普遍。那么有没有一种方法能够避免上述的情况呢?有,当然有。那就是使用HttpDNS。
HttpDNS
HttpDNS其实也是对DNS解析的另一种实现方式,只是将域名解析的协议由DNS协议换成了Http协议,并不复杂。使用HTTP协议向D+服务器的80端口进行请求,代替传统的DNS协议向DNS服务器的53端口进行请求,绕开了运营商的Local DNS,从而避免了使用运营商Local DNS造成的劫持和跨网问题。
接入HttpDNS也是很简单的,使用普通DNS时,客户端发送网络请求时,就直接发送出去了,有底层网络框架进行域名解析。当接入HttpDNS时,就需要自己发送域名解析的HTTP请求,当客户端拿到域名对应的IP之后,就向直接往此IP发送业务协议请求。
这样,就再也不用再考虑传统DNS解析会带来的那些问题了,因为是使用HTTP协议,所以不用担心域名劫持问题了;而且,如果选择好的DNS服务器提供商,还保证将用户引导的访问速度最快的IDC节点上。
对于刚才说的两种解决办法,我们推荐使用标准 API 来实现,下面就来讲方法:
OKHttp 标准 API 接入
OkHttp 其实本身已经暴露了一个 Dns 接口,默认的实现是使用系统的 InetAddress 类,发送 UDP 请求进行 DNS 解析。
我们只需要实现 OkHttp 的 Dns 接口,即可获得 HTTPDNS 的支持。
在我们实现的 Dns 接口实现类中,解析 DNS 的方式,换成 HTTPDNS,将解析结果返回。 下面我们来上代码:
class ApiDnsUtil private constructor() : Dns{
override fun lookup(hostname: String): MutableList<InetAddress> {
try {
var b : Boolean = false
var lookup : MutableList<InetAddress> = Dns.SYSTEM.lookup(hostname)
for (i in lookup.indices){
if (lookup[i].hostAddress.contains("127.0.0.1")){
b = true
}
}
if (b){
return InetAddress.getAllByName(SERVER_IP).asList() as MutableList<InetAddress>
}else{
return Dns.SYSTEM.lookup(hostname)
}
}catch (e: UnknownHostException){
return InetAddress.getAllByName(SERVER_IP).asList() as MutableList<InetAddress>
}
}
companion object {
// 这个值可以在网络正常时从服务器更新下来,SP、MMKV、SQL等保存在本地,随时更新
private const val SERVER_IP = "这里设置自己域名的原站IP(114.114.114.114)"
val INSTANCE: ApiDnsUtil by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { ApiDnsUtil() }
}
}
然后在Okhttp里引用就行了
OkHttpClient client = new OkHttpClient.Builder()
.dns(ApiDnsUtil.Companion.getINSTANCE())
.build();
这样就能实现了,网络请求就好了;
不过这种解决办法有一个问题,我也不知道安不安全,毕竟把自己域名的原站ip直接写到了代码中,大佬们能帮我解答一下么,这样到底安不安全?
参考文献: