头图

网络安全 DNSSEC & HTTPDNS

图片来源:https://unsplash.com/photos/Q...

作者:念山

背景

随着技术的发展,网络环境也变得越来越复杂,而对于一个以网络数据传输提供服务的 App 来讲,在复杂多变的网络环境下安全稳定有效的提供好服务显得尤为重要。而为了提供安全稳定有效的 HTTP 网络服务,我们从网络请求的初始阶段 DNS 解析上保证 DNS 安全性的技术:去分析下苹果2022年 WWDC 讲到的 DNSSEC 技术和我们云音乐现行的 HTTPDNS 并做下对比。

什么是 DNS

域名系统(Domain Name System,DNS) 则是将域名解析 IP 地址的一项互联网基础服务,提供该服务的服务器称为 域名服务器(Domain Name Server)。

域名(Domain Name,Domain) 是一个在互联网上标识主机或主机组的名称,相当于 IP 地址的别名,相对于晦涩难记的 IP 地址,域名更显得易于记忆。

域名系统是怎么工作的呢

互联网上的域名系统是一个分布式的系统,结构上是一个四层的树状层次结构:

域名系统

  • 本地域名服务器(Local Name Server,local DNS):如果通过 DHCP 配置,local DNS 由互联网服务提供商(ISP,如联通、电信)提供
  • 根域名服务器(Root Name Server):当 local DNS 查询不到解析结果时,第一步会向它进行查询,并获取顶级域名服务器的IP地址。全球一共有 13 个根域名服务器(除了它们的镜像),它们并不直接用于域名解析,仅用于指出可查询的顶级域名服务器。
  • 顶级域名服务器(Top-level Name Server):负责管理在该顶级域名服务器下注册的二级域名,例如.com 顶级域名服务器,而 baidu.com 权威服务器是注册在 .com 的权威域名服务器
  • 权威域名服务器(Authoritative Name Server):在特定区域内具有唯一性,负责维护该区域内的域名与 IP 地址的映射关系。在 DNS 应答报文中,标志位 AA 标识本次 DNS 记录是否来自权威域名服务器,否则可能来自缓存
模拟流程

那以我们主站域名 interface.music.163.com 为例,来看一下 DNS 的过程:

DNS流程

  • 0、首先检查 DNS 缓存,如果缓存老化或未命中,客户端需要向 local DNS 发送查询请求报文
  • 1、客户端向 local DNS 发送查询报文 query interface.music.163.com,local DNS 首先检查自身缓存,如果存在记录则直接返回结果,查询结束;如果缓存老化或未命中,则:
  • 2、local DNS 向根域名服务器发送查询报文 query interface.music.163.com,返回 .com 顶级域名服务器的地址(如果查无记录)
  • 3、local DNS 向 .com 顶级域名服务器发送查询报文 query interface.music.163.com,返回interface.music.163.com所在的权威域名服务器的地址(如果查无记录)
  • 4、local DNS 向 interface.music.163.com 权威域名服务器发送查询报文 query interface.music.163.com,得到 ip 地址,存入自身缓存并返回给客户端

DNS 特点

DNS 从 1983 年设计推出到现在已经在全球运作 40 年了,设备支持高,但是由于最初设计的时候没有考虑安全相关的问题,报文无鉴权和加密相关的信息,带来了不稳定的因素。

优势
  • 系统支持
  • 速度快
劣势
  • 中间人攻击

    • DNS 欺骗
    • DNS 劫持
  • 结构复杂,中间变数多

针对 DNS 不安全的情况,业界也在通过 DNSSEC、HTTPDNS 等技术方案补充来去保证安全提高服务可用。

什么是 DNSSEC

DNSSEC 可以说是在原有 DNS 基础之上做的扩展。
网域名称系统安全性扩充 (DNSSEC) 可为网域名称的 DNS (网域名称系统) 加上电子签名,藉此判断来源网路名称的真实性。此功能可以保护网路使用者不受假造 DNS 资料的威胁,让使用者要求正确网址时不会取得其他有意误导或恶意制作的网址。

DNSSEC 工作的流程

DNSSEC 通过建立信任链来验证记录
DNSSEC流程

举个例子:设备想要解析 www.example.org 并启用 DNSSEC 验证,过程如下:

  • 发送询问 IP 地址、签名和密钥的查询,通过响应可以建立从 IP 地址到密钥 1 的信任关系
  • 客户端向父区域 org 发送查询,询问可用于验证密钥 1 的记录,建立从密钥 1 到密钥 2 的信任关系
  • 设备递归地重复这个过程,直到它到达根域

DNSSEC 特点

通过流程总结下来

优势
  • 提供校验保证安全

    劣势
  • 需要路由器支持,路由器必须支持处理大于正常大小的 DNS 包

    • 正常的 DNS 包大小为 512 字节, DNSSEC 则大于 512 字节,国内较多路由器会丢弃超过 512 字节的 DNS 包
  • 域名注册服务商配置大部分是付费的
  • 云商支持弱

    • 较多云商不支持 DNSSEC 解析
    • 支持 DNSSEC 解析的基本还收费
  • 需要权威服务器支持
  • 系统兼容差

    • iOS16+ 支持
    • macOS Ventura 以上才支持

