Home > Debugging, Networking, Uncategorized > Troubleshooting Bonjour Networking for the iPhone

Troubleshooting Bonjour Networking for the iPhone

Debugging good code running in a stable environment is hard enough.  Debugging code that responds to outside events, well, that’s even harder.  If you are an iPhone developer working with NSNetService or GKSession, then you already know this.  Despite a glut of excellent documentation on how to write code that uses these powerful classes, there is a dearth of documentation on how to troubleshoot and debug that code.  Here are my notes on lessons that I have learned along the way.  If you have a problem with your code and are stuck, hopefully they will point you in the right direction.

Still with me?  Then let’s dig in!

There are many areas where things can go wrong. Since the NSNetService instance provided to your delegate methods is autoreleased, you need to retain it if you plan on reusing it out of scope for that method. Most people will add it to an NSMutableArray or NSMutableDictionary so that it is automatically retained, and only autoreleased when removed from the collection. If that is the case for your code, make sure that you have properly initialized your collection before adding the object. Since messages to nil are perfectly ok, you may be sending the addObject:netService message to nil.  You will not receive an obvious indication that you never initialized your array or dictionary, and it will appear as though everything is working just fine…except that delegate messages “mysteriously” don’t fire off when peers change status, when you try to connect to one, etc. This crops up often enough in Bonjour troubleshooting that I would recommend it as the first place to start your troubleshooting.  It happens to the best of us. Let’s look at some code that illustrates the problem.

This code might look good first blush, but it will result in the “net service stops sending delegate messages” problem:

//////////////// Header ///////////////////////////////
# pragma mark -
# pragma mark Class Header

#import <UIKit/UIKit.h>

@interface MyNetController : NSObject
{
		NSMutableArray *_peers;
		UITableView *_peerListTableView;
}

/////////////// Property Declarations /////////////////
@property (nonatomic, assign) NSMutableArray *peers;
@property (nonatomic, assign) UITableView *peerListTableView;
@end

/////////////// Body //////////////////////////////////
# pragma mark -
# pragma mark Class Implementation

@implementation MyNetController

@synthesize peers = _peers;
@synthesize peerListTableView = _peerListTableView;

- (id) init
{
	self = [super init];
	if (self != nil)
	{
		self.peers = [NSMutableArray array];
	}
	return self;
}

- (void)netServiceBrowser:(NSNetServiceBrowser *)netServiceBrowser
		   didFindService:(NSNetService *)netService
			   moreComing:(BOOL)moreServicesComing
{
	// Resolve the service to get our peer's name.
	[netService resolve];

	// Add the object to our collection so that
	// it gets retained for future use.
	[_peers addObject:netService];

	NSLog(@"Found a new peer.  Resolve started...");
}

- (void)netServiceDidResolveAddress:(NSNetService *)sender
{
	// Peer will never show up in our table view.
	[_peerListTableView reloadData];

	NSLog(@"This line will never get printed.");
}

@end

If you haven’t spotted it already, the problem lies in line 32.  It assigns an array to the peers property that will fall out of scope as soon as init returns.  As a result, our debug console will show “Found a new peer. Resolve started…” and that’s it.  We will never get a callback to netServiceDidResolveAddress:, since netService was autoreleased after netServiceBrowser:didFindService:moreComing: fell out of scope. We can fix the problem, in this case, by changing the assign symbol to retain on line 14. Now our console output will show “This line will never get printed” and our table will reload as expected.

