/* * Copyright (c) 2013-2014 Apple Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ /* * IPMonitorControlServer.c * - IPC channel to IPMonitor * - used to create interface rank assertions */ /* * Modification History * * December 16, 2013 Dieter Siegmund (dieter@apple.com) * - initial revision */ #include #include #include #include #include #include #include "IPMonitorControlServer.h" #include "symbol_scope.h" #include "IPMonitorControlPrivate.h" #include STATIC Boolean * S_verbose; #ifdef TEST_IPMONITOR_CONTROL #define my_log(__level, fmt, ...) SCPrint(TRUE, stdout, CFSTR(fmt "\n"), ## __VA_ARGS__) #else /* TEST_IPMONITOR_CONTROL */ #include "ip_plugin.h" #endif /* TEST_IPMONITOR_CONTROL */ 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; /* ifname = rank */ }; /** ** Support Functions **/ 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; } /** ** ControlSession **/ STATIC void AddChangedInterface(const void * key, const void * value, void * context) { InterfaceChangedListAddInterface((CFStringRef)key); return; } STATIC void ControlSessionInvalidate(ControlSessionRef session) { if (*S_verbose) { my_log(LOG_NOTICE, "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) { if (*S_verbose) { my_log(LOG_NOTICE, "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); if (*S_verbose) { my_log(LOG_NOTICE, "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) { /* no assertions, no need to store rank */ 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); } /** ** IPMonitorControlServer **/ 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_DEBUG, "IPMonitorControlServer: %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_DEBUG, "IPMonitorControlServer: %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) { /* no point in processing the request if we can't provide an answer */ return (EINVAL); } session = ControlSessionLookup(connection); if (session == NULL) { /* no session, no rank assertion */ 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; if (*S_verbose) { my_log(LOG_NOTICE, "IPMonitorControlServer: client %p went away", connection); } session = ControlSessionLookup(connection); if (session == NULL) { /* never asserted anything */ 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) { /* client didn't want a reply */ 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_NOTICE, "IPMonitorControlServer: connection interrupted"); } } else { my_log(LOG_NOTICE, "IPMonitorControlServer: unexpected event"); } return; } STATIC void IPMonitorControlServerHandleNewConnection(xpc_connection_t connection) { xpc_handler_t handler; handler = ^(xpc_object_t event) { IPMonitorControlServerHandleRequest(connection, event); }; 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) { xpc_type_t type; 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, "IPMonitorControlServer: %s", desc); xpc_release(connection); } else { my_log(LOG_NOTICE, "IPMonitorControlServer: %s", desc); } } else { my_log(LOG_NOTICE, "IPMonitorControlServer: unknown event %p", type); } }; 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; S_verbose = verbose; 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); }