是否要选择 DNSSEC?

DNSSEC 方案对于我们 App 来讲不可控因素太多,且可扩展性较弱所以我们选择了一套同样安全,且更为可控的方案-- HTTPDNS。

HTTPDNS

相较于 DN S和 DNSSEC,对于客户端来讲有一种可控性更强和更安全的方案:HTTPDNS,而 HTTPDNS 也是我们云音乐现在正在运行的方案。

什么是 HTTPDNS

DNSOverHTTP,通过 HTTP 报文请求来去拉取 DNS 的结果,客户端拿到结果后拿 IP 替换请求的 host 并设置 SNI 来去进行IP直连的请求,达到控制请求目的 IP 选择的结果,达到等同 DNS 的功能。

HTTPDNS 流程

  • 客户端请求自己 HTTPDNS 服务器拿到 IP
  • 发请求替换 host 为对应的 IP
  • 设置 SNI 来添加原始 host 信息

HTTPDNS 特点

优势
  • 可定制化强 : 服务走自己服务器
  • 安全 : 走 https 请求
劣势
  • 相较于 LocalDNS 时间损耗长:由于采用 HTTP 报文来去传输 domain 与 IP 的映射表
  • 需要自己处理替换域名的操作。
  • 具有一定的安全风险:iPhone 需要调用私有 API 去设置 SNI

安全风险

一个比较大的风险点是在使用私有 API 来去设置 SNI。

在基于目前网络基本使用 NSURLSession 的大背景下,在使用 HTTPDNS 技术的情况下有个重要的一个过程是通过私有 API 来设置 SNI 信息通过 HTTPS 的握手校验。但是私有 API 有随时被封禁的风险,如若被封禁则使用私有 API 来做 SNI 设置的HTTPS请求都不可用。

怎么来规避使用私有 API 这个安全风险呢?
核心问题

NSURLSession 无法控制 Socket 连接,所以只能替换 host 的方式来去处理,如果通过控制 Socket 连接池,就能规避掉 SNI 的问题

新的网络库

我们选择一套新的网络库 Cronet 既能控制 Socket 连接池,又能带来网络性能相关的提升及网络高级特性的支持。

IP 跑马

HTTPDNS 拿到的是一个 IP 数组,实际发请求的时候只需要一个 IP,那到底使用哪个 IP 来进行建连请求呢?那就需要通过一定的计分标准来给 IP 打分,通过 IP 跑马来拿到最佳 IP。

计分规则
单次请求的评分

单次请求评分按照正确请求和错误请求两种不同的计分方式来进行处理。

错误评分

把网络错误细分为5个等级,不同等级扣分分值不一样

typedef NS_ENUM(NSInteger, NENetErrorLevel)
{
    NENetErrorLevelNone, // 无错误,不扣分
    NENetErrorLevelDefault, // 默认,扣1分
    NENetErrorLevelisCancel, //取消不算错误,不扣分
    NENetErrorLevelNormal,  // 普通级别 扣10分
    NENetErrorLevelSerious, // 严重  扣20分
};

// 把对应的NSURLSession的网络报错对应到错误等级进行评分
- (NENetErrorLevel)errorLevelForError:(NSError *)error
{
    NENetErrorLevel errorLevel =NENetErrorLevelNone;
    BOOL isCancel = NO;
    BOOL isChainError = NO;
    BOOL isTimeout = NO;
    NSError *urlError = error;
    
    NSInteger errorCode = urlError.code;
    if ([urlError.domain isEqualToString:NSURLErrorDomain] ||
        [urlError.domain isEqualToString:@"AFNetworkingErrorDomain"])
    {
        if (errorCode == NSURLErrorCannotConnectToHost)
        {
            isChainError = YES;
        }
        isTimeout = errorCode == NSURLErrorTimedOut;
        isCancel = errorCode == NSURLErrorCancelled;
    }
    else if ([urlError.domain isEqualToString:NSPOSIXErrorDomain])
    {
        if (errorCode == 54 || //ECONNRESET
            errorCode == 57 || //ENOTCONN
            errorCode == 60 || //ETIMEDOUT
            errorCode == 61 ||
            errorCode == 65
            )   //ECONNREFUSED
        {
            isChainError = YES;
        }
    }
    
    if (isCancel) {
        errorLevel = NENetErrorLevelisCancel;
    } else if (isChainError) {
        errorLevel = NENetErrorLevelSerious;
    } else if (isTimeout) {
        errorLevel = NENetErrorLevelNormal;
    } else if (error) {
        errorLevel = NENetErrorLevelDefault;
    }
    return errorLevel;
}
正确的评分

正确的评分采用请求速度来去考量分值,选择基准线速度和时间,在基准线上线浮动进行算分。

耗时<=2.5s 才会去按照基准线进行计算分
耗时>4.5s 开始扣分

