Search code examples
objective-cmacosip-addressipv6

Get All of Mac's IP Addresses Using Objective-C


I'm trying to get all of my Mac's IP addresses (IPv4 + IPv6) but am not seeing the results I expect. I'm using code from this Stack Overflow post to get an array of all IP addresses, but there's an IP address missing.

I'm using another Mac to share it's internet connection and create a NAT64 network per Apple's documentation for testing/supporting IPv6 networks.

For example here's what the System Preferences → Network pane says my IP address is:

enter image description here

... but I see that I actually have two IPv6 addresses upon further inspection:

enter image description here

... but only one of these is returned:

"169.254.38.213",
"2001:2:0:aab1:d0ef:646d:f22a:5d83",
"127.0.0.1"

... when using this:

#include <ifaddrs.h>
#include <arpa/inet.h>
#include <net/if.h>

@interface AppDelegate ()

#define IP_ADDR_IPv4    @"ipv4"
#define IP_ADDR_IPv6    @"ipv6"

@end

@implementation AppDelegate

- (void) applicationDidFinishLaunching: (NSNotification *) aNotification
{
    NSMutableDictionary *addresses = [NSMutableDictionary dictionaryWithCapacity:8];
    NSMutableArray *ipAddressesArray = [[NSMutableArray alloc] init];

    // retrieve the current interfaces - returns 0 on success
    struct ifaddrs *interfaces;

    if (!getifaddrs(&interfaces))
    {
        // Loop through linked list of interfaces
        struct ifaddrs *interface;

        for (interface=interfaces; interface; interface=interface->ifa_next)
        {
            if (!(interface->ifa_flags & IFF_UP) /* || (interface->ifa_flags & IFF_LOOPBACK) */)
            {
                continue; // deeply nested code harder to read
            }

            const struct sockaddr_in *addr = (const struct sockaddr_in*)interface->ifa_addr;
            char addrBuf[ MAX(INET_ADDRSTRLEN, INET6_ADDRSTRLEN) ];

            if (addr && (addr->sin_family==AF_INET || addr->sin_family==AF_INET6))
            {
                NSString *name = [NSString stringWithUTF8String:interface->ifa_name];
                NSString *type;

                if (addr->sin_family == AF_INET)
                {
                    if (inet_ntop(AF_INET, &addr->sin_addr, addrBuf, INET_ADDRSTRLEN))
                    {
                        type = IP_ADDR_IPv4;
                    }
                }
                else
                {
                    const struct sockaddr_in6 *addr6 = (const struct sockaddr_in6*)interface->ifa_addr;

                    if (inet_ntop(AF_INET6, &addr6->sin6_addr, addrBuf, INET6_ADDRSTRLEN))
                    {
                        type = IP_ADDR_IPv6;
                    }
                }

                if (type)
                {
                    NSString *key = [NSString stringWithFormat:@"%@/%@", name, type];
                    addresses[key] = [NSString stringWithUTF8String:addrBuf];
                }
            }
        }

        for (id key in addresses)
        {
            if (![[addresses valueForKey:key] hasPrefix:@"fe80"])
            {
                [ipAddressesArray addObject:[addresses valueForKey:key]];
            }
        }

        // Free memory
        freeifaddrs(interfaces);
    }

    NSLog(@"%@", ipAddressesArray);
}

Any idea what is going on here? Does it matter? I'm trying to execute some other code conditionally based on IP address matching. It'd be one thing if the only IPv6 address returned was the one displayed to the user in System Preferences when first opening the Network pane, but instead the only IPv6 address returned is the "hidden" one that you have to dig into the Advanced... section to find. Thank you in advance.


Solution

  • The answer here is that I am an idiot. As suggested by Rob Napier in their comment on my question, I was essentially filtering out the missing IP addresses:

    NSString *key = [NSString stringWithFormat:@"%@/%@", name, type];
    addresses[key] = [NSString stringWithUTF8String:addrBuf];
    

    Each interface can have more than one IP address, but I since I was using the interface type as the unique key in my addresses dictionary, only one IP address per interface was present in the dictionary. I fixed this by returning an array of dictionaries rather than a single dictionary:

    if (![[NSString stringWithFormat:@"%s", addrBuf] hasPrefix:@"fe80"])
    {
          NSDictionary *addressDict = [NSDictionary dictionaryWithObjectsAndKeys :
                                                       [NSString stringWithFormat:@"%@/%@", name, type], @"Interface",
                                                       [NSString stringWithFormat:@"%s", addrBuf], @"Address",
                                                        nil];
    
         [addresses addObject:addressDict];
    }
    
    return addresses;