Files
waf-platform/HttpDNSSDK/sdk/ios/NewHttpDNS/IpStack/HttpdnsIpStackDetector.m

186 lines
5.9 KiB
Objective-C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// HttpdnsIpStackDetector.m
// NewHttpDNS
//
// Created by xuyecan on 2025/3/16.
// Copyright © 2025 new-inc.com. All rights reserved.
//
#import "HttpdnsIpStackDetector.h"
#import "HttpdnsLog_Internal.h"
#include <strings.h>
#include <errno.h>
#include <unistd.h>
#include <netinet/in.h>
#include <sys/socket.h>
typedef union httpdns_sockaddr_union {
struct sockaddr httpdns_generic;
struct sockaddr_in httpdns_in;
struct sockaddr_in6 httpdns_in6;
} httpdns_sockaddr_union;
/*
* 连接UDP套接字到指定的单播地址。这不会产生网络流量
* 但如果系统对目标没有或有限的可达性例如没有IPv4地址没有IPv6默认路由等
* 将快速失败。
*/
static const unsigned int kMaxLoopCount = 10;
static int httpdns_test_connect(int pf, struct sockaddr * addr, size_t addrlen) {
int s = socket(pf, SOCK_DGRAM, IPPROTO_UDP);
if (s < 0) {
return 0;
}
int ret;
unsigned int loop_count = 0;
do {
ret = connect(s, addr, (socklen_t)addrlen);
} while (ret < 0 && errno == EINTR && loop_count++ < kMaxLoopCount);
if (loop_count >= kMaxLoopCount) {
HttpdnsLogDebug("connect error. loop_count = %d", loop_count);
}
int success = (ret == 0);
loop_count = 0;
do {
ret = close(s);
} while (ret < 0 && errno == EINTR && loop_count++ < kMaxLoopCount);
if (loop_count >= kMaxLoopCount) {
HttpdnsLogDebug("close error. loop_count = %d", loop_count);
}
return success;
}
/*
* 以下函数用于确定IPv4或IPv6连接是否可用以实现AI_ADDRCONFIG。
*
* 严格来说AI_ADDRCONFIG不应该检查连接是否可用
* 而是检查指定协议族的地址是否"在本地系统上配置"。
* 然而bionic目前不支持getifaddrs
* 所以检查连接是下一个最佳选择。
*/
static int httpdns_have_ipv6(void) {
static struct sockaddr_in6 sin6_test = {0};
sin6_test.sin6_family = AF_INET6;
sin6_test.sin6_port = 80;
sin6_test.sin6_flowinfo = 0;
sin6_test.sin6_scope_id = 0;
bzero(sin6_test.sin6_addr.s6_addr, sizeof(sin6_test.sin6_addr.s6_addr));
sin6_test.sin6_addr.s6_addr[0] = 0x20;
// union
httpdns_sockaddr_union addr = {.httpdns_in6 = sin6_test};
return httpdns_test_connect(PF_INET6, &addr.httpdns_generic, sizeof(addr.httpdns_in6));
}
static int httpdns_have_ipv4(void) {
static struct sockaddr_in sin_test = {0};
sin_test.sin_family = AF_INET;
sin_test.sin_port = 80;
sin_test.sin_addr.s_addr = htonl(0x08080808L); // 8.8.8.8
// union
httpdns_sockaddr_union addr = {.httpdns_in = sin_test};
return httpdns_test_connect(PF_INET, &addr.httpdns_generic, sizeof(addr.httpdns_in));
}
/**
* 基于IPv4和IPv6连接检测当前IP协议栈类型
*/
static HttpdnsIPStackType detectIpStack(void) {
int hasIPv4 = httpdns_have_ipv4();
int hasIPv6 = httpdns_have_ipv6();
HttpdnsLogDebug("IP stack detection: IPv4=%d, IPv6=%d", hasIPv4, hasIPv6);
if (hasIPv4 && hasIPv6) {
return kHttpdnsIpDual;
} else if (hasIPv4) {
return kHttpdnsIpv4Only;
} else if (hasIPv6) {
return kHttpdnsIpv6Only;
} else {
return kHttpdnsIpUnknown;
}
}
@implementation HttpdnsIpStackDetector {
HttpdnsIPStackType _lastDetectedIpStack;
dispatch_queue_t _detectSerialQueue; // 用于控制检测操作的串行队列
dispatch_queue_t _updateSerialQueue;
BOOL _isDetecting; // 标记是否正在进行检测
NSTimeInterval _lastDetectionTime; // 上次检测的时间戳
}
+ (instancetype)sharedInstance {
static HttpdnsIpStackDetector *instance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[self alloc] init];
});
return instance;
}
- (instancetype)init {
self = [super init];
if (self) {
_lastDetectedIpStack = kHttpdnsIpUnknown;
_isDetecting = NO;
_lastDetectionTime = 0;
// 创建串行队列用于控制IP栈检测的并发
_detectSerialQueue = dispatch_queue_create("com.new.httpdns.ipstack.detect", DISPATCH_QUEUE_SERIAL);
_updateSerialQueue = dispatch_queue_create("com.new.httpdns.ipstack.update", DISPATCH_QUEUE_SERIAL);
}
return self;
}
- (HttpdnsIPStackType)currentIpStack {
// 如果不在主队列,同步获取主队列中的值以确保线程安全
__block HttpdnsIPStackType result;
dispatch_sync(_updateSerialQueue, ^{
result = self->_lastDetectedIpStack;
});
return result;
}
- (BOOL)isIpv6OnlyNetwork {
return [self currentIpStack] == kHttpdnsIpv6Only;
}
- (void)redetectIpStack {
// 完全异步执行,将检查逻辑放在串行队列中
dispatch_async(_detectSerialQueue, ^{
// 如果已经在检测中,直接返回
if (self->_isDetecting) {
HttpdnsLogDebug("IP stack detection already in progress, skipping");
return;
}
// 检查是否满足最小时间间隔要求1秒
NSTimeInterval currentTime = [[NSDate date] timeIntervalSince1970];
NSTimeInterval timeSinceLastDetection = currentTime - self->_lastDetectionTime;
if (timeSinceLastDetection < 1.0) {
return;
}
// 更新最后检测时间
self->_lastDetectionTime = currentTime;
// 标记为正在检测并执行检测
self->_isDetecting = YES;
// 执行实际的检测操作(已经在串行队列中,无需再次异步)
HttpdnsIPStackType detectedStack = detectIpStack();
// 在主队列中更新结果,确保线程安全
dispatch_async(self->_updateSerialQueue, ^{
self->_lastDetectedIpStack = detectedStack;
// 重置检测状态(已经在串行队列的异步块中,完成后再次异步到串行队列)
dispatch_async(self->_detectSerialQueue, ^{
self->_isDetecting = NO;
});
});
});
}
@end