/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ #import "HttpdnsRequest.h" #import "HttpdnsRemoteResolver.h" #import "HttpdnsUtil.h" #import "HttpdnsLog_Internal.h" #import "HttpdnsPublicConstant.h" #import "HttpdnsInternalConstant.h" #import "HttpdnsPersistenceUtils.h" #import "HttpdnsService_Internal.h" #import "HttpdnsScheduleCenter.h" #import "HttpdnsService_Internal.h" #import "HttpdnsReachability.h" #import "HttpdnsRequestManager.h" #import "HttpdnsIpStackDetector.h" #import "HttpdnsNWHTTPClient.h" #import static dispatch_queue_t _streamOperateSyncQueue = 0; @interface HttpdnsRemoteResolver () @property (nonatomic, strong) NSRunLoop *runloop; @property (nonatomic, strong) NSError *networkError; @property (nonatomic, weak) HttpDnsService *service; @property (nonatomic, strong) HttpdnsNWHTTPClient *httpClient; @end @implementation HttpdnsRemoteResolver { NSMutableData *_resultData; NSInputStream *_inputStream; BOOL _responseResolved; BOOL _compeleted; NSTimer *_timeoutTimer; NSDictionary *_httpJSONDict; } #pragma mark init + (void)initialize { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ _streamOperateSyncQueue = dispatch_queue_create("com.New.sdk.httpdns.runloopOperateQueue.HttpdnsRequest", DISPATCH_QUEUE_SERIAL); }); } - (instancetype)init { if (self = [super init]) { _resultData = [NSMutableData data]; _httpJSONDict = nil; self.networkError = nil; _responseResolved = NO; _compeleted = NO; _httpClient = [HttpdnsNWHTTPClient sharedInstance]; } return self; } #pragma mark LookupIpAction - (NSArray *)parseHttpdnsResponse:(NSDictionary *)json withQueryIpType:(HttpdnsQueryIPType)queryIpType { if (!json) { return nil; } // 验证响应? if (![self validateResponseCode:json]) { return nil; } // 获取数据内容 id data = [self extractDataContent:json]; if (!data) { return nil; } // 获取所有答? NSArray *answers = [self getAnswersFromData:data]; if (!answers) { return nil; } // 创建主机对象数组 NSMutableArray *hostObjects = [NSMutableArray array]; for (NSDictionary *answer in answers) { HttpdnsHostObject *hostObject = [self createHostObjectFromAnswer:answer]; if (hostObject) { [hostObjects addObject:hostObject]; } } return hostObjects; } // 验证响应? - (BOOL)validateResponseCode:(NSDictionary *)json { NSString *code = [json objectForKey:@"code"]; if (![code isEqualToString:@"success"]) { HttpdnsLogDebug("Response code is not success: %@", code); return NO; } return YES; } // 获取并处理解密数据内? - (id)extractDataContent:(NSDictionary *)json { // 获取mode,判断是否需要解? NSInteger mode = [[json objectForKey:@"mode"] integerValue]; id data = [json objectForKey:@"data"]; if (mode == 1) { // 只处理AES-CBC模式 // 需要解? data = [self decryptData:data withMode:mode]; } else if (mode != 0) { // 不支持的加密模式(如AES-GCM? HttpdnsLogDebug("Unsupported encryption mode: %ld", (long)mode); return nil; } if (![data isKindOfClass:[NSDictionary class]]) { HttpdnsLogDebug("Data is not a dictionary"); return nil; } return data; } // 解密数据 - (id)decryptData:(id)data withMode:(NSInteger)mode { HttpDnsService *service = self.service ?: [HttpDnsService sharedInstance]; NSString *aesSecretKey = service.aesSecretKey; if (![HttpdnsUtil isNotEmptyString:aesSecretKey]) { HttpdnsLogDebug("Response is encrypted but no AES key is provided"); return nil; } if (![data isKindOfClass:[NSString class]]) { HttpdnsLogDebug("Encrypted data is not a string"); return nil; } // 将Base64字符串转为NSData NSData *encryptedData = [[NSData alloc] initWithBase64EncodedString:data options:0]; if (!encryptedData || encryptedData.length <= 16) { HttpdnsLogDebug("Invalid encrypted data"); return nil; } // 从secretKey转换为二进制密钥 NSData *keyData = [HttpdnsUtil dataFromHexString:aesSecretKey]; if (!keyData) { HttpdnsLogDebug("Invalid AES key format"); return nil; } // 使用工具类解? NSError *decryptError = nil; NSData *decryptedData = [HttpdnsUtil decryptDataAESCBC:encryptedData withKey:keyData error:&decryptError]; if (decryptError || !decryptedData) { HttpdnsLogDebug("Failed to decrypt data: %@", decryptError); return nil; } // 将解密后的JSON数据解析为字? NSError *jsonError; id decodedData = [NSJSONSerialization JSONObjectWithData:decryptedData options:0 error:&jsonError]; if (jsonError) { HttpdnsLogDebug("Failed to parse decrypted JSON: %@", jsonError); return nil; } return decodedData; } // 从数据中获取答案数组 - (NSArray *)getAnswersFromData:(NSDictionary *)data { NSArray *answers = [data objectForKey:@"answers"]; if (![answers isKindOfClass:[NSArray class]] || answers.count == 0) { HttpdnsLogDebug("No answers in response"); return nil; } return answers; } // 从答案创建主机对? - (HttpdnsHostObject *)createHostObjectFromAnswer:(NSDictionary *)answer { // 获取域名 NSString *host = [answer objectForKey:@"dn"]; if (![HttpdnsUtil isNotEmptyString:host]) { HttpdnsLogDebug("Missing domain name in answer"); return nil; } // 创建并填充HostObject HttpdnsHostObject *hostObject = [[HttpdnsHostObject alloc] init]; [hostObject setHostName:host]; // 处理IPv4信息 [self processIPv4Info:answer forHostObject:hostObject]; // 处理IPv6信息 [self processIPv6Info:answer forHostObject:hostObject]; // 自定义ttl [HttpdnsUtil processCustomTTL:hostObject forHost:host service:self.service]; // 设置客户端IP NSString *clientIp = [[answer objectForKey:@"data"] objectForKey:@"cip"]; if ([HttpdnsUtil isNotEmptyString:clientIp]) { [hostObject setClientIp:clientIp]; } return hostObject; } // 处理IPv4信息 - (void)processIPv4Info:(NSDictionary *)answer forHostObject:(HttpdnsHostObject *)hostObject { NSDictionary *v4Data = [answer objectForKey:@"v4"]; if (![v4Data isKindOfClass:[NSDictionary class]]) { return; } NSArray *ip4s = [v4Data objectForKey:@"ips"]; if ([ip4s isKindOfClass:[NSArray class]] && ip4s.count > 0) { // 处理IPv4地址 [self setIpArrayToHostObject:hostObject fromIpsArray:ip4s forIPv6:NO]; // 设置IPv4的TTL [self setTTLForHostObject:hostObject fromData:v4Data forIPv6:NO]; // 处理v4的extra字段,优先使? [self processExtraInfo:v4Data forHostObject:hostObject]; // 检查是否有no_ip_code字段,表示无IPv4记录 if ([[v4Data objectForKey:@"no_ip_code"] isKindOfClass:[NSString class]]) { hostObject.hasNoIpv4Record = YES; } } else { // 没有IPv4地址但有v4节点,可能是无记? hostObject.hasNoIpv4Record = YES; } } // 处理IPv6信息 - (void)processIPv6Info:(NSDictionary *)answer forHostObject:(HttpdnsHostObject *)hostObject { NSDictionary *v6Data = [answer objectForKey:@"v6"]; if (![v6Data isKindOfClass:[NSDictionary class]]) { return; } NSArray *ip6s = [v6Data objectForKey:@"ips"]; if ([ip6s isKindOfClass:[NSArray class]] && ip6s.count > 0) { // 处理IPv6地址 [self setIpArrayToHostObject:hostObject fromIpsArray:ip6s forIPv6:YES]; // 设置IPv6的TTL [self setTTLForHostObject:hostObject fromData:v6Data forIPv6:YES]; // 只有在没有v4 extra的情况下才使用v6的extra if (![hostObject getExtra]) { [self processExtraInfo:v6Data forHostObject:hostObject]; } // 检查是否有no_ip_code字段,表示无IPv6记录 if ([[v6Data objectForKey:@"no_ip_code"] isKindOfClass:[NSString class]]) { hostObject.hasNoIpv6Record = YES; } } else { // 没有IPv6地址但有v6节点,可能是无记? hostObject.hasNoIpv6Record = YES; } } // 设置IP数组到主机对? - (void)setIpArrayToHostObject:(HttpdnsHostObject *)hostObject fromIpsArray:(NSArray *)ips forIPv6:(BOOL)isIPv6 { NSMutableArray *ipArray = [NSMutableArray array]; for (NSString *ip in ips) { if ([HttpdnsUtil isEmptyString:ip]) { continue; } HttpdnsIpObject *ipObject = [[HttpdnsIpObject alloc] init]; [ipObject setIp:ip]; [ipArray addObject:ipObject]; } if (isIPv6) { [hostObject setV6Ips:ipArray]; } else { [hostObject setV4Ips:ipArray]; } } // 设置TTL - (void)setTTLForHostObject:(HttpdnsHostObject *)hostObject fromData:(NSDictionary *)data forIPv6:(BOOL)isIPv6 { NSNumber *ttl = [data objectForKey:@"ttl"]; if (ttl) { if (isIPv6) { hostObject.v6ttl = [ttl longLongValue]; hostObject.lastIPv6LookupTime = [NSDate date].timeIntervalSince1970; } else { hostObject.v4ttl = [ttl longLongValue]; hostObject.lastIPv4LookupTime = [NSDate date].timeIntervalSince1970; } } else { if (isIPv6) { hostObject.v6ttl = 0; } else { hostObject.v4ttl = 0; } } } // 处理额外信息 - (void)processExtraInfo:(NSDictionary *)data forHostObject:(HttpdnsHostObject *)hostObject { id extra = [data objectForKey:@"extra"]; if (extra) { NSString *convertedExtra = [self convertExtraToString:extra]; if (convertedExtra) { [hostObject setExtra:convertedExtra]; } } } - (NSString *)constructHttpdnsResolvingUrl:(HttpdnsRequest *)request forV4Net:(BOOL)isV4 { // 获取基础信息 NSString *serverIp = [self getServerIpForNetwork:isV4]; HttpDnsService *service = self.service; if (![HttpdnsUtil isNotEmptyString:serverIp]) { return nil; } // 准备签名和加密参? NSDictionary *paramsToSign = [self prepareSigningParams:request forEncryption:[self shouldUseEncryption]]; // 计算签名 NSString *signature = [self calculateSignatureForParams:paramsToSign withSecretKey:service.secretKey]; // 构建URL NSString *url = [NSString stringWithFormat:@"%@/v2/d", serverIp]; // 添加所有参数并构建最终URL NSString *finalUrl = [self buildFinalUrlWithBase:url params:paramsToSign isEncrypted:[self shouldUseEncryption] signature:signature request:request]; return finalUrl; } // 获取当前应使用的服务器IP - (NSString *)getServerIpForNetwork:(BOOL)isV4 { HttpdnsScheduleCenter *scheduleCenter = self.service.scheduleCenter; if (!scheduleCenter) { return nil; } return isV4 ? [scheduleCenter currentActiveServiceServerV4Host] : [scheduleCenter currentActiveServiceServerV6Host]; } // 检查是否应该使用加? - (BOOL)shouldUseEncryption { HttpDnsService *service = self.service ?: [HttpDnsService sharedInstance]; return [HttpdnsUtil isNotEmptyString:service.aesSecretKey]; } // 准备需要进行签名的参数 - (NSDictionary *)prepareSigningParams:(HttpdnsRequest *)request forEncryption:(BOOL)useEncryption { HttpDnsService *service = self.service ?: [HttpDnsService sharedInstance]; NSInteger accountId = service.accountID; // 构建参与签名的参数字? NSMutableDictionary *paramsToSign = [NSMutableDictionary dictionary]; // 构建需要加密的参数字典 NSMutableDictionary *paramsToEncrypt = [NSMutableDictionary dictionary]; // 账号ID,参与签名但不加? [paramsToSign setObject:[NSString stringWithFormat:@"%ld", accountId] forKey:@"id"]; // 决定加密模式 NSString *mode = useEncryption ? @"1" : @"0"; // 0: 明文模式, 1: AES-CBC加密模式 [paramsToSign setObject:mode forKey:@"m"]; // 版本号,参与签名但不加密 [paramsToSign setObject:@"1.0" forKey:@"v"]; // 域名参数,参与签名并加密 [paramsToEncrypt setObject:request.host forKey:@"dn"]; // 查询类型,参与签名并加密 [paramsToEncrypt setObject:[self getQueryTypeString:request.queryIpType] forKey:@"q"]; // SDNS参数,参与签名并加密 [self addSdnsParams:request.sdnsParams toParams:paramsToEncrypt]; // 签名过期时间,参与签名但不加? long expiredTimestamp = [self calculateExpiredTimestamp]; NSString *expiredTimestampString = [NSString stringWithFormat:@"%ld", expiredTimestamp]; [paramsToSign setObject:expiredTimestampString forKey:@"exp"]; // 处理加密 if (useEncryption) { NSString *encryptedHexString = [self encryptParams:paramsToEncrypt]; if (encryptedHexString) { [paramsToSign setObject:encryptedHexString forKey:@"enc"]; } } else { // 明文模式下,加密参数也放入签名参数中 [paramsToSign addEntriesFromDictionary:paramsToEncrypt]; } return paramsToSign; } // 获取查询类型字符? - (NSString *)getQueryTypeString:(HttpdnsQueryIPType)queryIpType { if ((queryIpType & HttpdnsQueryIPTypeIpv4) && (queryIpType & HttpdnsQueryIPTypeIpv6)) { return @"4,6"; } else if (queryIpType & HttpdnsQueryIPTypeIpv6) { return @"6"; } return @"4"; } // 添加SDNS参数 - (void)addSdnsParams:(NSDictionary *)sdnsParams toParams:(NSMutableDictionary *)paramsToEncrypt { if ([HttpdnsUtil isNotEmptyDictionary:sdnsParams]) { [sdnsParams enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, NSString * _Nonnull obj, BOOL * _Nonnull stop) { NSString *sdnsKey = [NSString stringWithFormat:@"sdns-%@", key]; [paramsToEncrypt setObject:obj forKey:sdnsKey]; }]; } } // 计算过期时间? - (long)calculateExpiredTimestamp { HttpDnsService *service = self.service ?: [HttpDnsService sharedInstance]; long localTimestampOffset = (long)service.authTimeOffset; long localTimestamp = (long)[[NSDate date] timeIntervalSince1970]; if (localTimestampOffset != 0) { localTimestamp = localTimestamp + localTimestampOffset; } return localTimestamp + HTTPDNS_DEFAULT_AUTH_TIMEOUT_INTERVAL; } // 加密参数 - (NSString *)encryptParams:(NSDictionary *)paramsToEncrypt { NSError *error = nil; HttpDnsService *service = self.service ?: [HttpDnsService sharedInstance]; // 将待加密参数转为JSON NSData *jsonData = [NSJSONSerialization dataWithJSONObject:paramsToEncrypt options:0 error:&error]; if (error) { HttpdnsLogDebug("Failed to serialize params to JSON: %@", error); return nil; } // 从secretKey转换为二进制密钥 NSData *keyData = [HttpdnsUtil dataFromHexString:service.aesSecretKey]; if (!keyData) { HttpdnsLogDebug("Invalid AES key format"); return nil; } // 目前在OC中没有比较好的实现AES-GCM的方式,因此这里选择AES-CBC加密 NSData *encryptedData = [HttpdnsUtil encryptDataAESCBC:jsonData withKey:keyData error:&error]; if (error) { HttpdnsLogDebug("Failed to encrypt data: %@", error); return nil; } // 将加密结果转为十六进制字符串 return [HttpdnsUtil hexStringFromData:encryptedData]; } // 计算签名 - (NSString *)calculateSignatureForParams:(NSDictionary *)params withSecretKey:(NSString *)secretKey { if (![HttpdnsUtil isNotEmptyString:secretKey]) { return nil; } // 按照签名要求对参数进行排序并生成签名内容 NSArray *sortedKeys = [[params allKeys] sortedArrayUsingSelector:@selector(compare:)]; NSMutableArray *signParts = [NSMutableArray array]; for (NSString *key in sortedKeys) { [signParts addObject:[NSString stringWithFormat:@"%@=%@", key, [params objectForKey:key]]]; } // 组合签名字符? NSString *signContent = [signParts componentsJoinedByString:@"&"]; // 计算HMAC-SHA256签名 return [HttpdnsUtil hmacSha256:signContent key:secretKey]; } // 构建最终URL - (NSString *)buildFinalUrlWithBase:(NSString *)baseUrl params:(NSDictionary *)params isEncrypted:(BOOL)useEncryption signature:(NSString *)signature request:(HttpdnsRequest *)request { HttpDnsService *service = self.service ?: [HttpDnsService sharedInstance]; NSMutableString *finalUrl = [NSMutableString stringWithString:baseUrl]; [finalUrl appendString:@"?"]; // 首先添加必要参数 [finalUrl appendFormat:@"id=%ld", service.accountID]; [finalUrl appendFormat:@"&m=%@", [params objectForKey:@"m"]]; [finalUrl appendFormat:@"&exp=%@", [params objectForKey:@"exp"]]; [finalUrl appendFormat:@"&v=%@", [params objectForKey:@"v"]]; if (useEncryption) { // 加密模式下,添加enc参数 [finalUrl appendFormat:@"&enc=%@", [params objectForKey:@"enc"]]; } else { // 明文模式下,添加所有参? NSMutableDictionary *paramsForPlainText = [NSMutableDictionary dictionaryWithDictionary:params]; [paramsForPlainText removeObjectForKey:@"id"]; [paramsForPlainText removeObjectForKey:@"m"]; [paramsForPlainText removeObjectForKey:@"exp"]; [paramsForPlainText removeObjectForKey:@"v"]; for (NSString *key in paramsForPlainText) { // 跳过已添加的参数 if ([key isEqualToString:@"id"] || [key isEqualToString:@"m"] || [key isEqualToString:@"exp"] || [key isEqualToString:@"v"]) { continue; } NSString *value = [paramsForPlainText objectForKey:key]; [finalUrl appendFormat:@"&%@=%@", [HttpdnsUtil URLEncodedString:key], [HttpdnsUtil URLEncodedString:value]]; } } // 添加签名(如果有? if ([HttpdnsUtil isNotEmptyString:signature]) { [finalUrl appendFormat:@"&s=%@", signature]; } // 添加不参与签名的其他参数 [self appendAdditionalParams:finalUrl]; return finalUrl; } // 添加额外的不参与签名的参? - (void)appendAdditionalParams:(NSMutableString *)url { // sessionId NSString *sessionId = [HttpdnsUtil generateSessionID]; if ([HttpdnsUtil isNotEmptyString:sessionId]) { [url appendFormat:@"&sid=%@", sessionId]; } // 网络类型 NSString *netType = [[HttpdnsReachability sharedInstance] currentReachabilityString]; if ([HttpdnsUtil isNotEmptyString:netType]) { [url appendFormat:@"&net=%@", netType]; } // SDK版本 NSString *versionInfo = [NSString stringWithFormat:@"ios_%@", HTTPDNS_IOS_SDK_VERSION]; [url appendFormat:@"&sdk=%@", versionInfo]; } - (NSArray *)resolve:(HttpdnsRequest *)request error:(NSError **)error { HttpdnsLogDebug("lookupHostFromServer, request: %@", request); HttpDnsService *service = [HttpDnsService getInstanceByAccountId:request.accountId]; if (!service) { HttpdnsLogDebug("Missing service for accountId: %ld; ensure request.accountId is set and service initialized", (long)request.accountId); if (error) { *error = [NSError errorWithDomain:NEW_HTTPDNS_ERROR_DOMAIN code:NEW_HTTPDNS_HTTPS_COMMON_ERROR_CODE userInfo:@{NSLocalizedDescriptionKey: @"HttpDnsService not found for accountId"}]; } return nil; } self.service = service; NSString *url = [self constructHttpdnsResolvingUrl:request forV4Net:YES]; HttpdnsQueryIPType queryIPType = request.queryIpType; NSArray *hostObjects = [self sendRequest:url queryIpType:queryIPType error:error]; if (!(*error)) { return hostObjects; } @try { HttpdnsIPStackType stackType = [[HttpdnsIpStackDetector sharedInstance] currentIpStack]; // 由于上面默认只用ipv4请求,这里判断如果是ipv6-only环境,那就用v6的ip再试一? if (stackType == kHttpdnsIpv6Only) { url = [self constructHttpdnsResolvingUrl:request forV4Net:NO]; HttpdnsLogDebug("lookupHostFromServer by ipv4 server failed, construct ipv6 backup url: %@", url); hostObjects = [self sendRequest:url queryIpType:queryIPType error:error]; if (!(*error)) { return hostObjects; } } } @catch (NSException *exception) { HttpdnsLogDebug("lookupHostFromServer failed again by ipv6 server, exception: %@", exception.reason); } return nil; } - (NSArray *)sendRequest:(NSString *)urlStr queryIpType:(HttpdnsQueryIPType)queryIpType error:(NSError **)error { if (![HttpdnsUtil isNotEmptyString:urlStr]) { if (error) { *error = [NSError errorWithDomain:NEW_HTTPDNS_ERROR_DOMAIN code:NEW_HTTPDNS_HTTPS_COMMON_ERROR_CODE userInfo:@{NSLocalizedDescriptionKey: @"Empty resolve URL due to missing scheduler"}]; } return nil; } HttpDnsService *httpdnsService = self.service; NSString *scheme = httpdnsService.enableHttpsRequest ? @"https" : @"http"; NSString *fullUrlStr = [NSString stringWithFormat:@"%@://%@", scheme, urlStr]; NSTimeInterval timeout = httpdnsService.timeoutInterval > 0 ? httpdnsService.timeoutInterval : 10.0; NSString *userAgent = [HttpdnsUtil generateUserAgent]; HttpdnsNWHTTPClientResponse *httpResponse = [self.httpClient performRequestWithURLString:fullUrlStr userAgent:userAgent timeout:timeout error:error]; if (!httpResponse) { return nil; } if (httpResponse.statusCode != 200) { if (error) { NSString *errorMessage = [NSString stringWithFormat:@"Unsupported http status code: %ld", (long)httpResponse.statusCode]; *error = [NSError errorWithDomain:NEW_HTTPDNS_ERROR_DOMAIN code:NEW_HTTP_UNSUPPORTED_STATUS_CODE userInfo:@{NSLocalizedDescriptionKey: errorMessage}]; } return nil; } NSError *jsonError = nil; id jsonValue = [NSJSONSerialization JSONObjectWithData:httpResponse.body options:kNilOptions error:&jsonError]; if (jsonError) { if (error) { *error = jsonError; } return nil; } NSDictionary *json = [HttpdnsUtil getValidDictionaryFromJson:jsonValue]; if (!json) { if (error) { *error = [NSError errorWithDomain:NEW_HTTPDNS_ERROR_DOMAIN code:NEW_HTTP_PARSE_JSON_FAILED userInfo:@{NSLocalizedDescriptionKey: @"Failed to parse JSON response"}]; } return nil; } return [self parseHttpdnsResponse:json withQueryIpType:queryIpType]; } #pragma mark - Helper Functions // 将extra字段转换为NSString类型 - (NSString *)convertExtraToString:(id)extra { if (!extra) { return nil; } if ([extra isKindOfClass:[NSString class]]) { // 已经是字符串,直接返? return extra; } else { // 非字符串,尝试转换为JSON字符? NSError *error = nil; NSData *jsonData = [NSJSONSerialization dataWithJSONObject:extra options:0 error:&error]; if (!error && jsonData) { NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; return jsonString; } else { HttpdnsLogDebug("Failed to convert extra to JSON string: %@", error); return nil; } } } @end