Time to broadcast, iPhone style

Ran into a need to dynamically determine the current UDP broadcast address for the WiFi interface on the ole’ iPhone. Since NSHost appears to be a private API even w/the 3.0 software, it seems one must go lower. I wrapped it up in a neat little bundle that seems fairly usable if not verbose and full of magic (but understandable) numbers.

A few things of note. en0 is the WiFi interface. There are others. Instrument the following code w/some debug to get them all out. The ip/netmask methods return nil when the WiFi interface is not active. I would also be shocked if there were no corner cases I am ignoring…

#include <arpa/inet.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <errno.h>
#include <ifaddrs.h>
#include <stdio.h>
 
static NSString *kWifiInterface = @"en0";
 
@implementation NetUtil
 
+ (NSString *)broadcastAddressForAddress:(NSString *)ipAddress withMask:(NSString *)netmask {
    NSAssert(nil != ipAddress, @"IP address cannot be nil");
    NSAssert(nil != netmask, @"Netmask cannot be nil");
    NSArray *ipChunks = [ipAddress componentsSeparatedByString:@"."];
    NSAssert([ipChunks count] == 4, @"IP does not have 4 octets!");
    NSArray *nmChunks = [netmask componentsSeparatedByString:@"."];
    NSAssert([nmChunks count] == 4, @"Netmask does not have 4 octets!");
 
    NSUInteger ipRaw = 0;
    NSUInteger nmRaw = 0;
    NSUInteger shift = 24;
    for (NSUInteger i = 0; i < 4; ++i, shift -= 8) {
        ipRaw |= [[ipChunks objectAtIndex:i] intValue] << shift;
        nmRaw |= [[nmChunks objectAtIndex:i] intValue] << shift;
    }
 
    NSUInteger bcRaw = ~nmRaw | ipRaw;
    return [NSString stringWithFormat:@"%d.%d.%d.%d", (bcRaw & 0xFF000000) >> 24,
            (bcRaw & 0x00FF0000) >> 16, (bcRaw & 0x0000FF00) >> 8, bcRaw & 0x000000FF];
}
 
+ (NSString *)ipAddressForInterface:(NSString *)ifName {
    NSAssert(nil != ifName, @"Interface name cannot be nil");
 
    struct ifaddrs *addrs = NULL;
    if (getifaddrs(&addrs)) {
        NSLog(@"Failed to enumerate interfaces: %@", [NSString stringWithCString:strerror(errno)]);
        return nil;
    }
 
    /* walk the linked-list of interfaces until we find the desired one */
    NSString *addr = nil;
    struct ifaddrs *curAddr = addrs;
    while (curAddr != NULL) {
        if (AF_INET == curAddr->ifa_addr->sa_family) {
            NSString *curName = [NSString stringWithCString:curAddr->ifa_name];
            if ([ifName isEqualToString:curName]) {
                char* cstring = inet_ntoa(((struct sockaddr_in *)curAddr->ifa_addr)->sin_addr);
                addr = [NSString stringWithCString:cstring];
                break;
            }
        }
        curAddr = curAddr->ifa_next;
    }
 
    /* clean up, return what we found */
    freeifaddrs(addrs);
    return addr;
}
 
+ (NSString *)ipAddressForWifi {
    return [NetUtil ipAddressForInterface:kWifiInterface];
}
 
+ (NSString *)netmaskForInterface:(NSString *)ifName {
    NSAssert(nil != ifName, @"Interface name cannot be nil");
 
    struct ifreq ifr;
    strncpy(ifr.ifr_name, [ifName UTF8String], IFNAMSIZ-1);
    int fd = socket(AF_INET, SOCK_DGRAM, 0);
    if (-1 == fd) {
        NSLog(@"Failed to open socket to get netmask");
        return nil;
    }
 
    if (-1 == ioctl(fd, SIOCGIFNETMASK, &ifr)) {
        NSLog(@"Failed to read netmask: %@", [NSString stringWithCString:strerror(errno)]);
        close(fd);
        return nil;
    }
 
    close(fd);
    char *cstring = inet_ntoa(((struct sockaddr_in *)&ifr.ifr_addr)->sin_addr);
    return [NSString stringWithCString:cstring];
}
 
+ (NSString *)netmaskForWifi {
    return [NetUtil netmaskForInterface:kWifiInterface];
}
 
@end

