objective-cdnsservice-discoverydns-sdsrv

Resolving SRV records with iOS SDK


I want to resolve DNS SRV records using the iOS SDK.

I've already tried the high-level Bonjour APIs Apple is providing, but they're not what I need. Now I'm using DNS SD.

void *processQueryForSRVRecord(void *record) {
    DNSServiceRef sdRef;
    int context;
    printf("Setting up query for record: %s\n", record);
    DNSServiceQueryRecord(&sdRef, 0, 0, record, kDNSServiceType_SRV, kDNSServiceClass_IN, callback, &context);

    printf("Processing query for record: %s\n", record);
    DNSServiceProcessResult(sdRef);

    printf("Deallocating query for record: %s\n", record);
    DNSServiceRefDeallocate(sdRef);

    return NULL;
}

This works as long as it gets only correct SRV records (for example: _xmpp-server._tcp.gmail.com), but when the record is typed wrong, DNSServiceProcessResult(sdRef) goes into an infinite loop.

Is there a way to stop DNSServiceProcessResult or must I cancel the thread calling it?


Solution

  • Use good old select(). This is what I have at the moment:

    - (void)updateDnsRecords
    {
        if (self.dnsUpdatePending == YES)
        {
            return;
        }
        else
        {
            self.dnsUpdatePending = YES;
        }
    
        NSLog(@"DNS update");
        DNSServiceRef       sdRef;
        DNSServiceErrorType err;
    
        const char* host = [self.dnsHost UTF8String];
        if (host != NULL)
        {
            NSTimeInterval remainingTime = self.dnsUpdateTimeout;
            NSDate*        startTime = [NSDate date];
    
            err = DNSServiceQueryRecord(&sdRef, 0, 0,
                                        host,
                                        kDNSServiceType_SRV,
                                        kDNSServiceClass_IN,
                                        processDnsReply,
                                        &remainingTime);
    
            // This is necessary so we don't hang forever if there are no results
            int            dns_sd_fd = DNSServiceRefSockFD(sdRef);
            int            nfds      = dns_sd_fd + 1;
            fd_set         readfds;
            int            result;
    
            while (remainingTime > 0)
            {
                FD_ZERO(&readfds);
                FD_SET(dns_sd_fd, &readfds);
    
                struct timeval tv;
                tv.tv_sec  = (time_t)remainingTime;
                tv.tv_usec = (remainingTime - tv.tv_sec) * 1000000;
    
                result = select(nfds, &readfds, (fd_set*)NULL, (fd_set*)NULL, &tv);
                if (result == 1)
                {
                    if (FD_ISSET(dns_sd_fd, &readfds))
                    {
                        err = DNSServiceProcessResult(sdRef);
                        if (err != kDNSServiceErr_NoError)
                        {
                            NSLog(@"There was an error reading the DNS SRV records.");
                            break;
                        }
                    }
                }
                else if (result == 0)
                {
                    NBLog(@"DNS SRV select() timed out");
                    break;
                }
                else
                {
                    if (errno == EINTR)
                    {
                        NBLog(@"DNS SRV select() interrupted, retry.");
                    }
                    else
                    {
                        NBLog(@"DNS SRV select() returned %d errno %d %s.", result, errno, strerror(errno));
                        break;
                    }
                }
    
                NSTimeInterval elapsed = [[NSDate date] timeIntervalSinceDate:startTime];
                remainingTime -= elapsed;
            }
    
            DNSServiceRefDeallocate(sdRef);
        }
    }
    
    
    static void processDnsReply(DNSServiceRef       sdRef,
                                DNSServiceFlags     flags,
                                uint32_t            interfaceIndex,
                                DNSServiceErrorType errorCode,
                                const char*         fullname,
                                uint16_t            rrtype,
                                uint16_t            rrclass,
                                uint16_t            rdlen,
                                const void*         rdata,
                                uint32_t            ttl,
                                void*               context)
    {
        NSTimeInterval* remainingTime = (NSTimeInterval*)context;
    
        //  If a timeout occurs the value of the errorCode argument will be
        //  kDNSServiceErr_Timeout.
        if (errorCode != kDNSServiceErr_NoError)
        {
            return;
        }
    
        //  The flags argument will have the kDNSServiceFlagsAdd bit set if the
        //  callback is being invoked when a record is received in response to
        //  the query.
        //
        //  If kDNSServiceFlagsAdd bit is clear then callback is being invoked
        //  because the record has expired, in which case the ttl argument will
        //  be 0.
        if ((flags & kDNSServiceFlagsMoreComing) == 0)
        {
            *remainingTime = 0;
        }
    
        // Record parsing code below was copied from Apple SRVResolver sample.
        NSMutableData *         rrData = [NSMutableData data];
        dns_resource_record_t * rr;
        uint8_t                 u8;
        uint16_t                u16;
        uint32_t                u32;
    
        u8 = 0;
        [rrData appendBytes:&u8 length:sizeof(u8)];
        u16 = htons(kDNSServiceType_SRV);
        [rrData appendBytes:&u16 length:sizeof(u16)];
        u16 = htons(kDNSServiceClass_IN);
        [rrData appendBytes:&u16 length:sizeof(u16)];
        u32 = htonl(666);
        [rrData appendBytes:&u32 length:sizeof(u32)];
        u16 = htons(rdlen);
        [rrData appendBytes:&u16 length:sizeof(u16)];
        [rrData appendBytes:rdata length:rdlen];
    
        rr = dns_parse_resource_record([rrData bytes], (uint32_t) [rrData length]);
    
        // If the parse is successful, add the results.
        if (rr != NULL)
        {
            NSString *target;
    
            target = [NSString stringWithCString:rr->data.SRV->target encoding:NSASCIIStringEncoding];
            if (target != nil)
            {
                uint16_t priority = rr->data.SRV->priority;
                uint16_t weight   = rr->data.SRV->weight;
                uint16_t port     = rr->data.SRV->port;
    
                [[FailoverWebInterface sharedInterface] addDnsServer:target priority:priority weight:weight port:port ttl:ttl];  // You'll have to do this in with your own method.
            }
        }
    
        dns_free_resource_record(rr);
    }
    

    Here's the Apple SRVResolver sample from which I got the RR parsing.

    This Apple sample mentions that it may block forever, but strange enough suggest to use NSTimer when trying to add a timeout yourself. But I think using select() is a much better way.

    I have one to-do: Implement flushing cache with DNSServiceReconfirmRecord. But won't do that now.

    Be aware, this code is working, but I'm still testing it.

    You need to add libresolv.dylib to your Xcode project's 'Linked Frameworks and Libraries'.