8

Trying to use pybonjour but not sure if it is what I need. https://code.google.com/p/pybonjour/

I want to be able to discover iOS devices that appear on my network automatically, will be running a script later on based on this, but first I want to just discover a iOS devices as soon as it appear/disappears on my wifi network.

So the question, how do I do this? running on a windows machine with python27 and the pybonjour package installed, the two examples work from the pybonjour page, but what command do I run to discover iOS devices using the scripts included on my network? or will this only discovery services running on my pc that i run this script on!

If I am going in the wrong direction please let me know, I can't seem to find the documentation on this package!

python browse_and_resolve.py xxxxxx

Thx Matt.

Update...

This article and the browser was helpful, http://marknelson.us/2011/10/25/dns-service-discovery-on-windows/ in finding the services I needed to search for.

example; (this discovered my apple tv's, not at home atm so can't check what the iphone is called! I assume iphone!

python browse_and_resolve.py _appletv._tcp

Also if you have the windows utility dns-sd.exe this will search for all the services available on the network. I used this to find what I was looking for.

dns-sd -B _services._dns-sd._udp

Update...

"Bonjour is used in two ways: - publishing a service - detecting (browsing for) available services".

For what I want to do, I don't think it will work as the ipad/iPhone won't advertise a service unless I'm running a app that advertise one (or jailbreak my iPhone/ipad and then ssh will be open). Any more ideas?

Matt.
  • 1,043
  • 1
  • 12
  • 20
  • First, you did install Bonjour for Windows, right? – abarnert May 10 '13 at 00:52
  • yep. Think I have got it. Tired a few other commands like. python browse_and_resolve.py _airplay._tcp and that discovered my apple tv's – Matt. May 10 '13 at 01:10
  • 1
    Meanwhile, the point of Bonjour is to discover _services_, not _devices_. What would you do with a devices if you discovered it? The answer to that should tell you what service you plan to use, which tells you what service to browse for. For example, if you want to `ssh` to any jailbroken iPhones on your LAN, browse for `_ssh._tcp`. – abarnert May 10 '13 at 01:17
  • 1
    Finally, PyBonjour's documentation is just that page you linked to, because it's just a bunch of thin wrappers around either Apple's Bonjour or a port of it. So, read Apple's [overview](https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/NetServices/Introduction.html), [guide](https://developer.apple.com/library/mac/#documentation/Networking/Conceptual/dns_discovery_api/Introduction.html), and [reference](https://developer.apple.com/library/mac/#documentation/Networking/Reference/DNSServiceDiscovery_CRef/Reference/reference.html). – abarnert May 10 '13 at 01:21
  • I have been playing with it and I should be able to use the script to discover a certain service and instance name appearing on the network and then disappearing. seems to work pretty quickly too. Based on this I can action a custom script. – Matt. May 10 '13 at 01:29
  • Glad it worked out for you. – abarnert May 10 '13 at 01:31
  • mind u not sure if it will discover ipads/iphones, as it doesn't seem to pick them up with this script, as maybe they aren't advertising, will investigate a bit more tonight at home. – Matt. May 10 '13 at 04:09
  • 1
    That was my original point. As far as I know, a (non-jailbroken) iPhone doesn't advertise _any_ services, except when you're running an app that uses Bonjour. – abarnert May 10 '13 at 17:48

3 Answers3

4

What you're trying to do (a) probably can't be done, and (b) probably wouldn't be much use if it could.

The point of Bonjour is to discover services, not devices. Of course each service is provided by some device, so indirectly you can discover devices with it… but only by discovering a service that they're advertising.

As far as I know, (except Apple TVs) don't advertise any services, except while you're running an app that uses Bonjour to find the same app on other machines. (Except for jailbroken devices, which often advertise SSH, AFP, etc.)

There are a few ways to, indirectly, get a list of all services being advertised by anyone on the network. The simplest is probably to use Bonjour Browser for Windows. (I've never actually used it, but the original Mac tool and the Java port, both of which I have used, both suggest this Windows port for Windows users.) Fire it up and you'll get a list of services, and you can click on each one to get the details.

So, you can verify that your iPhone and iPad aren't advertising any services, which will show that there is no way to detect them via Bonjour.

Meanwhile, even if you did find a device, what are you planning to do? Presumably you want to communicate with the device in some way, right? Whatever service you're trying to communicate with… just browse for that service—and then, if appropriate, filter down to iOS devices. That's got to be easier than browsing for iOS devices and then filtering down to those that have the service you want.


As for whether there's any way to detect iOS devices… Well, there are at least two possibilities. I don't know if either of them will work, but…

First, even if the iOS device isn't advertising anything for you, I assume it's browsing for services you can advertise. How else does it find that there's an Apple TV to AirTunes to, an iTunes on the LAN to sync with, etc.?

So, use Bonjour Browser to get a list of all services your iTunes-running desktop, Apple TV, etc. are advertising. Then turn off all the services on your desktop, use PyBonjour to advertise whichever services seem plausibly relevant (and, if need be, use netcat to put trivial listeners on the ports you advertise). Then turn on your iPhone, and see if it connects to any of them. You may want to leave it running for a while, or switch WiFi off and back on. (I'm guessing that, despite Apple's recommendations, it doesn't browse continuously for most services, but just checks every once in a while and/or every time its network status changes. After all, Apple's recommendations are for foreground interactive apps, not background services.)

Unfortunately, even if you can find a service that all iOS devices will connect to, you may not be able to distinguish iOS devices from others just by getting connections there. For example, I'm pretty sure any Mac or Windows box running iTunes will hit up your fake AirTunes service, and any Mac will hit your AirPrint, and so on. So, how do you distinguish that from an iPhone hitting it? You may need to actually serve enough of the protocol to get information out of them. Which will be particularly difficult for Apple's undocumented protocols.

But hopefully you'll get lucky, and there will be something that all iOS devices, and nothing else, will want to talk to. iTunes Sync seems like the obvious possibility.

Alternatively, there are a few things they have to broadcast, or they just wouldn't work. You can't get on a WiFi network without broadcasts. And most home WiFi networks use DHCP, which means they have to broadcast DHCP discover (and request), as well. There may be some kind of heuristic signature you can detect in these messages. If nothing else, enabling DDNS should cause the device to send its hostname, and you can guess based on that (e.g., unless you change the defaults, hostname.lower().endswith('iphone')).

The easiest way is probably to set up your desktop as the main access point for your home network. I believe it's as simple as turning on Internet Connection Sharing somewhere in the control panel. (Setting up as a DHCP relay agent is much less overhead than being a full router, but I have no idea how you'd even get started doing that on Windows.) Then you can capture the DHCP broadcasts (or, failing that, the 802.11 broadcasts) as they come in. Wireshark will capture and parse the messages for you easily, so you can watch and see if it looks like this is worth pursuing farther. (See RFC 2131 for details on the format that aren't obvious from Wireshark's cryptic one-liner descriptions.)

You can take this even farther and watch the internet connections every host makes once they're connected to the internet. Any device that's periodically checking the App Store, the iOS upgrade server, etc.… Well, unless one of the jailbreak devteam guys lives in your house, that's probably an iPhone, right? The downside is that some of these checks may be very periodic, and detecting an iPhone 6 hours after it connects to your network isn't very exciting.

abarnert
  • 354,177
  • 51
  • 601
  • 671
  • Thx, just trying to detect when a person is home or not, using there iPhone. I cam then run scripts based on this on my server. Will have a good look at all the options in the morning. – Matt. May 12 '13 at 13:09
  • yeah, have been thinking that, will see as a last resort. – Matt. May 12 '13 at 23:35
  • Can't jailbreak, running latest OS. – Matt. May 14 '13 at 01:07
3

Use python-nmap rather than Bonjour. Or you could use pyzeroconf (Bonjour is an implementation of zeroconf) but it is a little outdated (but should still work).

python-nmap is probably easiest, let's suppose you wanted to find all connected devices that have 'iPhone' or 'iPad' in their hostname (just a simplistic concept):

import nmap

...

def notify_me(ip, hostname):
  print("I found an iOS device! IP Address: %s, Hostname: %s" % (ip, hostname))

iOS_device_list = ['iPhone', 'iPad']
iOS_devices_on_net = {}
nm = nmap.PortScanner()

# scan ip range
for i in range(2, 50, 1):
  ip = "192.168.1." + str(i)
  # specify ports to scan
  nm.scan(ip, '62078') # Matt mentioned that it picks up iphone-sync on this port
  hostname = nm[ip].hostname()
  for device in iOS_device_list:
    if device.lower() in hostname.lower():
      iOS_devices_on_net.update({ip:hostname})
      notify_me(ip, hostname)

# show all iOS devices in ip range
print iOS_devices_on_net

The limitation of this approach is that it relies on the individual having not changed their hostname which originally includes their name and device name. It also assumes that there is a port listening on the iOS device that will return a hostname (this may not be the case). You can use osscan which is preferred by running it as a command using python-nmap library. This is obviously a much better approach. My concept above is just a simple example of how it can be used.

Using nmap from the command line (I believe python-nmap has nm.commandline() method) is simplest:

nmap -O -v ip

Also try adding --osscan-guess; --fuzzy for best results. Example:

nmap -O -v --osscan-guess ip

Then just search the output for iOS device keywords (see this example). It's human-readable. Note that you'll need to be running all of this as an administrator for it to work properly (Windows: runas, other: sudo).

Dan
  • 4,488
  • 5
  • 48
  • 75
  • I'm pretty sure you need to `scan` each IP before `nm[ip]` will work. And of course you have to scan the right ports for it to guess the hostname, which… I'm not sure what ports you can get that from that an iPhone will have open. – abarnert May 10 '13 at 17:44
  • @abarnert you are correct, I wrote this off the top of my head. Updated response – Dan May 10 '13 at 18:03
  • That solves the first problem. And the extra bit about `-O` is nice (but you may want to add `--osscan-guess` or `--fuzzy` to your `-O` line). But still, do iPhones have any open ports in the 22-443 range? If not, there's nothing for `nmap` to go on (except maybe the timing). (Also, neither the example you give nor the docs it links to show the keywords to look for for iOS… but you can solve that by just running it against an iPhone and seeing the string; it's not exactly hard to parse manually.) – abarnert May 10 '13 at 18:34
  • One more thing: I haven't used `nmap` on Windows for a long time, but IIRC, while on POSIX it just won't let you use `-O` unless you `sudo` it, on Windows, it _will_ run without the right privs (like crafting raw IP packets), it just doesn't do a very good job, so you have to use `runas` or whatever. – abarnert May 10 '13 at 18:38
  • good points, I voted up your answer - I'm thinking you're right that it probably won't return anything on a non-jailbroken iDevice – Dan May 10 '13 at 18:44
  • One more one more thing: The good news is, I know the keywords to search for (`iOS` in OS details, or `iphone_os` in CPE). The bad news is, `nmap` cannot reliably distinguish between iOS and OS X. As of 6.25, it doesn't even seem to try anymore; it just reports them as, e.g., `Apple Mac OS X 10.8 - 10.8.1 (Mountain Lion) (Darwin 12.0.0 - 12.1.0) or iOS 5.0.1` (for a 10.8.3 Mac, a 10.8.2 Mac, and a jailbroken 5.1 iPhone). Still, that might at least give you a list of devices to filter further with some other means. (Any port that most Macs but few iPhones, even jailbroken, listen on?) – abarnert May 10 '13 at 18:46
  • @abarnert I'd have to do testing to see, I just don't have the time currently haha. – Dan May 10 '13 at 19:03
  • Unfortunately, I don't have any non-jailbroken iOS devices to test. (I do have an old iPhone 3G lying around that I could restore… but I no longer have a non-Lightning cable to power it up with.) So, it looks like the OP will have to do further testing himself. – abarnert May 10 '13 at 19:08
  • 1
    nmap picks up port 62078 = iphone-sync – Matt. May 14 '13 at 01:08
  • @Matt.: The phone listens on iphone-sync for connections from a desktop, not the other way around? If so, there's almost certainly a Bonjour service advertised for it, which means you _should_ be using Bonjour. Have you tried the Bonjour Browser test I suggested? – abarnert May 14 '13 at 01:20
  • I have tired a couple of bonjour browser's, none pick up the iphone/ipad. – Matt. May 14 '13 at 03:40
  • 1
    Another option I have thought of using a DNS server on linux box i have or just running a syslog server with the pfSense firewall I am currently running. – Matt. May 14 '13 at 05:13
0

So I have been working on the same issue for about a year now. I got it to work on my mac fairly quickly, but had a lot of trouble getting it to work right on my PC. I have tried many many different approaches. I have a home automation system that turns on the heating and hot water (via an arduino and RF module) when I or my partner are home (that is our iPhones are detectable on the home WiFi). In the end I used 'nslookup' to find the IP address for the iPhones (in case the IP address did change as they are dynamic (but they actually never do on my router)) and 'nmap' to detect if the iPhone is on the network. If the iPhone is in very deep sleep 'nmap' does not always find the phone, so I have made it check 10 times before it says the phone is home. Below is part of my home automation code in python. I have used threading. Any questions with the below code let me know.

# Dictionary to store variables to reuse on program restart
v = {
    'boilerControlCH' : 'HIH', # 'scheduled' or 'HIH' (Honey I'm Home)
    'boilerControlHW' : 'scheduled',
    'thermostatSetPoint' : 20.8,
    'thermostatVariance' : 0.1,
    'morningTime' : datetime(1970,1,1,6,0,0),
    'nightTime' : datetime(1970,1,1,23,0,0),
    'someOneHome' : False,
    'guest' : False,
    'minimumTemperatureOO' : False,
    'minimumTemperature' : 4.0,
    'iPhoneMark' : {'iPhoneHostname' : 'marks-iphone', 'home' : False},
    'iPhoneJessica' : {'iPhoneHostname' :'jessicaesiphone', 'home' : False}
    }

and

# Check if anyone at home
def occupancyStatus(person, Bol = False):
    with lockOccupancyStatus:
        someOneHome = False

        if 'iPhone' in person:
            v[person]['home'] = Bol
        elif 'retest' in person:
            pass
        else:
            v[person] = Bol

        if v['guest'] == True:
            someOneHome = True

        for key in v:
            if 'iPhone' in key:
                if v[key]['home'] == True:
                    someOneHome = True

        v['someOneHome'] = someOneHome
        variablesToFile()
    return

and the main code

# iPhone home status threading code
class nmapClass(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)
    def run(self):
        global exitCounter

        nmapThread()
        msg.log('Exited nmapThread')    
        waitEvent.set()
        waitEventAdjustable.set()
        serialDataWaiting.set()
        exitCounter += 1


def nmapThread():
    iPhone = {}
    maxCounts = 10
    for phone in v:
        if 'iPhone' in phone:
            iPhone[phone] = {}
            iPhone[phone]['hostname'] = v[phone]['iPhoneHostname']
            iPhone[phone]['count'] = maxCounts
    #msg.log(iPhone)

    while exitFlag[0] == 0:
        for phone in iPhone:
            if iPhone[phone]['count'] > 0:
                phoneFound = False
                IPAddress = '0.0.0.0'

                # Find iPhones IP address using its hostname
                commandNsloolup = 'nslookup %s' %iPhone[phone]['hostname']
                childNslookup = pexpect.popen_spawn.PopenSpawn(commandNsloolup, timeout = None)
                output = childNslookup.readline()
                while '\r\n' in output:
                    #msg.log(output)
                    if 'Name:' in output:
                        output = childNslookup.readline()
                        if 'Address:' in output:
                            tempStr = output
                            startPoint = tempStr.find('192')
                            tempStr = tempStr[startPoint:]
                            IPAddress = tempStr.replace('\r\n', '')
                            #msg.log(IPAddress)
                    output = childNslookup.readline()


                if IPAddress == '0.0.0.0':
                    pass
                    #msg.error('Error finding IP address for %s' %iPhone[phone]['hostname'], GFI(CF()).lineno)
                else:
                    #commandNmap = 'nmap -PR -sn %s' %IPAddress
                    #commandNmap = 'nmap -p 62078 -Pn %s' %IPAddress # -p specifies ports to try and access, -Pn removes pinging
                    commandNmap = 'nmap -p 62078 --max-rate 100 %s' %IPAddress
                    childNmap = pexpect.popen_spawn.PopenSpawn(commandNmap, timeout = None)
                    output = childNmap.readline()
                    while '\r\n' in output:
                        if 'Host is up' in output:
                            phoneFound = True
                            break
                        output = childNmap.readline()
                    #if phoneFound:
                    #   break


                if phoneFound:              
                    iPhone[phone]['count'] = 0

                    if v[phone]['home'] == False:
                        msg.log('%s\'s iPhone has returned home' %phone)
                        occupancyStatus(phone, True)
                        waitEventAdjustable.set()
                    #else:
                        #msg.log('%s\'s iPhone still at home' %phone)
                else:
                    iPhone[phone]['count'] -= 1

                    if v[phone]['home'] == True and iPhone[phone]['count'] == 0:
                        msg.log('%s\'s iPhone has left home' %phone)
                        occupancyStatus(phone, False)
                        waitEventAdjustable.set()
                    #else:
                        #msg.log('%s\'s iPhone still away from home' %phone)

            elif iPhone[phone]['count'] < 0:
                msg.error('Error with count variable in iPhone dictionary', GFI(CF()).lineno)


        longWait = True
        for phone in iPhone:
            if iPhone[phone]['count'] > 0:
                longWait = False
                #msg.log('%s: %s' %(phone, iPhone[phone]['count']))

        if longWait:
            #msg.log('wait long')               
            # 600 = run every 10 minutes
            waitEvent.wait(timeout=600)
            for phone in iPhone:
                iPhone[phone]['count'] = maxCounts
        else:
            #msg.log('wait short')
            waitEvent.wait(timeout=60)  

    return

The code may not work if you copy it straight into your own script, as there are some parts missing which I have not copied about to try and keep things simple and easy to read, but hopefully the above code gives everyone a sense of how I did things.