// 计算公式
- (double)calcllateSuccessMarkWithDataLength:(NSUInteger)dataLength cost:(NSTimeInterval)cost
{
    if (cost == 0) {
        return 0;
    }
    double mark;
    long delta = 0;
    if (cost <= CALL_TIME_GOOD_BENCH_MARK) { //如果<= 2.25s, 才有积分机会
        delta = CALL_TIME_GOOD_BENCH_MARK - cost;
    } else if (cost > CALL_TIME_BAD_BENCH_MARK) { //如果 > 4.5s, 则开始扣分
        delta = CALL_TIME_BAD_BENCH_MARK - cost;
    }
    // 超时不一定是IP有问题
    //通过耗时维度计算出的mark 最低-5分
    mark = MAX(delta,-5);
    if (dataLength > 0 && mark > 0) {
        double speed = dataLength / (cost + 0.0f);
        if (speed < CALL_SPEED_GOOD_BENCH_MARK) { //利用speed, 将请求体小的请求分数降低
            mark = speed / CALL_SPEED_GOOD_BENCH_MARK * mark;
        }
    }
    return mark;
}
IP 总分

IP 总分有两次策略:累计计分、瞬时计分

累计计分

当前网络环境下(WiFi/蜂窝)的所有请求的分值的加和。

瞬时计分

当前网络环境下(WiFi/蜂窝)的当前请求的分值就是IP的最终得分。

选择IP

请求发出的时候根据拿到的 IP 组按照 IP 的跑分拿最高分作为当成请求的 IP。

整体流程图

跑分整体流程

4. 小结与展望

网络安全不容小觑,各大厂商都在尽力去通过技术手段来去维护网络安全,来给用户提供一个安全可靠的网络环境。我们也希望苹果在后续可以在DNS相关方面提供更开放可由开发人员来去实现策略的 API,在保证网络安全基础上又能让开发人员能有一定的灵活性。针对文中 Cronet 的使用由于还在灰度推进中,等推全后也将梳理篇 Cronet 接入带来的利好及坑点来和大家交流。

参考资料

[1] WWDC - 10079

[2] DNSSEC 协议

[3] DNS劫持

[4] DNS欺骗

[5] Enable encrypted DNS

[6] Cronet 网络请求流程

本文发布自网易云音乐技术团队,文章未经授权禁止任何形式的转载。我们常年招收各类技术岗位,如果你准备换工作,又恰好喜欢云音乐,那就加入我们 grp.music-fe(at)corp.netease.com !

网易云音乐技术团队
网易云音乐大前端技术团队专栏

网易云音乐技术团队

3.3k 声望
3.4k 粉丝
0 条评论
推荐阅读
浅尝IM方案在H5活动场景中的应用
说起 IM,大家应该都或多或少了解过一些,一般被熟知是在一些聊天场景里应用的比较多;而一般情况下我们常接触的业务中大多是做一些接口的查询提交之类的操作,用正常的 Ajax 请求就足以满足需求,比较难接触到 I...

云音乐技术团队阅读 135

封面图
网易云音乐开源全链路埋点方案-曙光埋点(dawn)
网易云音乐开源了曙光埋点 dawn,一个跨多端的全链路埋点解决方案,旨在构造一个完美的数据理想国。曙光埋点创造性的提出了埋点虚拟树(VTree),并在此基础上实现了诸多能力,解决大前端侧埋点困难、精度差、不...

云音乐技术团队3阅读 2.4k

封面图
深度剖析 Runtime
做很多需求或者是技术细节验证的时候会用到 Runtime 技术,用了挺久的了,本文就写一些场景和源码分析相关的文章。先问几个小问题:class_rw_t的结构是数组,数组里面的元素是数组,那它是二维数组吗?为什么16字...

杭城小刘4阅读 888

封面图
Github-访问受限解决办法
前言最近发现自己的macbook 使用chrome连接github一直连接不上,于是网上找解决办法,记录下来分享给大家。操作1、获取github.com的IP打开网站websites.ipaddress.com/github.com`复制里面的ip2、打开iTerm2命令...

Awbeci阅读 6.6k

Mac下编译WebRTC(Mac和iOS版本)
随着新冠疫情的影响,这两年音视频的需求呈爆发式增长。在音视频领域中,WebRTC可以说是一个绕不开宝库,包括了音视频采集、编解码、传输、渲染的全过程。本文主要记录下在Mac平台上编译WebRTC Mac和iOS版本的全...

吴尼玛阅读 3.8k

Flutter 下载篇 - 叁 | 网络库切换实践与思考
本文是关于使用flutter_download_manager下载功能的实践和探索。我们将基于flutter_download_manager的功能扩展,改造成自己想要的样子。在阅读本文之前,建议先了解前两篇文章:

听蝉阅读 2.6k

OpenTranslator:一款基于ChatGPT API的翻译神器
这是一款使用 ChatGPT API 进行划词翻译和文本润色的浏览器插件。借助了 ChatGPT 强大的翻译能力,它将帮助您更流畅地阅读外语和编辑外语。

听蝉阅读 2.5k

网易云音乐技术团队

3.3k 声望
3.4k 粉丝
宣传栏