bdunagan
fill the void

Posted
28 November 2009 @ 10pm

Tagged
development

22 Comments

iPhone Tip: No NSHost

On the iPhone, I miss NSHost. CocoaTouch does contain a private version of this Cocoa class, but again, it is private. Recently, I needed to resolve a DNS name to an IP address in an iPhone app. It’s a one-liner in Cocoa: [[NSHost hostWithName:@"www.google.com"] address]. Not so on the iPhone without NSHost. So, with a little help from Apple’s CFHostSample sample project and their docs on CFHost, I put together this short snippet.

2010-02-09 Update:
What about the local IP addresses and ethernet MAC addresses? Building off the code from Zach Waugh (developer of QuickPic), I added two methods for getting the set of IP addresses and ethernet addresses. The wireless connection seems to be en0, while the cellular connection is pdp_ip0. Keep in mind that the IP addresses are what the device believes, not what the outside world sees (What Is My IP?), thanks to NATs.

2010-08-17 Update:
What about names for addresses? Again referencing Apple’s CFHostSample, I added a quick wrapper for CFHostGetNames.

// MIT license
// Remember to add CFNetwork.framework to your project using Add=>Existing Frameworks.

#import "BDHost.h"
#import <CFNetwork/CFNetwork.h>
#import <netinet/in.h>
#import <netdb.h>
#import <ifaddrs.h>
#import <arpa/inet.h>
#import <net/ethernet.h>
#import <net/if_dl.h>

@implementation BDHost

+ (NSString *)addressForHostname:(NSString *)hostname {
	NSArray *addresses = [BDHost addressesForHostname:hostname];
	if ([addresses count] > 0)
		return [addresses objectAtIndex:0];
	else
		return nil;
}

+ (NSArray *)addressesForHostname:(NSString *)hostname {
	// Get the addresses for the given hostname.
	CFHostRef hostRef = CFHostCreateWithName(kCFAllocatorDefault, (CFStringRef)hostname);
	BOOL isSuccess = CFHostStartInfoResolution(hostRef, kCFHostAddresses, nil);
	if (!isSuccess) return nil;
	CFArrayRef addressesRef = CFHostGetAddressing(hostRef, nil);
	if (addressesRef == nil) return nil;
	
	// Convert these addresses into strings.
	char ipAddress[INET6_ADDRSTRLEN];
	NSMutableArray *addresses = [NSMutableArray array];
	CFIndex numAddresses = CFArrayGetCount(addressesRef);
	for (CFIndex currentIndex = 0; currentIndex < numAddresses; currentIndex++) {
		struct sockaddr *address = (struct sockaddr *)CFDataGetBytePtr(CFArrayGetValueAtIndex(addressesRef, currentIndex));
		if (address == nil) return nil;
		getnameinfo(address, address->sa_len, ipAddress, INET6_ADDRSTRLEN, nil, 0, NI_NUMERICHOST);
		if (ipAddress == nil) return nil;
		[addresses addObject:[NSString stringWithCString:ipAddress encoding:NSASCIIStringEncoding]];
	}
	
	return addresses;
}

+ (NSString *)hostnameForAddress:(NSString *)address {
	NSArray *hostnames = [BDHost hostnamesForAddress:address];
	if ([hostnames count] > 0)
		return [hostnames objectAtIndex:0];
	else
		return nil;
}

