Post

获取本地ip及域名对应ip

获取本地ip及域名对应ip

参考:

前言

最近部分用户socket一直连不上服务器,将日志给运维看时,运维说只能看到域名信息,检查了该域名在用户所在地区没什么问题,如果我们能提供该用户调用域名时解析的ip更方便排查,于是特研究记录了连接socket时域名解析成ip的过程并记录日志

完整获取域名解析的ip代码 TestDomainIP.h

1
2
3
4
#import <Foundation/Foundation.h>
@interface TestDomainIP: NSObject
+ (void)getDomainToIp:(NSString *)domain port:(int)port;
@end

TestDomainIP.m

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#import <netdb.h>
#import <arpa/inet.h>
@interface TestDomainIP()
@end

@implementation TestDomainIP
+ (void)getDomainToIp:(NSString *)domain port:(int)port {
    NSString *portStr = [NSString stringWithFormat:@"%d", port];
        
    struct addrinfo hints, *res;
        
    memset(&hints, 0, sizeof(hints));
    hints.ai_family   = PF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_protocol = IPPROTO_TCP;
    // 使用系统方法将域名解析成ip
    int gai_error = getaddrinfo([domain UTF8String], [portStr UTF8String], &hints, &res);
    if (!gai_error) {
        if (res->ai_family == AF_INET){
            // 获取ipv4
            // 强转类型sockaddr * => sockaddr_in *
            struct sockaddr_in *ipv4 = (struct sockaddr_in *)res->ai_addr;
            char ipAddress[INET_ADDRSTRLEN];
            // 将解析到的ip字节流信息转为字符串
            inet_ntop(AF_INET, &(ipv4->sin_addr), ipAddress, INET_ADDRSTRLEN);
            printf("The IP address is: %s\n", ipAddress);
            // 将C语言字符串转换为OC字符串
            // NSString *ipHost = [NSString stringWithFormat:@"%s",ipAddress];
            NSString *ipHost = [NSString stringWithUTF8String: ipAddress];
            NSLog(@"The domain is: %@ IP address is: %@\n", domain, ipHost);
        }
    }
}
@end

测试:

1
2
3
TestDomainIP.getDomainToIp("www.baidu.com", port: 443)
The IP address is: 183.2.172.42
The domain is: www.baidu.com IP address is: 183.2.172.42

CocoaAsyncSocket流程及修改

使用的socket连接库CocoaAsyncSocket,该库的链接过程: try socket.connect(toHost: xx, onPort: xx) -> …
-> - (BOOL)connectToHost:(NSString *)inHost onPort:(uint16_t)port viaInterface:(NSString *)inInterface withTimeout:(NSTimeInterval)timeout error:(NSError **)errPtr -> + (NSMutableArray *)lookupHost:(NSString *)host port:(uint16_t)port error:(NSError **)errPtr

