一、DNS
DNS
(Domain Name System
,域名系统),万维网上作为域名和IP
地址相互映射的一个分布式数据库,能够使用户更方便的访问互联网,而不用去记住能够被机器直接读取的IP
数串。
简单说就是网络地址IP
由数字组成的,例如百度的服务器地址是115.239.210.27
,浏览器可以通过这个地址访问百度,但是这个数字记忆起来比较麻烦,于是就有了www.baidu.com
这个更直观的表示方法,DNS
就是连接这两者的服务。
二、DNS解析
域名到IP
地址的映射,DNS
解析请求采用UDP
数据报且明文的。
现在假如我们访问一个网站www.baidu.com
从按下回车到百度页面显示到我们的电脑上会经历如下几个步骤:
- 1:计算机会向我们的运营商(移动、电信、联通等)发出打开
www.baidu.com
的请求。 - 2:运营商收到请求后会到自己的
DNS
服务器中找www.baidu.com
这个域名所对应的服务器的IP
地址(也就是百度的服务器的IP
地址),这里比如是115.239.210.27
。 - 3:运营商用第二步得到的
IP
地址去找到百度的服务器请求得到数据后返回给我们。
其中第二步就是我们所说的DNS
解析过程,域名和IP
地址的关系其实就是我们的身份证号和姓名的关系,都是来标记一个人或者是一个网站的,只是IP
地址/身份证号只是一串没有意义的数字,辨识度低,又不好记,所以就会在IP
上加上一个域名以便区分,或是做的更加个性化,但是如果真的要来准确的区分还是要靠身份证号码或者是IP
的,所以DNS
解析就应运而生了。
二、DNS劫持
DNS
劫持,由于域名->IP
这个过程中,其解析是基于UDP
协议实现,所以报文是明文状态,可能会在请求过程中被监测,然后攻击者做一些自己的处理,比如返回假的IP
地址或者什么都不做使请求失去响应,其效果就是对特定的网络不能反应或访问的是假网址。根本原因就是以下两点:
- 1:恶意攻击,拦截运营商的解析过程,把自己的非法东西嵌入其中。
- 2:运营商为了利益或者一些其他的因素,允许一些第三方在自己的链接里打打广告之类的。
四、防止DNS劫持
了解了DNS
劫持的相关资料后我们就知道了,防止DNS
劫持就要从第二步入手,因为DNS
解析过程是运营商来操作的,我们不能去干涉他们,不然我们也就成了劫持者了,所以我们要做的就是在我们请求之前对我们的请求链接做一些修改,将我们原本的请求链接www.baidu.com
修改为115.239.210.27
,然后请求出去,这样的话就运营商在拿到我们的请求后发现我们直接用的就是IP
地址就会直接给我们放行,而不会去走他自己DNS
解析了,也就是说我们把运营商要做的事情自己先做好了。不走他的DNS
解析也就不会存在DNS
被劫持的问题,从根本是解决了。
我们知道要要把项目中请求的接口替换成IP其实很简单,URL
是字符串,域名替换IP
,无非就是一个字符串替换而已,的确这块其实没有什么技术含量。
这里要说一下IP
地址的来源,如何拿到一个域名所对应的IP
呢?这里就是需要用到另一个服务——HTTPDNS
。
1. HTTPDNS
HTTPDNS是客户端基于http协议向服务器A发送域名B解析请求(例如:www.baidu.com),服务器A直接返回域名B对应的ip地址(例如:115.239.210.27),客户端获取到的IP后就向直接往此IP发送业务协议请求。
这种方式替代了基于DNS协议向运营商LocalDNS发起解析请求,可以从根本上避免LocalDNS造成的域名劫持问题。
常规的DNS解析是通过UDP方式。
国内提供域名解析 API 接口的,有 DNSPod,现在国内有很多厂商为 DNSPod 开发了 SDK,比如 阿里、七牛(开源)等。不想自己写的,不妨使用这些 SDK。
2. 内置IP列表
可以在启动等阶段由服务端下发域名和 IP 的对应列表,客户端来进行缓存,发起网络请求的时候直接根据缓存 IP 来进行业务访问。
实现 HTTP 协议下 IP 连接其实是很简单的,我们只需要通过 NSURLProtocol 来拦截网络请求,然后将符号条件的网络请求 URL 中的域名修改为 IP 就可以啦,也就是本地域名解析LocalDNS。
#include
#include
/**
* 通过hostname获取ip列表 DNS解析地址
*/
- (NSArray *)getDNSsWithDormain:(NSString *)hostName{
NSMutableArray *result = [[NSMutableArray alloc] init];
NSArray *IPV4DNSs = [self getIPV4DNSWithHostName:hostName];
if (IPV4DNSs && IPV4DNSs.count > 0) {
[result addObjectsFromArray:IPV4DNSs];
}
//由于在IPV6环境下不能用IPV4的地址进行连接监测
//所以只返回IPV6的服务器DNS地址
NSArray *IPV6DNSs = [self getIPV6DNSWithHostName:hostName];
if (IPV6DNSs && IPV6DNSs.count > 0) {
[result removeAllObjects];
[result addObjectsFromArray:IPV6DNSs];
}
return [NSArray arrayWithArray:result];
}
//ipv4
- (NSArray *)getIPV4DNSWithHostName:(NSString *)hostName
{
const char *hostN = [hostName UTF8String];
struct hostent *phost;
@try {
phost = gethostbyname(hostN);
} @catch (NSException *exception) {
return nil;
}
NSMutableArray *result = [[NSMutableArray alloc] init];
int j = 0;
while (phost && phost->h_addr_list && phost->h_addr_list[j]) {
struct in_addr ip_addr;
memcpy(&ip_addr, phost->h_addr_list[j], 4);
char ip[20] = {0};
inet_ntop(AF_INET, &ip_addr, ip, sizeof(ip));
NSString *strIPAddress = [NSString stringWithUTF8String:ip];
[result addObject:strIPAddress];
j++;
}
return [NSArray arrayWithArray:result];
}
//ipv6
- (NSArray *)getIPV6DNSWithHostName:(NSString *)hostName
{
const char *hostN = [hostName UTF8String];
struct hostent *phost;
@try {
/**
* 只有在IPV6的网络下才会有返回值
*/
phost = gethostbyname2(hostN, AF_INET6);
} @catch (NSException *exception) {
return nil;
}
NSMutableArray *result = [[NSMutableArray alloc] init];
int j = 0;
while (phost && phost->h_addr_list && phost->h_addr_list[j]) {
struct in6_addr ip6_addr;
memcpy(&ip6_addr, phost->h_addr_list[j], sizeof(struct in6_addr));
NSString *strIPAddress = [self formatIPV6Address: ip6_addr];
[result addObject:strIPAddress];
j++;
}
return [NSArray arrayWithArray:result];
}
- (NSString *)formatIPV6Address:(struct in6_addr)ipv6Addr{
NSString *address = nil;
char dstStr[INET6_ADDRSTRLEN];
char srcStr[INET6_ADDRSTRLEN];
memcpy(srcStr, &ipv6Addr, sizeof(struct in6_addr));
if(inet_ntop(AF_INET6, srcStr, dstStr, INET6_ADDRSTRLEN) != NULL){
address = [NSString stringWithUTF8String:dstStr];
}
return address;
}
所以调用直接使用
[self getDNSsWithDormain:@"www.baidu.com"];