Created
March 26, 2018 02:33
-
-
Save KatelynHaworth/d50ce9167fe0377cd20d6585d77a70b4 to your computer and use it in GitHub Desktop.
Network change detection on macOS
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
/* | |
#cgo CFLAGS: -mmacosx-version-min=10.6 -D__MAC_OS_X_VERSION_MAX_ALLOWED=1080 | |
#cgo LDFLAGS: -framework CoreFoundation -framework SystemConfiguration | |
#include <SystemConfiguration/SystemConfiguration.h> | |
*/ | |
import "C" | |
import "unsafe" | |
//export nativeNetworkChangeCallback | |
func nativeNetworkChangeCallback(_ unsafe.Pointer, changedKeys C.CFArrayRef) { | |
callback(changedKeys) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
/* | |
#cgo CFLAGS: -mmacosx-version-min=10.6 -D__MAC_OS_X_VERSION_MAX_ALLOWED=1080 | |
#cgo LDFLAGS: -framework CoreFoundation -framework SystemConfiguration | |
#include <SystemConfiguration/SystemConfiguration.h> | |
extern void nativeNetworkChangeCallback(void * context, CFArrayRef changedKeys); | |
// cfstring_utf8_length returns the number of characters successfully converted to UTF-8 and | |
// the bytes required to store them. | |
static inline CFIndex cfstring_utf8_length(CFStringRef str, CFIndex *need) { | |
CFIndex n, usedBufLen; | |
CFRange rng = CFRangeMake(0, CFStringGetLength(str)); | |
return CFStringGetBytes(str, rng, kCFStringEncodingUTF8, 0, 0, NULL, 0, need); | |
} | |
// MacOS/X Code taken from http://developer.apple.com/technotes/tn/tn1145.html | |
static OSStatus MoreSCErrorBoolean(Boolean success) | |
{ | |
OSStatus err = noErr; | |
if (!success) | |
{ | |
int scErr = SCError(); | |
if (scErr == kSCStatusOK) scErr = kSCStatusFailed; | |
err = scErr; | |
} | |
return err; | |
} | |
static OSStatus MoreSCError(const void *value) {return MoreSCErrorBoolean(value != NULL);} | |
static OSStatus CFQError(CFTypeRef cf) {return (cf == NULL) ? -1 : noErr;} | |
static void CFQRelease(CFTypeRef cf) {if (cf != NULL) CFRelease(cf);} | |
// Create a SCF dynamic store reference and a corresponding CFRunLoop source. If you add the | |
// run loop source to your run loop then the supplied callback function will be called when local IP | |
// address list changes. | |
static OSStatus CreateIPAddressListChangeCallbackSCF(SCDynamicStoreCallBack callback, void *contextPtr, SCDynamicStoreRef *storeRef, CFRunLoopSourceRef *sourceRef) | |
{ | |
OSStatus err; | |
SCDynamicStoreContext context = {0, NULL, NULL, NULL, NULL}; | |
SCDynamicStoreRef ref = NULL; | |
CFStringRef patterns[2] = {NULL, NULL}; | |
CFArrayRef patternList = NULL; | |
CFRunLoopSourceRef rls = NULL; | |
assert(callback != NULL); | |
assert( storeRef != NULL); | |
assert(*storeRef == NULL); | |
assert( sourceRef != NULL); | |
assert(*sourceRef == NULL); | |
// Create a connection to the dynamic store, then create | |
// a search pattern that finds all entities. | |
context.info = contextPtr; | |
ref = SCDynamicStoreCreate(NULL, CFSTR("AddIPAddressListChangeCallbackSCF"), callback, &context); | |
err = MoreSCError(ref); | |
if (err == noErr) | |
{ | |
// This pattern is "State:/Network/Service/[^/]+/IPv4". | |
patterns[0] = SCDynamicStoreKeyCreateNetworkInterfaceEntity(NULL, kSCDynamicStoreDomainState, kSCCompAnyRegex, kSCEntNetIPv4); | |
err = MoreSCError(patterns[0]); | |
if (err == noErr) | |
{ | |
// This pattern is "State:/Network/Service/[^/]+/IPv6". | |
patterns[1] = SCDynamicStoreKeyCreateNetworkInterfaceEntity(NULL, kSCDynamicStoreDomainState, kSCCompAnyRegex, kSCEntNetIPv6); | |
err = MoreSCError(patterns[1]); | |
} | |
} | |
// Create a pattern list containing just one pattern, | |
// then tell SCF that we want to watch changes in keys | |
// that match that pattern list, then create our run loop | |
// source. | |
if (err == noErr) | |
{ | |
patternList = CFArrayCreate(NULL, (const void **) patterns, 2, &kCFTypeArrayCallBacks); | |
err = CFQError(patternList); | |
} | |
if (err == noErr) err = MoreSCErrorBoolean(SCDynamicStoreSetNotificationKeys(ref, NULL, patternList)); | |
if (err == noErr) | |
{ | |
rls = SCDynamicStoreCreateRunLoopSource(NULL, ref, 0); | |
err = MoreSCError(rls); | |
} | |
// Clean up. | |
CFQRelease(patterns[0]); | |
CFQRelease(patterns[1]); | |
CFQRelease(patternList); | |
if (err != noErr) | |
{ | |
CFQRelease(ref); | |
ref = NULL; | |
} | |
*storeRef = ref; | |
*sourceRef = rls; | |
assert( (err == noErr) == (*storeRef != NULL) ); | |
assert( (err == noErr) == (*sourceRef != NULL) ); | |
return err; | |
} | |
void IPConfigChangedCallback(SCDynamicStoreRef store, CFArrayRef changedKeys, void * context) | |
{ | |
nativeNetworkChangeCallback(context, changedKeys); | |
} | |
*/ | |
import "C" | |
import ( | |
"log" | |
"net" | |
"os" | |
"os/signal" | |
"reflect" | |
"runtime" | |
"strings" | |
"unsafe" | |
) | |
var ( | |
running bool | |
storeRef C.SCDynamicStoreRef | |
sourceRef C.CFRunLoopSourceRef | |
) | |
func main() { | |
log.Println("Starting network change detection") | |
returnCode := C.CreateIPAddressListChangeCallbackSCF((*[0]byte)(C.IPConfigChangedCallback), nil, &storeRef, &sourceRef) | |
if returnCode == C.noErr { | |
go nativeLoop() | |
} else { | |
log.Fatalf("failed to setup native hook: error code %#v", returnCode) | |
} | |
interruptHandle := make(chan os.Signal) | |
signal.Notify(interruptHandle, os.Interrupt) | |
<-interruptHandle | |
running = false | |
log.Println("Stopping network change detection") | |
} | |
func nativeLoop() { | |
runtime.LockOSThread() | |
defer runtime.UnlockOSThread() | |
C.CFRunLoopAddSource(C.CFRunLoopGetCurrent(), sourceRef, C.kCFRunLoopDefaultMode) | |
running = true | |
for running { | |
C.CFRunLoopRun() | |
} | |
C.CFRunLoopRemoveSource(C.CFRunLoopGetCurrent(), sourceRef, C.kCFRunLoopDefaultMode) | |
C.CFRelease((C.CFTypeRef)(storeRef)) | |
C.CFRelease((C.CFTypeRef)(sourceRef)) | |
} | |
func callback(changedKeys C.CFArrayRef) { | |
if (int)(C.CFArrayGetCount(changedKeys)) == 0 { | |
return | |
} | |
changeDetails := cfstringGo((C.CFStringRef)( | |
C.CFArrayGetValueAtIndex(changedKeys, (C.CFIndex)(0)), | |
)) | |
ipInterfaceName := strings.Split(changeDetails, "/")[3] | |
ipInterface, _ := net.InterfaceByName(ipInterfaceName) | |
log.Printf("Interface changed state: %#v", ipInterface) | |
} | |
// cfstring will convert a OSX | |
// CFStringRef string into a Golang | |
// compatible string | |
func cfstringGo(cfs C.CFStringRef) string { | |
var usedBufLen C.CFIndex | |
n := C.cfstring_utf8_length(cfs, &usedBufLen) | |
if n <= 0 { | |
return "" | |
} | |
rng := C.CFRange{location: C.CFIndex(0), length: n} | |
buf := make([]byte, int(usedBufLen)) | |
bufp := unsafe.Pointer(&buf[0]) | |
C.CFStringGetBytes(cfs, rng, C.kCFStringEncodingUTF8, 0, 0, (*C.UInt8)(bufp), C.CFIndex(len(buf)), &usedBufLen) | |
sh := &reflect.StringHeader{ | |
Data: uintptr(bufp), | |
Len: int(usedBufLen), | |
} | |
return *(*string)(unsafe.Pointer(sh)) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hey, does this code have a license? I'm using it to fix an issue with
pf
and would like to be able to make the hack open-source.