+ (NSArray *)hostnamesForAddress:(NSString *)address {
	// Get the host reference for the given address.
    struct addrinfo      hints;
    struct addrinfo      *result = NULL;
	memset(&hints, 0, sizeof(hints));
	hints.ai_flags    = AI_NUMERICHOST;
	hints.ai_family   = PF_UNSPEC;
	hints.ai_socktype = SOCK_STREAM;
	hints.ai_protocol = 0;
	int errorStatus = getaddrinfo([address cStringUsingEncoding:NSASCIIStringEncoding], NULL, &hints, &result);
	if (errorStatus != 0) return nil;
	CFDataRef addressRef = CFDataCreate(NULL, (UInt8 *)result->ai_addr, result->ai_addrlen);
	if (addressRef == nil) return nil;
	freeaddrinfo(result);
	CFHostRef hostRef = CFHostCreateWithAddress(kCFAllocatorDefault, addressRef);
	if (hostRef == nil) return nil;
	CFRelease(addressRef);
	BOOL isSuccess = CFHostStartInfoResolution(hostRef, kCFHostNames, NULL);
	if (!isSuccess) return nil;
	
	// Get the hostnames for the host reference.
	CFArrayRef hostnamesRef = CFHostGetNames(hostRef, NULL);
	NSMutableArray *hostnames = [NSMutableArray array];
	for (int currentIndex = 0; currentIndex < [(NSArray *)hostnamesRef count]; currentIndex++) {
		[hostnames addObject:[(NSArray *)hostnamesRef objectAtIndex:currentIndex]];
	}
	
	return hostnames;
}

+ (NSArray *)ipAddresses {
	NSMutableArray *addresses = [NSMutableArray array];
	struct ifaddrs *interfaces = NULL;
	struct ifaddrs *currentAddress = NULL;
	
	int success = getifaddrs(&interfaces);
	if (success == 0) {
		currentAddress = interfaces;
		while(currentAddress != NULL) {
			if(currentAddress->ifa_addr->sa_family == AF_INET) {
				NSString *address = [NSString stringWithUTF8String:inet_ntoa(((struct sockaddr_in *)currentAddress->ifa_addr)->sin_addr)];
				if (![address isEqual:@"127.0.0.1"]) {
					NSLog(@"%@ ip: %@", [NSString stringWithUTF8String:currentAddress->ifa_name], address);
					[addresses addObject:address];
				}
			}
			currentAddress = currentAddress->ifa_next;
		}
	}
	freeifaddrs(interfaces);
	return addresses;
}

+ (NSArray *)ethernetAddresses {
	NSMutableArray *addresses = [NSMutableArray array];
	struct ifaddrs *interfaces = NULL;
	struct ifaddrs *currentAddress = NULL;
	int success = getifaddrs(&interfaces);
	if (success == 0) {
		currentAddress = interfaces;
		while(currentAddress != NULL) {
			if(currentAddress->ifa_addr->sa_family == AF_LINK) {
				NSString *address = [NSString stringWithUTF8String:ether_ntoa((const struct ether_addr *)LLADDR((struct sockaddr_dl *)currentAddress->ifa_addr))];
				
				// ether_ntoa doesn't format the ethernet address with padding.
				char paddedAddress[80];
				int a,b,c,d,e,f;
				sscanf([address UTF8String], "%x:%x:%x:%x:%x:%x", &a, &b, &c, &d, &e, &f);
				sprintf(paddedAddress, "%02X:%02X:%02X:%02X:%02X:%02X",a,b,c,d,e,f);
				address = [NSString stringWithUTF8String:paddedAddress];
				
				if (![address isEqual:@"00:00:00:00:00:00"] && ![address isEqual:@"00:00:00:00:00:FF"]) {
					NSLog(@"%@ mac: %@", [NSString stringWithUTF8String:currentAddress->ifa_name], address);
					[addresses addObject:address];
				}
			}
			currentAddress = currentAddress->ifa_next;
		}
	}
	freeifaddrs(interfaces);
	return addresses;
}

@end

22 Comments

Posted by
luke
8 February 2010 @ 9pm

Yes…. but how to get the local IP of the iPhone?


Posted by
bdunagan
10 February 2010 @ 1am

Good idea! Just updated the post to include code for getting IP address and ethernet MAC address.


Posted by
Chris
8 March 2010 @ 12pm

Thank you very much for this. It’s helped me tremendously.


Posted by
Dan
26 March 2010 @ 10am