6 Comments

  1. I have not tested your code yet, but it appears to solve at least *part* of a problem that I’ve got.

    The other part is, I need to actually send a UDP broadcast message to the WiFi broadcast address, from my iPhone, and listen for a response. I’ve found a bunch of examples, mostly linux-based, but all of them fail on the iPhone. Here’s my code – what fails is listed below it:

    {
    struct sockaddr_in saddr = { 0 };
    struct sockaddr_in raddr = { 0 };

    int sock;
    int on = 1;

    // open socket
    if ((sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1)
    {
    return ( TEL_SOCKET_OPEN_ERROR );
    }

    // enable udp broadcast
    if ( setsockopt ( sock, SOL_SOCKET, SO_BROADCAST, &on, sizeof on ) < 0 )
    {
    close ( sock );
    return ( TEL_SOCKET_OPEN_ERROR );
    }

    // setup source port / destination address

    saddr.sin_len = sizeof ( saddr );
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(4031);
    saddr.sin_addr.s_addr = inet_addr ( "10.255.255.255" ); // htonl(INADDR_BROADCAST);

    // bind outgoing port

    if (bind(sock, (sockaddr *) &saddr, sizeof(saddr)) < 0)
    {
    perror ( "bind" );
    // return ( TEL_SOCKET_OPEN_ERROR );
    }

    raddr.sin_len = sizeof ( raddr );
    raddr.sin_family = AF_INET;
    raddr.sin_port = htons(4031);
    raddr.sin_addr.s_addr = inet_addr ( "10.255.255.255" ); // htonl(INADDR_BROADCAST);

    const char *buf = "skyfi?";
    if (sendto(sock, buf, strlen(buf), 0, (sockaddr *) &raddr, sizeof(raddr)) < 0)
    {
    perror ( "sendto" );
    return ( TEL_SOCKET_WRITE_ERROR );
    }

    sleep ( 1 );

    char reply[256] = { 0 };
    int replen = 0;

    replen = recvfrom ( sock, reply, sizeof ( reply ), 0, NULL, 0 );
    if ( replen < 1 )
    {
    return ( TEL_SOCKET_READ_ERROR );
    }

    return ( TEL_NO_ERROR );
    }

    The above code always fails at the sendto() call, and errno is set to 49 (EADDRNOTAVAIL). perror() gives me sendto: Can't assign requested address. I happen to know that the WiFi network broadcast address is 10.255.255.255 – that's why that value is hard coded. But I still can't send to it. Arrrgh!

    Incidentally, your code (if it works) would solve the other part of my problem, which is finding the broadcast address in the first place.

    Thanks in advance,

    -Tim

  2. Nick says:

    I can’t stress enough staying in ObjC-land for iPhone development. I had to drop to C to get the netmask, etc only because the NSHost API isn’t present in the iPhone SDK.

    And luckily, someone else has written a nice little UDP library for it as well: http://code.google.com/p/cocoaasyncsocket

    Using that, in some of my code (which this post was also written for), sending a UDP broadcast is as simple as:


    // initialization
    socket = [[AsyncUdpSocket alloc] initWithDelegate:self];
    NSError *error = nil;
    if (![socket enableBroadcast:YES error:&error]) {
    NSLog(@"Failed to enable broadcast: '%@'", error);
    }

    and


    // use
    unsigned char bytes[] = {0};
    NSData *data = [NSData dataWithBytes:bytes length:sizeof(bytes)];
    if (![socket sendData:data toHost:broadcastHost port:33848 withTimeout:2.0 tag:DISCOVERY_TAG]) {
    NSLog(@"Failed to send data to broadcast host '%@'", broadcastHost);
    return;
    }

    // casually do other stuff, the UDP is asynchronous
    [socket receiveWithTimeout:10.0 tag:DISCOVERY_TAG];

    Finally, a callback like:


    - (BOOL)onUdpSocket:(AsyncUdpSocket *)sock didReceiveData:(NSData *)data withTag:(long)tag fromHost:(NSString *)host port:(UInt16)port {
    if (DISCOVERY_TAG != tag) {
    return NO;
    }

    // queue up work unit for data received
    // ....
    [[NSNotificationCenter defaultCenter] postNotificationName:@"udp.work.recv" object:self];
    [socket receiveWithTimeout:10.0 tag:DISCOVERY_TAG];
    return YES;
    }

  3. Thanks, Nick. It appears to be working now. After much experimentation, the solution to my original problem was to bind the UDP socket to the WiFi interface address, not to the broadcast address for that interface – and your code for finding the WiFI interface address solved it. Actually, I managed to do it with just the getifaddrs() call you had inside ipAddressForInterface[].

    I’m not sure if I agree with your assessment that it’s always best to work on objective-C. This particular bit of code that I’m working on has to be portable to OS X, Linux, and WIn32, as well as iPhone OS, so keeping it all in C is really the only option. And if it works OK on all 4 platforms … why not?

    Anyhow, thanks again for the help. Greatly appreciated.

  4. Nick says:

    The higher level the better, as long as the frameworks are sound and everything. But yeah, your choice is kind of made for you if you have multi-platform requirements like that!

    Glad I could help.

  5. Oliver says:

    Hi,
    I have a similar problem.

    I cant connect to the address…why?
    Im using the AsyncUdpSocket class.

    int port = 8080;
    NSString *add_tmp = @”192.168.123.100″;
    NSData *add = [add_tmp dataUsingEncoding:NSUTF8StringEncoding];

    if(![listenSocket bindToAddress:addr error:&err]){
    printf(“ERROR”)
    };

    What I make wrong?
    Can anyone help me please?

  6. Nick says:

    Instead of:

    printf(“ERROR”);

    Trying using the error that should have been filled with useful information:

    NSLog(@”Error occurred: ‘%@'”, err);

Leave a Reply

*