getaddrinfo([host UTF8String], [portStr UTF8String], &hints, &res0);: 使用系统获取域名解析成ip addrinfo:系统提供的解析域名的信息 ai_family:域名类型(AF_INET=ipv4、AF_INET6=ipv6) ai_addr:域名内容(类型为:sockaddr *ai_addrlen:域名内容数据长度

CocoaAsyncSocket里核心将域名转换为ip的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#import <netinet/in.h>
#import <ifaddrs.h>
#import <sys/socket.h>
        // 将端口数字转换为字符串
        NSString *portStr = [NSString stringWithFormat:@"%hu", port];
		
        struct addrinfo hints, *res, *res0;
		
        memset(&hints, 0, sizeof(hints));
        hints.ai_family   = PF_UNSPEC;
		hints.ai_socktype = SOCK_STREAM;
		hints.ai_protocol = IPPROTO_TCP;
		// 使用系统方法将域名解析成ip
		int gai_error = getaddrinfo([host UTF8String], [portStr UTF8String], &hints, &res0);
		
		if (gai_error)
		{
			error = [self gaiError:gai_error];
		}
		else
		{
            // 解析正确
			NSUInteger capacity = 0;
			for (res = res0; res; res = res->ai_next)
			{
				if (res->ai_family == AF_INET || res->ai_family == AF_INET6) { 
                    // 如果是IPV4或IPV6的地址
					capacity++;
				}
			}
			
			addresses = [NSMutableArray arrayWithCapacity:capacity];
			
			for (res = res0; res; res = res->ai_next)
			{
				if (res->ai_family == AF_INET)
				{
					// Found IPv4 address. IPV4的地址
					// Wrap the native address structure, and add to results.
					NSData *address4 = [NSData dataWithBytes:res->ai_addr length:res->ai_addrlen];
					[addresses addObject:address4];
				}
				else if (res->ai_family == AF_INET6)
				{
					// Fixes connection issues with IPv6
					// https://github.com/robbiehanson/CocoaAsyncSocket/issues/429#issuecomment-222477158
					
					// Found IPv6 address.
					// Wrap the native address structure, and add to results.
					
					struct sockaddr_in6 *sockaddr = (struct sockaddr_in6 *)(void *)res->ai_addr;
					in_port_t *portPtr = &sockaddr->sin6_port;
					if ((portPtr != NULL) && (*portPtr == 0)) {
					        *portPtr = htons(port);
					}

					NSData *address6 = [NSData dataWithBytes:res->ai_addr length:res->ai_addrlen];
					[addresses addObject:address6];
				}
			}
			freeaddrinfo(res0);
			
			if ([addresses count] == 0)
			{
				error = [self gaiError:EAI_FAIL];
			}
		}

修改源码部分一、(GCDAsyncSocket.h):在socket正式开始连接时增加delegate方法

1
2
3
4
5
6
7
8
@protocol GCDAsyncSocketDelegate <NSObject>
@optional

// ...

- (void)socketDidGetIP:(NSString *)ipHost;

// ...

修改源码部分二、(GCDAsyncSocket.m):在socket正式开始连接时增加下回调

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
@implementation GCDAsyncSocket
// ....
- (BOOL)connectWithAddress4:(NSData *)address4 address6:(NSData *)address6 error:(NSError **)errPtr
{
	LogTrace();
	
	NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
	
	LogVerbose(@"IPv4: %@:%hu", [[self class] hostFromAddress:address4], [[self class] portFromAddress:address4]);
	LogVerbose(@"IPv6: %@:%hu", [[self class] hostFromAddress:address6], [[self class] portFromAddress:address6]);
	
	// Determine socket type
	
	BOOL preferIPv6 = (config & kPreferIPv6) ? YES : NO;
	
	// Create and bind the sockets
    
    if (address4)
    {
        LogVerbose(@"Creating IPv4 socket");
        
        socket4FD = [self createSocket:AF_INET connectInterface:connectInterface4 errPtr:errPtr];
    }
    
    if (address6)
    {
        LogVerbose(@"Creating IPv6 socket");
        
        socket6FD = [self createSocket:AF_INET6 connectInterface:connectInterface6 errPtr:errPtr];
    }
    
    if (socket4FD == SOCKET_NULL && socket6FD == SOCKET_NULL)
    {
        return NO;
    }
	
	int socketFD, alternateSocketFD;
	NSData *address, *alternateAddress;
	
    if ((preferIPv6 && socket6FD != SOCKET_NULL) || socket4FD == SOCKET_NULL)
    {
        socketFD = socket6FD;
        alternateSocketFD = socket4FD;
        address = address6;
        alternateAddress = address4;
    }
    else
    {
        socketFD = socket4FD;
        alternateSocketFD = socket6FD;
        address = address4;
        alternateAddress = address6;
        
        __strong id<GCDAsyncSocketDelegate> theDelegate = delegate;
        
        if (delegateQueue && [theDelegate respondsToSelector: @selector(socketDidGetIP:inPort:ipHost:)])
        {
            // 强转类型sockaddr * => sockaddr_in *
            struct sockaddr_in *ipv4 = (struct sockaddr_in *)[address4 bytes];;
            char ipAddress[INET_ADDRSTRLEN];
            // 将解析到的ip字节流信息转为字符串
            inet_ntop(AF_INET, &(ipv4->sin_addr), ipAddress, INET_ADDRSTRLEN);
            // printf("The IP address is: %s\n", ipAddress);
            // 将C语言字符串转换为OC字符串
            NSString *ipHost = [NSString stringWithFormat:@"%s",ipAddress];
            dispatch_async(delegateQueue, ^{ @autoreleasepool {
                [theDelegate socketDidGetIP:ipHost];
            }});
        }
    }

    int aStateIndex = stateIndex;
    
    [self connectSocket:socketFD address:address stateIndex:aStateIndex];
    
    if (alternateAddress)
    {
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(alternateAddressDelay * NSEC_PER_SEC)), socketQueue, ^{
            [self connectSocket:alternateSocketFD address:alternateAddress stateIndex:aStateIndex];
        });
    }
	
	return YES;
}
// ...
This post is licensed under CC BY 4.0 by the author.