Some other common issues you might want to watch out for:

  • Make sure that your Application Protocol Name conforms to the RFC specifications:
    “[There may] be no more than fourteen characters (not counting the mandatory underscore), conforming to normal DNS host name rules: Only lower-case letters, digits, and hyphens; must begin and end with lower-case letter or digit.”
  • Use @"" instead of @"local." for the domain when using NSNetService.  From Apple’s Bonjour FAQ:
    “If you pass an empty string (“”), then Mac OS X will automatically do the right thing. In Mac OS X 10.2 and 10.3, it will register using just link-local multicast. Starting in Mac OS X 10.4, it will automatically register your service in a user-chosen unicast DNS domain as well, if applicable. You only need to pass a specific string if you have some particular reason to want to register in some specific remote domain.”
  • Bonjour does not allow for duplicate service names in the same domain. NSNetService can automatically handle this case by appending ” 2″ at the end of the name of the service instance.  However, this will not be the case if you are manually setting the service name.  Make sure that you write an error handler for name collisions in your netService:didNotPublish: method if you are passing something other than nil as the name parameter of initWithDomain:type:name:port.
  • The OS X mDNS responder limits how frequently you can update your TXT record.  If your TXT record update rate exceeds 10 updates per minute, it will begin to introduce progressively longer delays before sending them.  The mDNS responder will print a line similar to the following in the system.log (which you can view in XCode):

    Mon Aug 24 11:03:40 unknown mDNSResponder[16] <Error>: Excessive update rate for tester._my-app._tcp.local.; delaying announcement by 3 seconds.

  • Bonjour/NSNetServiceBrowser on the iPhone/iPod Touch will utilize both Wifi and Bluetooth for service discovery–at least on supported devices. Each time you begin browsing for services, it will search both WiFi and Bluetooth (which you can verify the log messages in the iPhone’s Console, in Organizer). Since your Simulator “device” cannot use Bluetooth, your iPhone discovers it over WiFi. However, if you are using NSNetService or GKSession to publish on your iPhone, then you are publishing over both WiFi *and* Bluetooth as well (if your model supports it and  Bluetooth is enabled). NSNetServiceBrowser and GKSession, when running on BT-capable hardware, will dutifully find that both instances, and report both via delegate callbacks.
  • NSNetService and GKSession will attempt to advertise on all network interfaces that are administratively up. If you see multiple instances of the same peer, and you are using NSNetService, this may be why. To prevent that from happening, stop resolving the service once the first result comes back. In other words, in your - (void)netServiceDidResolveAddress:(NSNetService *)sender method, include [sender stop]. This should prevent your code from getting this delegate message more than once for the same peer.
  • Bluetooth PAN setup takes longer than publishing via Wifi, so the BT-discovered services will sometimes show up well after all the Wifi-based services have been discovered and resolved. When testing two real devices, I’ve even seen both services show up in my UI (usually only after the other phone crashes).  This primarily holds true for NSNetBrowser.  I haven’t come across it yet with GKSession, though I’ve spent less time working with GameKit.  This delay does make for some frustrating coding. If you are not using GameKit, your best bet is to utilize netService:didNotResolve: to either (a) retry resolution (maybe after a timeout), or (b) invalidate the netService instance and wait for the other phone to relaunch their app.
  • If you are using multiple threads, make sure that your NSNetService gets scheduled into an actively running runloop and one that is running in default or common modes.

As an aside, there is an open bug filed with Apple whereby every so often, a normal Bonjour update will not result in a delegate method getting fired. I have only observed this occurring on a 3GS doing the monitoring. This can result in your app slipping out of sync with the network, despite the code doing everything right.  For example, a player may show a custom TXTRecord value of “available” when they actually are “busy.”  You can diagnose this fairly readily.  Trying periodically updating the TXTRecord on a test device. If every so often one peer shows the status as “available” while the rest show show “busy,” especially if device in err is a 3GS, then you probably have encountered it (assuming each one is running the same build).

NSNetServiceBrowser should consistently notify its delegate whenever a service begins/ends advertising on the network (under nominal conditions). The bug above is only an intermittent one, and apparently, hardware specific. If you see it occurring consistently, then it’s likely that your app is the real culprit.  One easily missed problem for apps running network code on a background thread: throwing an un-handled exception on that thread. This can occur without crashing your whole app due to Cocoa/Unix’s rules on threading. If your networking code “just stops working for no reason,” then you may want to check your iPhones Console and logs for error messages. Make sure you have set a breakpoint on the objc_exception_throw symbol.

Here’s another troubleshooting technique that I’ve found invaluable: monitor Bonjour broadcasts on your dev workstation via Terminal using the dns-sd command (see below). This will let you see all comings and goings on your local network for your service. If your app quits, but dns-sd does not show a Remove event, then your code needs revisiting. If dns-sd shows a remove event, but one your other apps instances doesn’t fire a delegate event then you may be seeing the above mentioned bug. It may also be the case that your code isn’t doing what you think it’s doing. And remember, this will only help you troubleshoot Wifi-to-Wifi service Bonjour. Bluetooth-to-Bluetooth (PAN) networking is not supported from iPhone Simulator.

