Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to detect IP address change on OSX programmatically in C or C++

Tags:

c++

c

macos

I have to be able to detect an IP address change for my Mac client. I need to perform an action every time I get a new one, when I go from wifi to wired ...

Anyone has done something similar? I currently poll every minute and I need to change that to be more event driven.

like image 484
reza Avatar asked Jul 17 '12 23:07

reza


1 Answers

There are multiple ways to do this, from IOKit notifications on up, but the simplest is probably the SystemConfiguration framework.

The first step is to fire up scutil and play with it to figure out which key(s) you want notification on:

$ scutil
> list
...
> n.add State:/Network/Global/IPv4
> n.watch
... unplug your network cable (or disconnect from WiFi)
notification callback (store address = 0x10e80e3c0).
changed key [0] = State:/Network/Global/IPv4

Look at that, got it on first try. :) But if you want to watch a particular NIC, or use IPv6 instead of v4, etc., obviously you'll want a different key from the list. Note that you can use regex patterns (POSIX style, as defined by man 3 regex), so if you want to watch, say, any NIC for IPv4, you can use State:/Network/Interface/.*/IPv4, or if you want to say global IPv4 or IPv6, State:/Network/Global/IPv., etc.

Now you just call SCDynamicStoreSetNotificationKeys with the keys you want.

Note that SCDynamicStoreSetNotificationKeys can take regex patterns (POSIX style, as defined by man 3 regex)

Since it's a bit painful in C, I'll write it in Python:

#!/usr/bin/python

from Foundation import *
from SystemConfiguration import *

def callback(store, keys, info):
  for key in keys:
    print key, SCDynamicStoreCopyValue(store, key)

store = SCDynamicStoreCreate(None, 
                             "global-network-watcher",
                             callback,
                             None)
SCDynamicStoreSetNotificationKeys(store,
                                  None,
                                  ['State:/Network/Global/IPv4'])
CFRunLoopAddSource(CFRunLoopGetCurrent(),
                   SCDynamicStoreCreateRunLoopSource(None, store, 0),
                   kCFRunLoopCommonModes)
CFRunLoopRun()

The main reason this is more painful in C is that you need dozens of lines of boilerplate for things like creating an CFArray with a CFString in it, printing CFString values, managing the lifetimes of the objects, etc. From Jeremy Friesner's comment, there's C++ sample code available if you'd rather read 113 lines of C++ than 17 lines of Python. But really, there's only one line here that should be unfamiliar to someone who's never used Python:

def callback(store, keys, info):
  for key in keys:
    print key, SCDynamicStoreCopyValue(store, key)

… is the equivalent of the C definition:

void callback(SCDynamicStoreRef store, CFArrayRef keys, void *info) {
  /* iterate over keys, printing something for each one */
}

Oddly, I can't find the actual reference or guide documentation on SystemConfiguration anymore; the only thing that comes up for SCDynamicStoreSetNotificationKeys or related functions is in the Navigating Firewalls section of CFNetwork Programming Guide. But the original technote TN1145: Living in a Dynamic TCP/IP Environment still exists, and it's got enough background and sample code to figure out how write this yourself (and how to detect the new IP address(es) when you get notified).

Obviously this requires you to know what exactly you're trying to watch for. If you don't know that, nobody can tell you how to watch for it. Your original question was how to "detect an IP address change".

What the code above will do is detect when your default address changes. That's the address that gets used when you connect a socket to an internet address without binding it, or bind a socket to '0.0.0.0' to act as an internet server. If you haven't written the server code you care about, nearly all network clients do the former, and most servers do the latter unless you configure them otherwise, so that's probably all you care about.

Now let's go through the examples in your comments one by one:

if i am trying to determine a network change, wifi to LAN

There is no such thing as changing from WiFi to LAN. When you connect to a LAN, the WiFi is still working. Of course you can manually disable it before or after connecting to the LAN, but you don't have to, and it's a separate step, with a separate notification.

Normally, adding a LAN will change your default address to the LAN's address, so /Network/Global will notify you. If the OS can tell the LAN is not actually connected to the internet, or you've changed some hidden settings to make it prefer WiFi to LAN, etc., it won't change the default address, and /Network/Global will not notify you, but you probably don't care.

If you do care about whether a particular interface gets, loses, or changes an address, you can watch that interface. On most Macs, the built-in Ethernet is en0, and the built-in WiFi is en1, but of course you may have a third-party USB WiFi connector, or you may be using a tethered cell phone, or you may be interested not so much in the actual IP address of the LAN as in the VPN address of the VPN the LAN is connected to, etc. If you're writing something special purpose, you probably know which interface you care about, so you can watch, e.g., State:/Network/Interface/en0/IPv4. If you want to be notified on any interface changing no matter what, just watch State:/Network/Interface/.*/IPv4.

or to hotspot or another wifi

Changing from one WiFi network to another (hotspot or otherwise) will change en1—or, if you're using a third-party WiFi adapter, some other interface. If your default address at the time comes from WiFi, it will also change Global, which means the code above will work as-is. If your default address is still your LAN, Global won't change, but you probably don't care. If you do care, watch Interface/en1 or Interface/.*, etc., as above.

what all network settings should I be watching for IPV4 and 6

Just replace IPv4 with IPv6, or use IPv.. But do you really care about IPv6?

what else

What else do you care about? If there's something you actually want notification of, you presumably know what that something is.

Beyond that, if the system tells you that the foo address on the bar interface has changed to "ZZ9 Plural Z Alpha", and you've never heard of the foo protocol, what could you usefully do with that information? But if you really want it anyway, again, you can just use a regex pattern to watch for anything under each interface.

like image 78
abarnert Avatar answered Oct 20 '22 04:10

abarnert