GorillaPatch
GorillaPatch

Reputation: 5057

How to detect IPv4 address change on macOS using SystemConfiguration framework

I am trying to use the SystemConfiguration on mac OS to get a notification when a new network interface appears on the mac and a new IP address is assigned for it.

I set it up to watch for the system configuration key State:/Network/Interface and it works that I get a notification whenever a new network interface appears or disappears.

However I would like to get a notification whenever the IPv4 address is assigned on the new network interface (e.g. by DHCP). I know that the key State:/Network/Interface/en0/IPv4 is holding the IPv4 address for the en0 interface. But using regular expressions as depicted in the man page for all IPv4 addresses State:/Network/Interface/.*/IPv4 does not work for the new interface.

I have put together a small minimal code example on github, however one can also use the scutil command line tool.

Link to demo repository

main.c

#import <Foundation/Foundation.h>
#import <SystemConfiguration/SystemConfiguration.h>

/* Callback used if a configuration change on monitored keys was detected.
 */
void dynamicStoreCallback(SCDynamicStoreRef store, CFArrayRef changedKeys, void* __nullable info) {
    CFIndex count = CFArrayGetCount(changedKeys);
    for (CFIndex i=0; i<count; i++) {
        NSLog(@"Key \"%@\" was changed", CFArrayGetValueAtIndex(changedKeys, i));
    }
}

int main(int argc, const char * argv[]) {
    NSArray *SCMonitoringInterfaceKeys = @[@"State:/Network/Interface.*"];
    @autoreleasepool {
        SCDynamicStoreRef dsr = SCDynamicStoreCreate(NULL, CFSTR("network_interface_detector"), &dynamicStoreCallback, NULL);
        SCDynamicStoreSetNotificationKeys(dsr, CFBridgingRetain(SCMonitoringInterfaceKeys), NULL);
        CFRunLoopAddSource(CFRunLoopGetCurrent(), SCDynamicStoreCreateRunLoopSource(NULL, dsr, 0), kCFRunLoopDefaultMode);
        NSLog(@"Starting RunLoop...");
        while([[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]);
    }
    return 0;
}

Upvotes: 1

Views: 638

Answers (1)

GorillaPatch
GorillaPatch

Reputation: 5057

With the help of some developer colleagues I found out what went wrong. The signature for the SCDynamicStoreSetNotificationKeys function is as follows:

Boolean SCDynamicStoreSetNotificationKeys (SCDynamicStoreRef store,
                CFArrayRef          __nullable  keys,
                CFArrayRef          __nullable  patterns
                )

Meaning that I have to set the pattern separately from the keys which act as the root of the tree under which the pattern matching will occur. Here is the modified version of my main.m:

int main(int argc, const char * argv[]) {
    NSArray *SCMonitoringInterfaceKeys = @[@"State:/Network/Interface"];
    NSArray *patterns = @[@"en\\d*/IPv4"];
    @autoreleasepool {
        SCDynamicStoreRef dsr = SCDynamicStoreCreate(NULL, CFSTR("network_interface_detector"), &dynamicStoreCallback, NULL);
        SCDynamicStoreSetNotificationKeys(dsr, CFBridgingRetain(SCMonitoringInterfaceKeys), CFBridgingRetain(patterns));
        CFRunLoopAddSource(CFRunLoopGetCurrent(), SCDynamicStoreCreateRunLoopSource(NULL, dsr, 0), kCFRunLoopDefaultMode);
        NSLog(@"Starting RunLoop...");
        while([[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]);
    }
    return 0;
}

I have included the solution into the solved branch of the repo.

Upvotes: 2

Related Questions