#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 SCDynamicStoreRef store = NULL;
static CFRunLoopSourceRef rls = NULL;
static Boolean _verbose = FALSE;
int
__createMediaOptions(CFStringRef interface, 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
_NetworkInterfaceSetMediaOptions(CFStringRef interface,
CFDictionaryRef options)
{
CFArrayRef available = NULL;
CFDictionaryRef current = NULL;
struct ifmediareq ifm;
struct ifreq ifr;
Boolean ok = FALSE;
int newOptions;
CFDictionaryRef requested;
int sock = -1;
if (!NetworkInterfaceCopyMediaOptions(interface, ¤t, NULL, &available, FALSE)) {
return FALSE;
}
requested = __copyMediaOptions(options);
if (requested == NULL) {
CFDictionaryRef baseOptions;
baseOptions = CFDictionaryGetValue(baseSettings, interface);
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(TRUE, LOG_DEBUG, CFSTR("requested media settings unavailable"));
goto done;
}
newOptions = __createMediaOptions(interface, requested);
if (newOptions == -1) {
goto done;
}
sock = socket(AF_INET, SOCK_DGRAM, 0);
if (sock < 0) {
SCLog(TRUE, LOG_ERR, CFSTR("socket() failed: %s"), strerror(errno));
goto done;
}
bzero((char *)&ifm, sizeof(ifm));
(void)_SC_cfstring_to_cstring(interface, ifm.ifm_name, sizeof(ifm.ifm_name), kCFStringEncodingASCII);
if (ioctl(sock, SIOCGIFMEDIA, (caddr_t)&ifm) < 0) {
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;
if (ioctl(sock, SIOCSIFMEDIA, (caddr_t)&ifr) < 0) {
SCLog(TRUE, LOG_DEBUG, CFSTR("ioctl(SIOCSIFMEDIA) failed: %s"), strerror(errno));
goto done;
}
ok = TRUE;
done :
if (available) CFRelease(available);
if (current) CFRelease(current);
if (requested) CFRelease(requested);
if (sock >= 0) (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
_NetworkInterfaceSetMTU(CFStringRef interface,
CFDictionaryRef options)
{
int mtu_cur = -1;
int mtu_max = -1;
int mtu_min = -1;
int requested;
CFNumberRef val;
if (!NetworkInterfaceCopyMTU(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, interface);
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(interface, ifr.ifr_name, sizeof(ifr.ifr_name), kCFStringEncodingASCII);
ifr.ifr_mtu = requested;
sock = socket(AF_INET, SOCK_DGRAM, 0);
if (sock < 0) {
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(interface, 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 ifKey, CFDictionaryRef options)
{
CFStringRef interface = NULL;
static CFStringRef prefix = NULL;
if (!prefix) {
prefix = SCDynamicStoreKeyCreate(NULL,
CFSTR("%@/%@/%@/"),
kSCDynamicStoreDomainSetup,
kSCCompNetwork,
kSCCompInterface);
}
interface = parse_component(ifKey, prefix);
if (!interface) {
goto done;
}
if (options) {
if (!CFDictionaryContainsKey(baseSettings, interface)) {
CFDictionaryRef cur_media = NULL;
CFMutableDictionaryRef new_media = NULL;
int cur_mtu = -1;
if (NetworkInterfaceCopyMediaOptions(interface, &cur_media, NULL, NULL, FALSE)) {
if (cur_media != NULL) {
new_media = CFDictionaryCreateMutableCopy(NULL, 0, cur_media);
CFRelease(cur_media);
}
}
if (NetworkInterfaceCopyMTU(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, interface, new_media);
CFRelease(new_media);
}
}
(void)_NetworkInterfaceSetMediaOptions(interface, options);
(void)_NetworkInterfaceSetMTU (interface, options);
} else {
options = CFDictionaryGetValue(baseSettings, interface);
if (options) {
(void)_NetworkInterfaceSetMediaOptions(interface, options);
(void)_NetworkInterfaceSetMTU (interface, options);
CFDictionaryRemoveValue(baseSettings, interface);
}
}
done :
if (interface) CFRelease(interface);
return;
}
static void
linkConfigChangedCallback(SCDynamicStoreRef store, CFArrayRef changedKeys, void *arg)
{
CFIndex i;
CFIndex n;
CFDictionaryRef linkInfo;
linkInfo = SCDynamicStoreCopyMultiple(store, changedKeys, NULL);
n = CFArrayGetCount(changedKeys);
for (i = 0; i < n; i++) {
CFStringRef key;
CFDictionaryRef link;
key = CFArrayGetValueAtIndex(changedKeys, i);
link = CFDictionaryGetValue(linkInfo, key);
updateLink(key, link);
}
CFRelease(linkInfo);
return;
}
__private_extern__
void
load_LinkConfiguration(CFBundleRef bundle, Boolean bundleVerbose)
{
CFStringRef key;
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);
store = SCDynamicStoreCreate(NULL,
CFSTR("Link Configuraton plug-in"),
linkConfigChangedCallback,
NULL);
if (!store) {
SCLog(TRUE, LOG_ERR, CFSTR("SCDynamicStoreCreate() failed: %s"), SCErrorString(SCError()));
goto error;
}
patterns = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
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);
if (!SCDynamicStoreSetNotificationKeys(store, NULL, patterns)) {
SCLog(TRUE, LOG_ERR,
CFSTR("SCDynamicStoreSetNotificationKeys() failed: %s"),
SCErrorString(SCError()));
goto error;
}
rls = SCDynamicStoreCreateRunLoopSource(NULL, store, 0);
if (!rls) {
SCLog(TRUE, LOG_ERR,
CFSTR("SCDynamicStoreCreateRunLoopSource() failed: %s"),
SCErrorString(SCError()));
goto error;
}
CFRunLoopAddSource(CFRunLoopGetCurrent(), rls, kCFRunLoopDefaultMode);
CFRelease(patterns);
return;
error :
if (baseSettings) CFRelease(baseSettings);
if (store) CFRelease(store);
if (patterns) CFRelease(patterns);
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