Here is how you use that handy command:

dns-sd -B _serviceName
dns-sd -L Tester _serviceName._tcp.

Where  serviceName is a string that identifies your Bonjour service (like “my-app”).  Don’t omit the initial underscore (e.g. “_my-app”). Replace “Tester” with the display name/service name for a particular instance (e.g. “Player 1″).  The first line shows how to display the activity of all Bonjour peers advertising your service on your network segment.  It will let you know when each peer first appears and last disappears.  It provides a timestamp each time, which can really help in correlating events among multiple peers–perfect for debugging.  If you want to watch the activity of a single peer, including monitoring the status of all TXTRecord key/value pairs, then use syntax of line 2.  This will let you know if code updates to the TXTRecord dictionary actually get written to the network (as it were).

Flaggin’ It Down

If you spend any time using the dns-sd tool, you may wonder what the “Flags” column of its output means. There isn’t much documentation on this, so I had to dig through the source code to find out for sure. So that you don’t have to do the same thing, here’s what they mean (at least, when you use dns-sd for browsing published services):

  • 0 = REMOVE entry message (0).
  • 1 = REMOVE entry message (0) plus MORE TO COME message (1).
  • 2 = ADD entry message (2).
  • 3 = ADD entry message (2) plus MORE TO COME message (1).

You may suspect, at some point, that your network may be causing problems for your application. One technique I would recommend in order to determine that for sure: setup an ad hoc WiFi network between your Mac and your iPhone. This eliminates your wireless access point as a source of error. Just make sure that all other network connections on your Mac and iPhone are disabled first.  For example, unplug any physical Ethernet cables on your Mac, and disable Bluetooth on your iPhone.  That way GKSession will be working with just the WiFi interfaces. This setup has the added advantage of letting you build/test/debug while flying over 10,000 feet!  It also lets you write and test networking code, either Foundation networking or GKSession, using just the Simulator and your device.

Finally, don’t forget to test your code with real-world WiFi networks.  There are a number of factors that may be present in a network that causes trouble for your application. Public hot spots increasingly prevent individual WiFi clients from communicating with each other on same subnet (often referred to as “isolation”).  In addition, some will quarantine a network client until the user clicks through a “Terms of Service” web page. It does no good to develop an app that runs great in a lab environment, but breaks for your customers.  In fact, you may not even make it through the approval process.  If you don’t have a Q/A department with a huge testing lab at your disposal, just visit a few places with free public hot spots (e.g. Starbucks, McDonald’s, most coffee shops, hotels, big airports, etc), connect up with two or three test devices, and fire up your app on each.  This will give you an idea of how your code works in heterogenous environments.

I hope you find something helpful in these notes as you debug your code. This was kind of a brain dump, so I hope some of it made some sense. And please feel free to add your own notes in the comments section.

Happy Coding!

