#include <stdio.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <net/if.h>
#include <net/if_media.h>
#include <SystemConfiguration/SystemConfiguration.h>
#include <SystemConfiguration/SCPrivate.h>
#include <SystemConfiguration/SCValidation.h>
#include <SystemConfiguration/LinkConfiguration.h>
#include <SystemConfiguration/SCDPlugin.h> // for _SCDPluginExecCommand
static CFMutableDictionaryRef baseSettings = NULL;
static CFStringRef interfacesKey = NULL;
static SCDynamicStoreRef store = NULL;
static CFRunLoopSourceRef rls = NULL;
static CFMutableDictionaryRef wantSettings = NULL;
static Boolean _verbose = FALSE;
int
__createMediaOptions(CFStringRef interfaceName, CFDictionaryRef media_options);
static CFDictionaryRef
__copyMediaOptions(CFDictionaryRef options)
{
CFMutableDictionaryRef requested = NULL;
CFTypeRef val;
if (!isA_CFDictionary(options)) {
return NULL;
}
val = CFDictionaryGetValue(options, kSCPropNetEthernetMediaSubType);
if (isA_CFString(val)) {
requested = CFDictionaryCreateMutable(NULL,
0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
CFDictionaryAddValue(requested, kSCPropNetEthernetMediaSubType, val);
} else {
;
return NULL;
}
val = CFDictionaryGetValue(options, kSCPropNetEthernetMediaOptions);
if (isA_CFArray(val)) {
CFDictionaryAddValue(requested, kSCPropNetEthernetMediaOptions, val);
} else {
;
CFRelease(requested);
return NULL;
}
return requested;
}
__private_extern__
Boolean
_SCNetworkInterfaceSetMediaOptions(SCNetworkInterfaceRef interface,
CFDictionaryRef options)
{
CFArrayRef available = NULL;
CFDictionaryRef current = NULL;
struct ifmediareq ifm;
struct ifreq ifr;
CFStringRef interfaceName;
Boolean ok = FALSE;
int newOptions;
CFDictionaryRef requested;
int sock = -1;
interfaceName = SCNetworkInterfaceGetBSDName(interface);
if (interfaceName == NULL) {
SCLog(_verbose, LOG_INFO, CFSTR("no BSD interface name for %@"), interface);
return FALSE;
}
if (!SCNetworkInterfaceCopyMediaOptions(interface, ¤t, NULL, &available, FALSE)) {
SCLog(_verbose, LOG_INFO, CFSTR("no media options for %@"), interfaceName);
return FALSE;
}
requested = __copyMediaOptions(options);
if (requested == NULL) {
CFDictionaryRef baseOptions;
baseOptions = CFDictionaryGetValue(baseSettings, interfaceName);
requested = __copyMediaOptions(baseOptions);
}
if (requested == NULL) {
requested = __copyMediaOptions(current);
}
if (requested == NULL) {
goto done;
}
if ((current != NULL) && CFEqual(current, requested)) {
ok = TRUE;
goto done;
}
if (!CFArrayContainsValue(available, CFRangeMake(0, CFArrayGetCount(available)), requested)) {
SCLog(_verbose, LOG_INFO, CFSTR("requested media settings unavailable for %@"), interfaceName);
goto done;
}
newOptions = __createMediaOptions(interfaceName, requested);
if (newOptions == -1) {
goto done;
}
sock = socket(AF_INET, SOCK_DGRAM, 0);
if (sock == -1) {
SCLog(TRUE, LOG_ERR, CFSTR("socket() failed: %s"), strerror(errno));
goto done;
}
bzero((char *)&ifm, sizeof(ifm));
(void)_SC_cfstring_to_cstring(interfaceName, ifm.ifm_name, sizeof(ifm.ifm_name), kCFStringEncodingASCII);
if (ioctl(sock, SIOCGIFMEDIA, (caddr_t)&ifm) == -1) {
SCLog(TRUE, LOG_DEBUG, CFSTR("ioctl(SIOCGIFMEDIA) failed: %s"), strerror(errno));
goto done;
}
bzero((char *)&ifr, sizeof(ifr));
bcopy(ifm.ifm_name, ifr.ifr_name, sizeof(ifr.ifr_name));
ifr.ifr_media = ifm.ifm_current & ~(IFM_NMASK|IFM_TMASK|IFM_OMASK|IFM_GMASK);
ifr.ifr_media |= newOptions;
SCLog(_verbose, LOG_INFO, CFSTR("old media settings: 0x%8.8x (0x%8.8x)"), ifm.ifm_current, ifm.ifm_active);
SCLog(_verbose, LOG_INFO, CFSTR("new media settings: 0x%8.8x"), ifr.ifr_media);
if (ioctl(sock, SIOCSIFMEDIA, (caddr_t)&ifr) == -1) {
SCLog(TRUE, LOG_DEBUG, CFSTR("%@: ioctl(SIOCSIFMEDIA) failed: %s"), interfaceName, strerror(errno));
goto done;
}
ok = TRUE;
done :
if (available != NULL) CFRelease(available);
if (current != NULL) CFRelease(current);
if (requested != NULL) CFRelease(requested);
if (sock != -1) (void)close(sock);
return ok;
}
#ifndef USE_SIOCSIFMTU
static void
ifconfig_exit(pid_t pid, int status, struct rusage *rusage, void *context)
{
char *if_name = (char *)context;
if (WIFEXITED(status)) {
if (WEXITSTATUS(status) != 0) {
SCLog(TRUE, LOG_ERR,
CFSTR("ifconfig %s failed, exit status = %d"),
if_name,
WEXITSTATUS(status));
}
} else if (WIFSIGNALED(status)) {
SCLog(TRUE, LOG_DEBUG,
CFSTR("ifconfig %s: terminated w/signal = %d"),
if_name,
WTERMSIG(status));
} else {
SCLog(TRUE, LOG_DEBUG,
CFSTR("ifconfig %s: exit status = %d"),
if_name,
status);
}
CFAllocatorDeallocate(NULL, if_name);
return;
}
#endif
__private_extern__
Boolean
_SCNetworkInterfaceSetMTU(SCNetworkInterfaceRef interface,
CFDictionaryRef options)
{
CFStringRef interfaceName;
int mtu_cur = -1;
int mtu_max = -1;
int mtu_min = -1;
int requested;
CFNumberRef val;
interfaceName = SCNetworkInterfaceGetBSDName(interface);
if (interfaceName == NULL) {
return FALSE;
}
if (!SCNetworkInterfaceCopyMTU(interface, &mtu_cur, &mtu_min, &mtu_max)) {
return FALSE;
}
val = NULL;
if (isA_CFDictionary(options)) {
val = CFDictionaryGetValue(options, kSCPropNetEthernetMTU);
val = isA_CFNumber(val);
}
if (val == NULL) {
CFDictionaryRef baseOptions;
baseOptions = CFDictionaryGetValue(baseSettings, interfaceName);
if (baseOptions != NULL) {
val = CFDictionaryGetValue(baseOptions, kSCPropNetEthernetMTU);
}
}
if (val != NULL) {
CFNumberGetValue(val, kCFNumberIntType, &requested);
} else {
requested = mtu_cur;
}
if (requested == mtu_cur) {
return TRUE;
}
if (((mtu_min >= 0) && (requested < mtu_min)) ||
((mtu_max >= 0) && (requested > mtu_max))) {
return FALSE;
}
#ifdef USE_SIOCSIFMTU
{
struct ifreq ifr;
int ret;
int sock;
bzero((char *)&ifr, sizeof(ifr));
(void)_SC_cfstring_to_cstring(interfaceName, ifr.ifr_name, sizeof(ifr.ifr_name), kCFStringEncodingASCII);
ifr.ifr_mtu = requested;
sock = socket(AF_INET, SOCK_DGRAM, 0);
if (sock == -1) {
SCLog(TRUE, LOG_ERR, CFSTR("socket() failed: %s"), strerror(errno));
return FALSE;
}
ret = ioctl(sock, SIOCSIFMTU, (caddr_t)&ifr);
(void)close(sock);
if (ret == -1) {
SCLog(TRUE, LOG_DEBUG, CFSTR("ioctl(SIOCSIFMTU) failed: %s"), strerror(errno));
return FALSE;
}
}
#else
{
char *ifconfig_argv[] = { "ifconfig", NULL, "mtu", NULL, NULL };
pid_t pid;
ifconfig_argv[1] = _SC_cfstring_to_cstring(interfaceName, NULL, 0, kCFStringEncodingASCII);
(void)asprintf(&ifconfig_argv[3], "%d", requested);
pid = _SCDPluginExecCommand(ifconfig_exit, ifconfig_argv[1], 0, 0, "/sbin/ifconfig", ifconfig_argv );
free(ifconfig_argv[3]);
if (pid <= 0) {
return FALSE;
}
}
#endif
return TRUE;
}
static CFStringRef
parse_component(CFStringRef key, CFStringRef prefix)
{
CFMutableStringRef comp;
CFRange range;
if (CFStringHasPrefix(key, prefix) == FALSE) {
return NULL;
}
comp = CFStringCreateMutableCopy(NULL, 0, key);
CFStringDelete(comp, CFRangeMake(0, CFStringGetLength(prefix)));
range = CFStringFind(comp, CFSTR("/"), 0);
if (range.location == kCFNotFound) {
return comp;
}
range.length = CFStringGetLength(comp) - range.location;
CFStringDelete(comp, range);
return comp;
}
static void updateLink(CFStringRef interfaceName, CFDictionaryRef options);
static void
updateInterfaces(CFArrayRef newInterfaces)
{
CFIndex i;
CFIndex n_old;
CFIndex n_new;
static CFArrayRef oldInterfaces = NULL;
n_old = (oldInterfaces != NULL) ? CFArrayGetCount(oldInterfaces) : 0;
n_new = CFArrayGetCount(newInterfaces);
for (i = 0; i < n_new; i++) {
CFStringRef interfaceName;
interfaceName = CFArrayGetValueAtIndex(newInterfaces, i);
if ((n_old == 0) ||
!CFArrayContainsValue(oldInterfaces,
CFRangeMake(0, n_old),
interfaceName)) {
CFDictionaryRef options;
options = CFDictionaryGetValue(wantSettings, interfaceName);
updateLink(interfaceName, options);
}
}
if (oldInterfaces != NULL) CFRelease(oldInterfaces);
oldInterfaces = CFRetain(newInterfaces);
}
static void
updateLink(CFStringRef interfaceName, CFDictionaryRef options)
{
SCNetworkInterfaceRef interface;
if (options != NULL) {
CFDictionarySetValue(wantSettings, interfaceName, options);
} else {
CFDictionaryRemoveValue(wantSettings, interfaceName);
}
interface = _SCNetworkInterfaceCreateWithBSDName(NULL, interfaceName,
kIncludeAllVirtualInterfaces);
if (interface == NULL) {
return;
}
if (options != NULL) {
if (!CFDictionaryContainsKey(baseSettings, interfaceName)) {
CFDictionaryRef cur_media = NULL;
CFMutableDictionaryRef new_media = NULL;
int cur_mtu = -1;
if (SCNetworkInterfaceCopyMediaOptions(interface, &cur_media, NULL, NULL, FALSE)) {
if (cur_media != NULL) {
new_media = CFDictionaryCreateMutableCopy(NULL, 0, cur_media);
CFRelease(cur_media);
}
}
if (SCNetworkInterfaceCopyMTU(interface, &cur_mtu, NULL, NULL)) {
if (cur_mtu != -1) {
CFNumberRef num;
if (new_media == NULL) {
new_media = CFDictionaryCreateMutable(NULL,
0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
}
num = CFNumberCreate(NULL, kCFNumberIntType, &cur_mtu);
CFDictionaryAddValue(new_media, kSCPropNetEthernetMTU, num);
CFRelease(num);
}
}
if (new_media != NULL) {
CFDictionarySetValue(baseSettings, interfaceName, new_media);
CFRelease(new_media);
}
}
(void)_SCNetworkInterfaceSetMediaOptions(interface, options);
(void)_SCNetworkInterfaceSetMTU (interface, options);
} else {
options = CFDictionaryGetValue(baseSettings, interfaceName);
if (options != NULL) {
(void)_SCNetworkInterfaceSetMediaOptions(interface, options);
(void)_SCNetworkInterfaceSetMTU (interface, options);
CFDictionaryRemoveValue(baseSettings, interfaceName);
}
}
CFRelease(interface);
return;
}
static void
linkConfigChangedCallback(SCDynamicStoreRef store, CFArrayRef changedKeys, void *arg)
{
CFDictionaryRef changes;
CFIndex i;
CFIndex n;
static CFStringRef prefix = NULL;
if (prefix == NULL) {
prefix = SCDynamicStoreKeyCreate(NULL,
CFSTR("%@/%@/%@/"),
kSCDynamicStoreDomainSetup,
kSCCompNetwork,
kSCCompInterface);
}
changes = SCDynamicStoreCopyMultiple(store, changedKeys, NULL);
n = CFArrayGetCount(changedKeys);
for (i = 0; i < n; i++) {
CFStringRef key;
CFDictionaryRef info;
key = CFArrayGetValueAtIndex(changedKeys, i);
info = CFDictionaryGetValue(changes, key);
if (CFEqual(key, interfacesKey)) {
CFArrayRef interfaces;
interfaces = CFDictionaryGetValue(info, kSCPropNetInterfaces);
if (isA_CFArray(interfaces)) {
updateInterfaces(interfaces);
}
} else {
CFStringRef interfaceName;
interfaceName = parse_component(key, prefix);
if (interfaceName != NULL) {
updateLink(interfaceName, info);
CFRelease(interfaceName);
}
}
}
CFRelease(changes);
return;
}
__private_extern__
void
load_LinkConfiguration(CFBundleRef bundle, Boolean bundleVerbose)
{
CFStringRef key;
CFMutableArrayRef keys = NULL;
Boolean ok;
CFMutableArrayRef patterns = NULL;
if (bundleVerbose) {
_verbose = TRUE;
}
SCLog(_verbose, LOG_DEBUG, CFSTR("load() called"));
SCLog(_verbose, LOG_DEBUG, CFSTR(" bundle ID = %@"), CFBundleGetIdentifier(bundle));
baseSettings = CFDictionaryCreateMutable(NULL,
0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
wantSettings = CFDictionaryCreateMutable(NULL,
0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
store = SCDynamicStoreCreate(NULL,
CFSTR("Link Configuraton plug-in"),
linkConfigChangedCallback,
NULL);
if (store == NULL) {
SCLog(TRUE, LOG_ERR, CFSTR("SCDynamicStoreCreate() failed: %s"), SCErrorString(SCError()));
goto error;
}
keys = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
patterns = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
interfacesKey = SCDynamicStoreKeyCreateNetworkInterface(NULL,
kSCDynamicStoreDomainState);
CFArrayAppendValue(keys, interfacesKey);
key = SCDynamicStoreKeyCreateNetworkInterfaceEntity(NULL,
kSCDynamicStoreDomainSetup,
kSCCompAnyRegex,
kSCEntNetAirPort);
CFArrayAppendValue(patterns, key);
CFRelease(key);
key = SCDynamicStoreKeyCreateNetworkInterfaceEntity(NULL,
kSCDynamicStoreDomainSetup,
kSCCompAnyRegex,
kSCEntNetEthernet);
CFArrayAppendValue(patterns, key);
CFRelease(key);
key = SCDynamicStoreKeyCreateNetworkInterfaceEntity(NULL,
kSCDynamicStoreDomainSetup,
kSCCompAnyRegex,
kSCEntNetFireWire);
CFArrayAppendValue(patterns, key);
CFRelease(key);
ok = SCDynamicStoreSetNotificationKeys(store, keys, patterns);
CFRelease(keys);
CFRelease(patterns);
if (!ok) {
SCLog(TRUE, LOG_ERR,
CFSTR("SCDynamicStoreSetNotificationKeys() failed: %s"),
SCErrorString(SCError()));
goto error;
}
rls = SCDynamicStoreCreateRunLoopSource(NULL, store, 0);
if (rls == NULL) {
SCLog(TRUE, LOG_ERR,
CFSTR("SCDynamicStoreCreateRunLoopSource() failed: %s"),
SCErrorString(SCError()));
goto error;
}
CFRunLoopAddSource(CFRunLoopGetCurrent(), rls, kCFRunLoopDefaultMode);
return;
error :
if (baseSettings != NULL) CFRelease(baseSettings);
if (wantSettings != NULL) CFRelease(wantSettings);
if (store != NULL) CFRelease(store);
return;
}
#ifdef MAIN
int
main(int argc, char **argv)
{
_sc_log = FALSE;
_sc_verbose = (argc > 1) ? TRUE : FALSE;
load_LinkConfiguration(CFBundleGetMainBundle(), (argc > 1) ? TRUE : FALSE);
CFRunLoopRun();
exit(0);
return 0;
}
#endif