iOS实现ip直连的几种方案

背景

  1. 有些域名可能会被劫持,或者被封,导致线上用户无法使用某些功能
  2. iOS系统没有提供本地dns解析的方案,难以像安卓的okhttp那样,直接提供一个dns解析规则给sdk,则接口就就使用该条解析规则进行网络请求

本文针对我前一段时间进行iOS相关的ip直连资料查找和研究做个记录,如写得不对请帮我指出,感激。

方案

1. 替换host信任ip

这个方案就是把host用ip替换掉,然后进行网络请求, 比如请求:www.baidu.com 替换掉host,url为:https://157.148.69.74, 这时候会触发证书信任错误,这时候不管三七二十一就直接信任返回的证书即可,证书信任的代码如下:

swift 复制代码
func urlSession(_ session: URLSession,
                    didReceive challenge: URLAuthenticationChallenge,
                    completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
        DispatchQueue.global().async {
            // 收到证书挑战,先判断需要认证的方法是不是需要认证服务器证书
            if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust,
               // 从传上来的挑战对象中取出serverTrust对象
               let serverTrust = challenge.protectionSpace.serverTrust {
                // 从serverTrust对象中取出证书
                let credential = URLCredential(trust: serverTrust)
                // 返回该证书,即表示信任这个证书
                completionHandler(.useCredential, credential)
            } else {
                completionHandler(.performDefaultHandling, nil)
            }
        }
    }

这是最干脆的无脑信任证书,有些文章还会在信任前加一些判断,判断challenge.protectionSpace.host是否和header里的host是否一致,其实对这里信任没啥帮助,只是增加一些安全,比如,用百度的ip去请求,服务器返回别的证书,比如map.baidu.com,则这里无法匹配,这个情况在云服务器很常见,就是sni的情况,云服务器就是多对多的情况,ip是多个,证书也是多个。握手的时候必须要支持sni协议,在header中添加host,而iOS的URLSession就完全不管header中的字段,只拿url的host进行握手,就很气人。

说个题外话,这里最好用线程返回,用主线程会报线程警告。

这个方案在iOS17以下使用都没有问题,但是iOS17又加强安全了,使用上面的信任之后,依然报ssl错误,错误码为-1200,找了一下ios的更新日志,发现是ios17提高ats的安全级别了, iOS17更新日志 nsallowslocalnetworking

解决方案就是在info.plist中添加Allow Arbitrary Loads并置为YES,表示支持任意请求,如果不能开这个开关,而服务器又是SNI的,则目前这个方案还没有什么好的办法。

2. 支持sni的网络库-CFNetwork

CFNetwork是一个iOS的底层库,这里有一个kCFStreamPropertySSLSettings进行设置握手的host,这样在握手的时候就有host了

c 复制代码
// 创建CFHTTPMessage对象的输入流
    CFReadStreamRef readStream = CFReadStreamCreateForHTTPRequest(kCFAllocatorDefault, cfrequest);
    inputStream = (__bridge_transfer NSInputStream *) readStream;
    
    // 设置SNI host信息,关键步骤
    NSString *host = [curRequest.allHTTPHeaderFields objectForKey:@"host"];
    if (!host) {
        host = curRequest.URL.host;
    }
    [inputStream setProperty:NSStreamSocketSecurityLevelNegotiatedSSL forKey:NSStreamSocketSecurityLevelKey];
    NSDictionary *sslProperties = [[NSDictionary alloc] initWithObjectsAndKeys:
                                   host, (__bridge id) kCFStreamSSLPeerName,
                                   nil];
    [inputStream setProperty:sslProperties forKey:(__bridge_transfer NSString *) kCFStreamPropertySSLSettings];

虽然苹果把CFNetwok的api都标记为废弃,但其实URLSession和Network都是对接CFNetwork库,只是api比较底层,官方不建议使用

另一个问题就是CFNetwork是很底层的库,需要我们自己封装处理并发代理证书等诸多问题

