IPMonitorControlServer.c   [plain text]


/*
 * Copyright (c) 2013-2016 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 <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, __format, ...)	SCPrint(TRUE, stdout, CFSTR(__format "\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<string> = rank<number> */
};

/**
 ** 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)) {
	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, rank);
	return;
    }
    existing_rank = CFDictionaryGetValue(*assertions_p, key);
    if (existing_rank == NULL
	|| (CFNumberCompare(rank, existing_rank, NULL)
	    == kCFCompareGreaterThan)) {
	CFDictionarySetValue(*assertions_p, key, rank);
    }
    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)
{
    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) {
	    /* 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)) {
	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) {
	/* 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;

    my_log(LOG_DEBUG, "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_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) {
	IPMonitorControlServerHandleRequest(connection, event);
    };
    xpc_connection_set_event_handler(connection, handler);
    xpc_connection_set_target_queue(connection, S_IPMonitorControlServerQueue);
    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, "%s", desc);
		xpc_release(connection);
	    }
	    else {
		my_log(LOG_NOTICE, "%s", desc);
	    }
	}
	else {
	    my_log(LOG_NOTICE, "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;

    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);
}