iPhone: Bonjour NSNetService IP address and port
Asked Answered
A

3

17

Excuse my iPhone/Objective-C newbie status please!

I've found my HTTP server using NSNetServiceBrowser, but now I just want the IP address and port of the service found.

I've got something like the following in my delegate method:

NSNetService* server = [serverBrowser.servers objectAtIndex:0];

NSString            *name = nil;
NSData              *address = nil;
struct sockaddr_in  *socketAddress = nil;
NSString            *ipString = nil;
int                 port;
uint                 i;
for (i = 0; i < [[server addresses] count]; i++)
{
    name = [server name];
    address = [[server addresses] objectAtIndex:i];
    socketAddress = (struct sockaddr_in *)
    [address bytes];
    ipString = [NSString stringWithFormat: @"%s",
                inet_ntoa (socketAddress->sin_addr)];
    port = socketAddress->sin_port;
    NSLog(@"Server found is %s %d",ipString,port);
}

but the for loop is never entered, even though the delegate is called. Any ideas? Thanks!

Aphonic answered 2/6, 2009 at 8:37 Comment(1)
As a side comment, the for loop you have there is considered bad form (and is slow). You should use the enhanced for loop: for (NSData *addressData in [server addressses]) { …Barcroft
E
45

I realize this is an old thread, but I've just run across this as well. There are a few problems with the code above:

  1. It's not IPv6 savvy. At a minimum, it should detect and discard IPv6 addresses if the rest of your app can only handle v4 addresses, but ideally you should be prepared to pass both address families upstream.

  2. The port assignment will generate incorrect values for Intel processors. You need to use htons to fix that.

  3. As Andrew noted above, the iteration should use the enhanced for loop.

  4. (EDIT: Added this) As noted on another related thread, the use of inet_ntoa is discouraged in favor of inet_ntop.

Putting all of this together, you get:

char addressBuffer[INET6_ADDRSTRLEN];

for (NSData *data in self.addresses)
{
    memset(addressBuffer, 0, INET6_ADDRSTRLEN);

    typedef union {
        struct sockaddr sa;
        struct sockaddr_in ipv4;
        struct sockaddr_in6 ipv6;
    } ip_socket_address;

    ip_socket_address *socketAddress = (ip_socket_address *)[data bytes];

    if (socketAddress && (socketAddress->sa.sa_family == AF_INET || socketAddress->sa.sa_family == AF_INET6))
    {
        const char *addressStr = inet_ntop(
                socketAddress->sa.sa_family,
                (socketAddress->sa.sa_family == AF_INET ? (void *)&(socketAddress->ipv4.sin_addr) : (void *)&(socketAddress->ipv6.sin6_addr)),
                addressBuffer,
                sizeof(addressBuffer));

        int port = ntohs(socketAddress->sa.sa_family == AF_INET ? socketAddress->ipv4.sin_port : socketAddress->ipv6.sin6_port);

        if (addressStr && port)
        {
            NSLog(@"Found service at %s:%d", addressStr, port);
        }
    }
}
Ergosterol answered 12/2, 2011 at 7:1 Comment(5)
Just like to add that I had to #include <arpa/inet.h> :)Chloras
Instead of 100 you could use one of the constants INET6_ADDRSTRLEN or INET_ADDRSTRLEN.Headliner
This code is not correct for IPv6 addresses. You are using the IPv4 sockaddr_in structure. Which is consistent with IPv6 for the first 2 structure members but not after that.Belmonte
I've subsequently updated the code above to handle ipv6 addresses correctly.Belmonte
Ridiculously helpful, thank you! I like the code so much I did a remix in a category below.Heterochromatic
B
18

The NSNetService you get back in the callback isn't ready to be used. You have to call the following method to get addresses for it:

- (void)resolveWithTimeout:(NSTimeInterval)timeout;

Implement the NSNetService delegate method to find out when it resolves:

- (void)netServiceDidResolveAddress:(NSNetService *)sender;

At that point, there should be at least one address in the service.

Also, take care to read the documentation and the header file carefully! There is some complexity to the issue here that I've glossed over.

Barcroft answered 2/6, 2009 at 8:48 Comment(0)
H
3

Remix of the accepted answer in a category:

NSNetService+Util.h

#import <Foundation/Foundation.h>

@interface NSNetService (Util)

- (NSArray*) addressesAndPorts;

@end


@interface AddressAndPort : NSObject 

@property (nonatomic, assign) int port;
@property (nonatomic, strong)  NSString *address;

@end

NSNetService+Util.m

#import "NSNetService+Util.h"
#include <arpa/inet.h>

@implementation NSNetService (Util)

- (NSArray*) addressesAndPorts {

    // this came from https://mcmap.net/q/686309/-iphone-bonjour-nsnetservice-ip-address-and-port
    NSMutableArray *retVal = [NSMutableArray array];
    char addressBuffer[INET6_ADDRSTRLEN];

    for (NSData *data in self.addresses)
    {
        memset(addressBuffer, 0, INET6_ADDRSTRLEN);

        typedef union {
            struct sockaddr sa;
            struct sockaddr_in ipv4;
            struct sockaddr_in6 ipv6;
        } ip_socket_address;

        ip_socket_address *socketAddress = (ip_socket_address *)[data bytes];

        if (socketAddress && (socketAddress->sa.sa_family == AF_INET || socketAddress->sa.sa_family == AF_INET6))
        {
            const char *addressStr = inet_ntop(
                                               socketAddress->sa.sa_family,
                                               (socketAddress->sa.sa_family == AF_INET ? (void *)&(socketAddress->ipv4.sin_addr) : (void *)&(socketAddress->ipv6.sin6_addr)),
                                               addressBuffer,
                                               sizeof(addressBuffer));

            int port = ntohs(socketAddress->sa.sa_family == AF_INET ? socketAddress->ipv4.sin_port : socketAddress->ipv6.sin6_port);

            if (addressStr && port)
            {
                AddressAndPort *aAndP = [[AddressAndPort alloc] init];
                aAndP.address = [NSString stringWithCString:addressStr encoding:kCFStringEncodingUTF8];
                aAndP.port = port;
                [retVal addObject:aAndP];
            }
        }
    }
    return retVal;

}


@end


@implementation AddressAndPort
@end

[Yes, I have no fear of creating lots of NSObject instances...]

Heterochromatic answered 19/9, 2016 at 16:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.