不过腾讯的httpdns库里包含了一个CFNetwork的实现,里面的代码看不到,不知道实现得怎么样,我也没试过,阿http dns里就差劲了,就提供了一两个文章,把方案描述一下,让开发者自己抉择。

3. 支持sni的网络库-curl

curl是一个c级别的网络底层库,mac和linux系统都自带这个库,但iOS没有,使用这个库需要先去打包成iOS的网络库,然后集成到项目中,api也是c语言的,跟CFNetwork一样,也需要自己处理诸多问题,我觉得这个方案也不好,不多展开讨论

4. 支持sni的网络库-封装的curl库

之前搜索资料的时候找到一个oc库,是参考swift foundation里编写的一个网络库,名字不太记得了,当时用了一下,有些情况会卡死,于是用了一下就没使用了,后面自己把swift foundation中的网络库抽出来了,就没去理会这个库了,官方的应该更有保障吧

官方swift foundation库地址是swift-corelibs-foundation,这个是苹果为swift跨平台而抽离出来一个基础库,20年还有一些进度,后面就没进度了,还有大半api未完成,URLSession也有部分功能未完成,比如URLAuthenticationChallenge就没完成,吐槽中,具体完成度[# Implementation Status](swift-corelibs-foundation/Docs/Status.md at main · apple/swift-corelibs-foundation (github.com))

我从swif foundation中抽离出来网络库名为SSURLSession,zhtut/SSURLSession,把URLSession相关的代码都抽出来了,一开始手动抽,苹果有更新的时候又得维护,后面搞了一个python脚本更新,方便多了。包括编译curl,也是一起做了。

目前只支持swift,使用方法就是多调用一个SSURLSession,比如SSURLSession.URLSession.shared,其他都一样。

ip直连的用法,有两个key可以使用,效果应该是一样的,都是在URLRequest的header中添加key,第一个是resolve

swift 复制代码
let resolve = "\(host):443:\(ip)"
request.setValue(resolve, forHTTPHeaderField: "resolve")

第二个是connectTo

swift 复制代码
let connectTo = "\(host):443:\(ip):443"
request.setValue(connectTo, forHTTPHeaderField: "connectTo")

设置了之后就可以用SSURLSession.URLSession去发送请求了

不用替换url的host

这个方案的缺点也很明显,官方有些功能未完成,导致有些功能无法使用,比如URLAuthenticationChallenge未完成,抓包的时候,也收不到证书挑战的通知,导致ssl pining无法使用,可能存在一些其他问题,并且要集成一个额外的网络库,增大了包体积。

而且而且商用不够,希望大厂能派出大佬或团队,来编写一个iOS的支持直连的强大网络库,打压一下苹果独断专行的气焰。

结论

目前还是无法很好的解决ip直连的问题

第一次写技术文章,很多语法不熟悉,排版不好见谅。

相关推荐
咕噜签名分发冰淇淋4 小时前
免下载苹果 IPA 文件重签名工具:快速更换应用名称和 BID的教程
ios
二流小码农13 小时前
鸿蒙开发:DevEcoStudio中的代码提取
android·ios·harmonyos
Digitally19 小时前
如何用4 种可靠的方法更换 iPhone(2025 年指南)
ios·iphone
9765033351 天前
iOS 审核 cocos 4.3a【苹果机审的“分层阈值”设计】
flutter·游戏·unity·ios
I烟雨云渊T1 天前
iOS Alamofire库的使用
ios
程序员老刘·1 天前
iOS 26 beta1 真机无法执行hot reload
flutter·ios·跨平台开发·客户端开发
EndingCoder1 天前
React Native 构建与打包发布(iOS + Android)
android·react native·ios
程序员小刘1 天前
HarmonyOS 5鸿蒙多端编译实战:从Android/iOS到HarmonyOS 5 的跨端迁移指南详
android·ios·华为·harmonyos
I烟雨云渊T1 天前
iOS swiftUI的实用举例
ios·swiftui·swift
getapi1 天前
将 App 安装到 iPhone 真机上测试
ios·iphone