Firstly, this is crazy helpful, and the first result in a search that has stretched for days. My question is, does this find the IP and MAC Address of the iPhone itself, or of the local network connection? I’m just wondering because the majority of my searches for “MAC address” and “iPhone” lead me to code which finds the device’s addresses, not the network. Anyway, it may be my inexperience with code like this, but if it does indeed find the MAC address of the local network, this would be a dream come true.


Posted by
bdunagan
29 March 2010 @ 9pm

@Dan: Not sure what you mean about the device’s MAC address versus the local network’s MAC address. Do you mean the local router’s? All networked devices are assigned an ethernet MAC address, and you’ll get one for every interface from the code posted. But the network itself doesn’t have an ethernet MAC address. Hope that helps! :)


Posted by
Smokey
1 April 2010 @ 8am

This is a great time saver for me. I was going to write it from scratch but yours save the day. Thanks a lot!


Posted by
Greg Haygood
6 April 2010 @ 8pm

Rock on! I was previously using NSHost:hostWithName, but Apple notified me with the latest update to one of my apps that I had to stop using that call. Thanks for saving me the time!


Posted by
Christian Winter
12 April 2010 @ 6am

Hey There Good Sample Code! Thanks a lot!

Do you have an idea how i can get the CNAME of a domain?
Like with nslookup -type=CNAME http://www.google.com

I’m trying all day, but i don’t find a solution


Posted by
Ben
14 May 2010 @ 8am

Hate to be so naive, but where is BDHost.h (or BDHost in general)? Thanks


Posted by
Ben
14 May 2010 @ 8am

Actually, I guess if it’s just the method signatures for this code, I can create one…

*** more importantly, does this only work if you have Wi-Fi connection or will it also work on the carrier’s network?


Posted by
Ben
14 May 2010 @ 8am

Ok – last one, I promise…

I got it working, but I was actually looking for a client, not a server so wasn’t I surprised?

In any case, this is a beautiful piece of work – thanks for making it available.


Posted by
bdunagan
17 May 2010 @ 10pm

@Ben Glad you got it figured out! :)


Posted by
bdunagan
17 May 2010 @ 10pm

@Christian Winter Sorry, no idea how to get the CNAME for a domain.


Posted by
David
11 August 2010 @ 3pm

Brilliant tutorial, is there an option to do this for the alternative hostWithAddress? That’d be fantastic.


Posted by
bdunagan
17 August 2010 @ 7pm

@David Great idea! I added a hostnameWithAddress method.


Posted by
Mikywan
23 February 2011 @ 2pm

First of all, thanks for this sample.

I couldn’t make hostnamesForAddress work, when It gets to

BOOL isSuccess = CFHostStartInfoResolution(hostRef, kCFHostNames, NULL);

isSuccess is always false.

Any ideas?


Posted by
bdunagan
24 February 2011 @ 12pm

@Mikywan Hmm, strange. The first thing I’d try is replacing NULL with a CFStreamError object to see what error it returns. The error should shed some light on the problem.


Posted by
Chris
6 May 2011 @ 6am

Hi,

I modified the code to retrieve the IP Address, now it can retrieve IPv6 as well.
Hope it helps : http://wiki.gonzofamily.com/a/Retrieve_local_IPv4/IPv6_on_Wifi/Cellular


Posted by
bdunagan
6 May 2011 @ 11am

@Chris Excellent! Thanks for the link.


Posted by
Chris
8 May 2011 @ 2am

welcome, in fact I also changed addressesForHostname() so that it returns an array of IPv6 and IPv4 addresses, but I still need to improve it.
http://wiki.gonzofamily.com/a/DNS_resolution_with_IPv6_in_Objective_C

Would you know why I have each IP 2 times? I had to add a test to make sur I add them only one time to the Array


Posted by
bdunagan
12 May 2011 @ 8am

@Chris Very nice! Not sure why you get duplicate IPs. Sorry.


Posted by
Greg
31 August 2011 @ 11am

Thanks bdunagan. I was bouncing around the CF and NS libraries trying to find exactly this and it was giving me a headache. This is very concise and to the point. Thank you!

-G


Leave a Comment