About these ads
  1. Michelle
    01/22/2010 at 8:01 PM

    Thank you for this post! It has helped me to find the source of problems in my program, that is, why my iPhone has connectivity trouble when communicating with a mac running my host application using NSNetServices. The culprit? Bluetooth. When turned off, my programs communicate perfectly.

    Do you know of any way to prevent the use of bluetooth on the iPhone in this situation (disabling it) without making the user actually turn bluetooth off through settings?

    • Zack
      01/22/2010 at 8:29 PM

      I’m glad you found it helpful. I know I’ve wasted way too many hours trying to track down Bonjour phantoms. Bonjour is great, but it can be a real bear to troubleshoot.

      As for your question: unfortunately, Apple does not allow developers to turn on/turn off the Bluetooth hardware (at least with version 2.x and 3.x of the SDK). And they have a good reason for that. If a user is listening to music with a Bluetooth headphones, or making a phone call with a Bluetooth headset, they would probably be upset if you turned BT off.

      When using NSNetService with BT on, it shouldn’t be giving you the kind of trouble you are experiencing. The iPhone will attempt to advertise on via Bluetooth as well as WiFi, but since OS X cannot “see” that attempt, your Mac should remain blissfully ignorant. That said, there are a few places in Foundation networking where it is easy to take a wrong turn inadvertently. I have done it many times. If you provide more information about the specific problem you are having, we should be able to find a way around it.

  2. David Riley
    09/13/2010 at 12:19 PM

    A helpful hint about colliding service names: In 10.5, Apple introduced an option to auto-modify non-nil names in NSNetService. If you do [service publishWithOptions:0] instead of [service publish], it overrides the default (the relevant option is NSNetServiceNoAutoRename).

    That said, of course everyone should be using the netService:DidNotPublish: delegate method for the obvious reasons. But this should make it a little easier if you don’t want to write your own auto-name-mangling code.

    • Zack
      10/27/2010 at 6:37 PM

      Great tip. Thanks for sharing!

  3. vidz
    01/19/2011 at 2:02 AM

    Thanks a lot!It helped me to a great extent. Using Bonjour API’s in my iPhone app, i am able to discover devices. Now, please help me to make BT PAN set-up, so that file SEND/RECEIVE can be achieved.

    • Zack
      01/25/2011 at 10:01 AM

      I’m you found it helpful!

      In iOS, the operating system handles the setup/teardown of the Bluetooth PAN. That’s good news for you, since you don’t have to worry about it.

      If you need more assistance with that to do once you’re successfully discovering Bonjour services, check out Apple’s Cocoa Networking documentation.

      Good luck!

  4. Konrad
    01/24/2011 at 4:56 PM

    This was a great help,

    thanks a lot!

    • Zack
      01/25/2011 at 10:02 AM

      You’re welcome! : )

  5. anjali
    07/22/2011 at 7:58 AM

    Thanks for the post.
    I created an application using the Bonjour API. When it uses bluetooth for discovering the services, the application works perfectly. while using wifi its not working. meaning it does not discover the services. Where the problem is? Help me. Thanks.

  6. 02/01/2012 at 3:56 AM

    Zack,
    I just wish there were more posts like this on programming.
    Real life situations, problems and tips and not just a theory or “hello world” guide that copies the SDK samples.
    (I’ve been mucking with the GK/Bonjour here and there so I can relate). Good work!

    Now for the question ;)
    iOS 5 introduced a change for BT in Bonjour (or P2P interface as they seem to call it sometimes).
    According to the docs, NSNetService will no longer publish on BT using the high level API, but one needs to resort to some dns_sd.h APIs.
    Maybe if you find time you can write a follow up post on the new iOS 5 change and ways to handle it.
    I might have to do some work on it soon, and so far it seems like a tricky path again.

    Thanks again.

    • Zack
      02/01/2012 at 11:10 PM

      dumkycat,

      That’s a disappointment to hear. I thought NSNetServiceBrowser\NSNetService was an elegant approach. I have to admit I haven’t been keeping up with recent iOS changes like I should. I see the changes you’re referring to. Looks like a custom Objective-C wrapper class for the DNS-SD library may be in order here. Maybe this summer I’ll be able to find some time to dig in again. Thanks for the feedback, and good luck!

      • 02/02/2012 at 5:41 AM

        It was and elegant approach, and so was GameKit up to a point (implemented a bit poorly when it came out and missing some power from the lower level APIs like TXT record data you can control.)

        So I’ll just follow up on my findings. I ended up using this code (with a small adaptation to my needs, but it’s great)

        https://github.com/tolo/HHServices

        (Many thanks to Tobias for authoring and sharing this code)

        Some people also mentioned MyNetwork:

        https://bitbucket.org/snej/mynetwork/wiki/Home

        Which is probably good (didn’t verify), but was a bit too much code and dependencies for what I needed right now.

        I hope this helps someone, I know that Stackoverlow is currently low on appropriate answers for this (there are some hints, but Tobias’ code is not mentioned).

      • Zack
        02/02/2012 at 12:10 PM

        Fantastic resources. HHServices looks very nice.

  7. Soon
    02/01/2012 at 10:21 PM

    Hi, Zack. I’m making a research on finding possibilities to make the iPhone as a BT-Headset with Bluetooth DECT Phone base.
    After I studied your article with the WiTap sample project which demonstrates Bonjour via Bluetooth, it seems to be profiled as PAN in default. Do you know how to manipulate Bluetooth profiles in iOS? Thanks.

    • Zack
      02/01/2012 at 11:14 PM

      Unfortunately, iOS doesn’t now allow you to configure Bluetooth profiles. That is handled by iOS itself. If you jailbreak your phone, then you will have more options.

  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: