IPMonitorControlServer.c [plain text]
#include <CoreFoundation/CoreFoundation.h>
#include <xpc/xpc.h>
#include <xpc/private.h>
#include <sys/queue.h>
#include <CoreFoundation/CFRunLoop.h>
#include <SystemConfiguration/SCNetworkConfigurationPrivate.h>
#include "IPMonitorControlServer.h"
#include "symbol_scope.h"
#include "IPMonitorControlPrivate.h"
#include <SystemConfiguration/SCPrivate.h>
#ifdef TEST_IPMONITOR_CONTROL
#define my_log(__level, fmt, ...) SCPrint(TRUE, stdout, CFSTR(fmt "\n"), ## __VA_ARGS__)
#else
#include "ip_plugin.h"
#endif
STATIC dispatch_queue_t S_IPMonitorControlServerQueue;
typedef struct ControlSession ControlSession, * ControlSessionRef;
#define LIST_HEAD_ControlSession LIST_HEAD(ControlSessionHead, ControlSession)
#define LIST_ENTRY_ControlSession LIST_ENTRY(ControlSession)
LIST_HEAD_ControlSession S_ControlSessions;
struct ControlSession {
LIST_ENTRY_ControlSession link;
xpc_connection_t connection;
CFMutableDictionaryRef assertions;
};
STATIC CFMutableArrayRef S_if_changes;
STATIC CFRange S_if_changes_range;
STATIC void
InterfaceChangedListAddInterface(CFStringRef ifname)
{
if (S_if_changes == NULL) {
S_if_changes = CFArrayCreateMutable(NULL,
0, &kCFTypeArrayCallBacks);
CFArrayAppendValue(S_if_changes, ifname);
S_if_changes_range.length = 1;
}
else if (CFArrayContainsValue(S_if_changes, S_if_changes_range,
ifname) == FALSE) {
CFArrayAppendValue(S_if_changes, ifname);
S_if_changes_range.length++;
}
}
STATIC CFArrayRef
InterfaceChangedListCopy(void)
{
CFArrayRef current_list;
current_list = S_if_changes;
S_if_changes = NULL;
return (current_list);
}
STATIC void
InterfaceRankAssertionAdd(const void * key, const void * value, void * context)
{
CFMutableDictionaryRef * assertions_p;
CFNumberRef existing_rank;
CFNumberRef rank = (CFNumberRef)value;
assertions_p = (CFMutableDictionaryRef *)context;
if (*assertions_p == NULL) {
*assertions_p
= CFDictionaryCreateMutable(NULL, 0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
CFDictionarySetValue(*assertions_p, key, value);
return;
}
existing_rank = CFDictionaryGetValue(*assertions_p, key);
if (existing_rank == NULL
|| (CFNumberCompare(rank, existing_rank, NULL)
== kCFCompareGreaterThan)) {
CFDictionarySetValue(*assertions_p, key, value);
}
return;
}
STATIC CFDictionaryRef
InterfaceRankAssertionsCopy(void)
{
CFMutableDictionaryRef assertions = NULL;
ControlSessionRef session;
LIST_FOREACH(session, &S_ControlSessions, link) {
if (session->assertions == NULL) {
continue;
}
CFDictionaryApplyFunction(session->assertions,
InterfaceRankAssertionAdd,
&assertions);
}
return (assertions);
}
STATIC CFRunLoopRef S_runloop;
STATIC CFRunLoopSourceRef S_signal_source;
STATIC void
SetNotificationInfo(CFRunLoopRef runloop, CFRunLoopSourceRef rls)
{
S_runloop = runloop;
S_signal_source = rls;
return;
}
STATIC void
GenerateNotification(void)
{
if (S_signal_source != NULL) {
CFRunLoopSourceSignal(S_signal_source);
if (S_runloop != NULL) {
CFRunLoopWakeUp(S_runloop);
}
}
return;
}
STATIC void
AddChangedInterface(const void * key, const void * value, void * context)
{
InterfaceChangedListAddInterface((CFStringRef)key);
return;
}
STATIC void
ControlSessionInvalidate(ControlSessionRef session)
{
my_log(LOG_DEBUG, "Invalidating %p", session);
LIST_REMOVE(session, link);
if (session->assertions != NULL) {
my_log(LOG_DEBUG,
"IPMonitorControlServer: %p pid %d removing assertions %@",
session->connection,
xpc_connection_get_pid(session->connection),
session->assertions);
CFDictionaryApplyFunction(session->assertions, AddChangedInterface,
NULL);
CFRelease(session->assertions);
session->assertions = NULL;
GenerateNotification();
}
return;
}
STATIC void
ControlSessionRelease(void * p)
{
my_log(LOG_DEBUG, "Releasing %p", p);
free(p);
return;
}
STATIC ControlSessionRef
ControlSessionLookup(xpc_connection_t connection)
{
return ((ControlSessionRef)xpc_connection_get_context(connection));
}
STATIC ControlSessionRef
ControlSessionCreate(xpc_connection_t connection)
{
ControlSessionRef session;
session = (ControlSessionRef)malloc(sizeof(*session));
bzero(session, sizeof(*session));
session->connection = connection;
xpc_connection_set_finalizer_f(connection, ControlSessionRelease);
xpc_connection_set_context(connection, session);
LIST_INSERT_HEAD(&S_ControlSessions, session, link);
my_log(LOG_DEBUG, "Created %p (connection %p)", session, connection);
return (session);
}
STATIC ControlSessionRef
ControlSessionGet(xpc_connection_t connection)
{
ControlSessionRef session;
session = ControlSessionLookup(connection);
if (session != NULL) {
return (session);
}
return (ControlSessionCreate(connection));
}
STATIC void
ControlSessionSetInterfaceRank(ControlSessionRef session,
const char * ifname,
SCNetworkServicePrimaryRank rank)
{
CFStringRef ifname_cf;
if (session->assertions == NULL) {
if (rank == kSCNetworkServicePrimaryRankDefault) {
return;
}
session->assertions
= CFDictionaryCreateMutable(NULL, 0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
}
ifname_cf = CFStringCreateWithCString(NULL, ifname,
kCFStringEncodingUTF8);
if (rank == kSCNetworkServicePrimaryRankDefault) {
CFDictionaryRemoveValue(session->assertions, ifname_cf);
if (CFDictionaryGetCount(session->assertions) == 0) {
CFRelease(session->assertions);
session->assertions = NULL;
}
}
else {
CFNumberRef rank_cf;
rank_cf = CFNumberCreate(NULL, kCFNumberSInt32Type, &rank);
CFDictionarySetValue(session->assertions, ifname_cf, rank_cf);
CFRelease(rank_cf);
}
InterfaceChangedListAddInterface(ifname_cf);
GenerateNotification();
CFRelease(ifname_cf);
return;
}
STATIC SCNetworkServicePrimaryRank
ControlSessionGetInterfaceRank(ControlSessionRef session,
const char * ifname)
{
SCNetworkServicePrimaryRank rank = kSCNetworkServicePrimaryRankDefault;
if (session->assertions != NULL) {
CFStringRef ifname_cf;
CFNumberRef rank_cf;
ifname_cf = CFStringCreateWithCString(NULL, ifname,
kCFStringEncodingUTF8);
rank_cf = CFDictionaryGetValue(session->assertions, ifname_cf);
CFRelease(ifname_cf);
if (rank_cf != NULL) {
(void)CFNumberGetValue(rank_cf, kCFNumberSInt32Type, &rank);
}
}
return (rank);
}
STATIC Boolean
IPMonitorControlServerValidateConnection(xpc_connection_t connection)
{
uid_t uid;
uid = xpc_connection_get_euid(connection);
return (uid == 0);
}
STATIC int
IPMonitorControlServerHandleSetInterfaceRank(xpc_connection_t connection,
xpc_object_t request,
xpc_object_t reply)
{
const char * ifname;
SCNetworkServicePrimaryRank rank;
ControlSessionRef session;
if (IPMonitorControlServerValidateConnection(connection) == FALSE) {
my_log(LOG_INFO, "connection %p pid %d permission denied",
connection, xpc_connection_get_pid(connection));
return (EPERM);
}
ifname
= xpc_dictionary_get_string(request,
kIPMonitorControlRequestKeyInterfaceName);
if (ifname == NULL) {
return (EINVAL);
}
rank = (SCNetworkServicePrimaryRank)
xpc_dictionary_get_uint64(request,
kIPMonitorControlRequestKeyPrimaryRank);
switch (rank) {
case kSCNetworkServicePrimaryRankDefault:
case kSCNetworkServicePrimaryRankFirst:
case kSCNetworkServicePrimaryRankLast:
case kSCNetworkServicePrimaryRankNever:
case kSCNetworkServicePrimaryRankScoped:
break;
default:
return (EINVAL);
}
session = ControlSessionGet(connection);
ControlSessionSetInterfaceRank(session, ifname, rank);
my_log(LOG_INFO, "connection %p pid %d set %s %u",
connection, xpc_connection_get_pid(connection), ifname, rank);
return (0);
}
STATIC int
IPMonitorControlServerHandleGetInterfaceRank(xpc_connection_t connection,
xpc_object_t request,
xpc_object_t reply)
{
const char * ifname;
SCNetworkServicePrimaryRank rank;
ControlSessionRef session;
if (reply == NULL) {
return (EINVAL);
}
session = ControlSessionLookup(connection);
if (session == NULL) {
return (ENOENT);
}
ifname
= xpc_dictionary_get_string(request,
kIPMonitorControlRequestKeyInterfaceName);
if (ifname == NULL) {
return (EINVAL);
}
rank = ControlSessionGetInterfaceRank(session, ifname);
xpc_dictionary_set_uint64(reply, kIPMonitorControlResponseKeyPrimaryRank,
rank);
return (0);
}
STATIC void
IPMonitorControlServerHandleDisconnect(xpc_connection_t connection)
{
ControlSessionRef session;
my_log(LOG_DEBUG, "IPMonitorControlServer: client %p went away", connection);
session = ControlSessionLookup(connection);
if (session == NULL) {
return;
}
ControlSessionInvalidate(session);
return;
}
STATIC void
IPMonitorControlServerHandleRequest(xpc_connection_t connection,
xpc_object_t request)
{
xpc_type_t type;
type = xpc_get_type(request);
if (type == XPC_TYPE_DICTIONARY) {
int error = 0;
uint64_t request_type;
xpc_connection_t remote;
xpc_object_t reply;
request_type
= xpc_dictionary_get_uint64(request,
kIPMonitorControlRequestKeyType);
reply = xpc_dictionary_create_reply(request);
switch (request_type) {
case kIPMonitorControlRequestTypeSetInterfaceRank:
error = IPMonitorControlServerHandleSetInterfaceRank(connection,
request,
reply);
break;
case kIPMonitorControlRequestTypeGetInterfaceRank:
error = IPMonitorControlServerHandleGetInterfaceRank(connection,
request,
reply);
break;
default:
error = EINVAL;
break;
}
if (reply == NULL) {
return;
}
xpc_dictionary_set_int64(reply, kIPMonitorControlResponseKeyError,
error);
remote = xpc_dictionary_get_remote_connection(request);
xpc_connection_send_message(remote, reply);
xpc_release(reply);
}
else if (type == XPC_TYPE_ERROR) {
if (request == XPC_ERROR_CONNECTION_INVALID) {
IPMonitorControlServerHandleDisconnect(connection);
}
else if (request == XPC_ERROR_CONNECTION_INTERRUPTED) {
my_log(LOG_INFO, "connection interrupted");
}
}
else {
my_log(LOG_NOTICE, "unexpected event");
}
return;
}
STATIC void
IPMonitorControlServerHandleNewConnection(xpc_connection_t connection)
{
xpc_handler_t handler;
handler = ^(xpc_object_t event) {
os_activity_t activity_id;
activity_id = os_activity_start("processing IPMonitor [rank] request",
OS_ACTIVITY_FLAG_DEFAULT);
IPMonitorControlServerHandleRequest(connection, event);
os_activity_end(activity_id);
};
xpc_connection_set_event_handler(connection, handler);
xpc_connection_resume(connection);
return;
}
STATIC xpc_connection_t
IPMonitorControlServerCreate(dispatch_queue_t queue, const char * name)
{
uint64_t flags = XPC_CONNECTION_MACH_SERVICE_LISTENER;
xpc_connection_t connection;
xpc_handler_t handler;
connection = xpc_connection_create_mach_service(name, queue, flags);
if (connection == NULL) {
return (NULL);
}
handler = ^(xpc_object_t event) {
os_activity_t activity_id;
xpc_type_t type;
activity_id = os_activity_start("processing IPMonitor [rank] connection request",
OS_ACTIVITY_FLAG_DEFAULT);
type = xpc_get_type(event);
if (type == XPC_TYPE_CONNECTION) {
IPMonitorControlServerHandleNewConnection(event);
}
else if (type == XPC_TYPE_ERROR) {
const char * desc;
desc = xpc_dictionary_get_string(event, XPC_ERROR_KEY_DESCRIPTION);
if (event == XPC_ERROR_CONNECTION_INVALID) {
my_log(LOG_NOTICE, "%s", desc);
xpc_release(connection);
}
else {
my_log(LOG_NOTICE, "%s", desc);
}
}
else {
my_log(LOG_NOTICE, "unknown event %p", type);
}
os_activity_end(activity_id);
};
S_IPMonitorControlServerQueue = queue;
xpc_connection_set_event_handler(connection, handler);
xpc_connection_resume(connection);
return (connection);
}
PRIVATE_EXTERN Boolean
IPMonitorControlServerStart(CFRunLoopRef runloop, CFRunLoopSourceRef rls,
Boolean * verbose)
{
dispatch_queue_t q;
xpc_connection_t connection;
SetNotificationInfo(runloop, rls);
q = dispatch_queue_create("IPMonitorControlServer", NULL);
connection = IPMonitorControlServerCreate(q, kIPMonitorControlServerName);
if (connection == NULL) {
my_log(LOG_ERR,
"IPMonitorControlServer: failed to create server");
dispatch_release(q);
return (FALSE);
}
return (TRUE);
}
PRIVATE_EXTERN CFArrayRef
IPMonitorControlServerCopyInterfaceRankInformation(CFDictionaryRef * info)
{
__block CFArrayRef changed;
__block CFDictionaryRef dict;
dispatch_sync(S_IPMonitorControlServerQueue,
^{
dict = InterfaceRankAssertionsCopy();
changed = InterfaceChangedListCopy();
});
*info = dict;
return (changed);
}