MCDeviceDiscovery.m [plain text]
//
// MCDeviceDiscovery.m
// HIDRelay
//
// Created by Roberto Yepez on 8/5/14.
// Copyright (c) 2014 Roberto Yepez. All rights reserved.
//
#import <TargetConditionals.h>
#import "MCDeviceDiscovery.h"
#import <SystemConfiguration/SCNetworkConfigurationPrivate.h>
#import <SystemConfiguration/SCDynamicStore.h>
#import <SystemConfiguration/SCDynamicStoreKey.h>
#import <SystemConfiguration/SCSchemaDefinitions.h>
#import <net/if.h>
#import <netdb.h>
#import <sys/ioctl.h>
#import <netinet/in.h>
char * const kHandshakingMulticastPort = "5123";
// all nodes in link multicast address
char * const kHandshakingMulticastAddress = "ff02::1";
// Prefix used in link local IP addresses
NSString * const kHandshakeMessagePrefix = @"hrhs";
@interface MCDeviceDiscovery ()
{
}
// Queue for asynchronous socket sending and receiving
@property (nonatomic,strong) dispatch_queue_t handshakeWorkQueue;
// Timer to periodically send a multicast message
@property (nonatomic,strong) dispatch_source_t multicastSendTimer;
// name of local active ethernet interface (ex. @"en1")
@property (nonatomic,strong) NSString *localInterfaceName;
// IPv4 IP assigned to localInterfaceName. This is expected to be a link local address (169.254.X.X).
@property (nonatomic,strong) NSString *localIP;
// IPv4 IP assigned to remote host. This is expected to be a link local address (169.254.X.X).
@property (nonatomic,strong) NSString *remoteIP;
// IPv6 address used for multicasting during handshaking algorithm
@property (nonatomic, assign) struct addrinfo *multicastAddr;
// IPv6 socket used for sending multicast during handshaking
@property (nonatomic, assign) int sendSocketFd;
// IPv6 socket used for receiving multicast during handshaking
@property (nonatomic, assign) int receiveSocketFd;
// Dispatch source used for asyunchronous reading
@property (nonatomic, strong) dispatch_source_t readSource;
// YES when local host knows the remoteIP and the remote host knows the localP
@property (nonatomic, assign) BOOL didCompleteHandshake;
// Block to call when finished performing handshake
@property (nonatomic, strong) MCDeviceHandshakeCompletion handshakeCompletionBlock;
@end
@implementation MCDeviceDiscovery
- (id)initWithInterface:(NSString *)interface
{
self = [super init];
if (self != nil) {
self.localInterfaceName = interface;
}
return self;
}
#pragma mark Local Interface Info
+ (NSString *)localEthernetInterfaceName
{
NSString * result = nil;
SCDynamicStoreRef storeRef = SCDynamicStoreCreate(NULL,(CFStringRef)@"FindActiveEthernetInterface", NULL, NULL);
NSString *globalPath = (__bridge_transfer NSString*)SCDynamicStoreKeyCreateNetworkGlobalEntity(kCFAllocatorDefault, kSCDynamicStoreDomainSetup, kSCEntNetIPv4);
// equivalent to SCDynamicStoreCopyValue(storeRef, CFSTR("Setup:/Network/Global/IPv4")) but without assuming the schema structure
NSDictionary *global = (__bridge_transfer NSDictionary*)SCDynamicStoreCopyValue(storeRef, (__bridge CFStringRef)globalPath);
/* Expect global to contain something like
ServiceOrder = (
"3E2652AC-CD50-4E4B-AA9B-872CB8E0E5D3",
"CCC216BF-393A-41FE-87DB-A36E65C59A10",
"041A0098-0BF4-4F0B-9C2D-39E034B807E6"
);
*/
//NSLog(@"serviceOrderList NSArray *serviceOrderList = global[(__bridge NSString*)kSCPropNetServiceOrder];
for (NSString *serviceID in serviceOrderList) {
//NSLog(@"serviceID: // equivalent to CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("Setup:/Network/Service/ NSString* path = (__bridge_transfer NSString*)SCDynamicStoreKeyCreateNetworkServiceEntity(kCFAllocatorDefault, kSCDynamicStoreDomainSetup, (__bridge CFStringRef)serviceID, kSCEntNetInterface);
NSDictionary *serviceInterface = (__bridge_transfer NSDictionary *)SCDynamicStoreCopyValue(storeRef, (__bridge CFStringRef)path);
/* Expect serviceInterface to contain something like
DeviceName = en0;
Hardware = AirPort;
Type = Ethernet;
UserDefinedName = "Wi-Fi";
*/
//NSLog(@"serviceInterface NSString *interfaceName = serviceInterface[(__bridge NSString*)kSCPropNetInterfaceDeviceName];
NSString *hardware = serviceInterface[(__bridge NSString*)kSCPropNetInterfaceHardware];
NSString *type = serviceInterface[(__bridge NSString*)kSCPropNetInterfaceType];
//NSString *userDefinedName = serviceInterface[(__bridge NSString*)kSCPropUserDefinedName];
if ([interfaceName hasPrefix:@"en"] &&
[hardware isEqualToString:(__bridge NSString*)kSCValNetInterfaceTypeEthernet] &&
[type isEqualToString:(__bridge NSString*)kSCValNetInterfaceTypeEthernet]) {
SCNetworkInterfaceRef interface = _SCNetworkInterfaceCreateWithBSDName(kCFAllocatorDefault, (__bridge CFStringRef)(interfaceName), 0);
BOOL isActive = NO;
// *** RY: TODO: Figure out better way to restrict or handle more interfaces
if ( interface ) {
NSLog(@"Found and Ethernet interface if (!_SCNetworkInterfaceIsThunderbolt(interface) &&
!_SCNetworkInterfaceIsTethered(interface) &&
!_SCNetworkInterfaceIsBuiltin(interface)) {
NSLog(@"Found and Ethernet interface // Equivalent to CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("State:/Network/Interface/ NSString *linkPath = (__bridge_transfer NSString*)SCDynamicStoreKeyCreateNetworkInterfaceEntity(kCFAllocatorDefault, kSCDynamicStoreDomainState, (__bridge CFStringRef)interfaceName, kSCEntNetLink);
NSDictionary *linkDict = (__bridge_transfer NSDictionary*)SCDynamicStoreCopyValue(storeRef, (__bridge CFStringRef)linkPath);
isActive = [linkDict[(__bridge NSString*)kSCPropNetLinkActive] boolValue];
} else {
NSLog(@"Sadly said interface was not suitable");
}
CFRelease(interface);
}
NSLog(@"Active = if (isActive) {
result = interfaceName;
break;
}
}
}
return result;
}
+ (NSString *)ipv4AddressForInterface:(NSString *)interfaceName
{
if (!interfaceName || ![interfaceName UTF8String]) return nil;
/* determine UDN according to MAC address */
int sock = socket (AF_INET, SOCK_STREAM, 0);
if (sock < 0)
{
NSLog(@"Error creating socket to return IP");
return NULL;
}
struct ifreq ifr;
strcpy(ifr.ifr_name, [interfaceName UTF8String]);
ifr.ifr_addr.sa_family = AF_INET;
if (ioctl (sock, SIOCGIFADDR, &ifr) < 0)
{
NSLog(@"Error calling ioctl to return IP");
close (sock);
return NULL;
}
uint32_t ip = ((struct sockaddr_in *) &ifr.ifr_addr)->sin_addr.s_addr;
ip = ntohl (ip);
NSString *result = [NSString stringWithFormat:@"
close (sock);
return result;
}
#pragma mark Handshaking algorithm
// Makes sure we have an active ethernet interace with a link local IP address
- (BOOL)discoverLocalInterface
{
//self.localInterfaceName = nil;
self.localIP = nil;
self.localInterfaceName = self.localInterfaceName ? self.localInterfaceName : [MCDeviceDiscovery localEthernetInterfaceName];
if (!self.localInterfaceName) {
NSLog(@" return NO;
}
self.localIP = [MCDeviceDiscovery ipv4AddressForInterface:self.localInterfaceName];
if (!self.localIP) {
NSLog(@" return NO;
}
// We don't know the remote IP yet
self.remoteIP = @"NONE";
return YES;
}
// Initializes multicastAddr and sendSocketFd state
- (BOOL)setUpMulticastSendSocket
{
NSLog(@"
if (!self.localInterfaceName || !self.localIP) {
NSLog(@" return NO;
}
struct addrinfo hints;
bzero(&hints, sizeof(hints));
hints.ai_family = AF_INET6;
hints.ai_flags = AI_NUMERICHOST;
hints.ai_socktype = SOCK_DGRAM;
int err_num;
err_num = getaddrinfo(kHandshakingMulticastAddress, kHandshakingMulticastPort, &hints, &_multicastAddr);
if(err_num) {
NSLog(@"Error: return NO;
}
_sendSocketFd = socket(_multicastAddr->ai_family, _multicastAddr->ai_socktype, _multicastAddr->ai_protocol);
if (_sendSocketFd < 0) {
NSLog(@"Error: return NO;
}
unsigned int ifindex;
ifindex = if_nametoindex([self.localInterfaceName UTF8String]);
if (setsockopt(_sendSocketFd, IPPROTO_IPV6, IPV6_MULTICAST_IF, &ifindex,sizeof(ifindex)) !=0) {
NSLog(@"Error: return NO;
}
return YES;
}
// initializes receiveSocketFd and joins multicast group for receiving
// must be called after setUpMulticastSendSocket because it uses multicastAddr
- (BOOL)setUpMulticastReceiveSocket
{
NSLog(@"
if (!self.localInterfaceName || !self.localIP) {
NSLog(@" return NO;
}
if (!_multicastAddr) {
NSLog(@" return NO;
}
// Get local address to intialize receiveSocketFd
struct addrinfo hints;
struct addrinfo *localAddr;
bzero(&hints, sizeof(hints));
hints.ai_family = _multicastAddr->ai_family;
hints.ai_socktype = _multicastAddr->ai_socktype;
hints.ai_flags = AI_PASSIVE;
int err_num;
err_num = getaddrinfo(NULL, kHandshakingMulticastPort, &hints, &localAddr);
if(err_num) {
NSLog(@"Error: return NO;
}
_receiveSocketFd = socket(localAddr->ai_family, localAddr->ai_socktype, localAddr->ai_protocol);
if (_receiveSocketFd < 0) {
NSLog(@"Error: return NO;
}
if (bind(_receiveSocketFd, localAddr->ai_addr, localAddr->ai_addrlen) < 0) {
NSLog(@"Error: return NO;
}
freeaddrinfo(localAddr);
// set to non-blocking
int flags;
flags = fcntl(_receiveSocketFd,F_GETFL);
if (fcntl(_receiveSocketFd, F_SETFL, flags | O_NONBLOCK) == -1){
NSLog(@"Error: return NO;
}
// Join the multicast group
struct ipv6_mreq multicastRequest;
const char *interfaceName = [self.localInterfaceName UTF8String];
multicastRequest.ipv6mr_interface = if_nametoindex(interfaceName);
multicastRequest.ipv6mr_multiaddr = ((struct sockaddr_in6*)(_multicastAddr->ai_addr))->sin6_addr;
if ( setsockopt(_receiveSocketFd, IPPROTO_IPV6, IPV6_JOIN_GROUP, (char*) &multicastRequest, sizeof(multicastRequest)) != 0 )
{
NSLog(@"Error: return NO;
}
return YES;
}
- (BOOL)setUpReceiveDispatchSource
{
NSLog(@"
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
self.readSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, _receiveSocketFd, 0, queue);
if (!self.readSource)
{
NSLog(@"Error: return NO;
}
// Install the event handler
dispatch_source_set_event_handler(self.readSource, ^{
if (dispatch_source_get_data(self.readSource) > 0) {
[self receiveHandshakeMulticast];
}
});
// Install the cancellation handler
dispatch_source_set_cancel_handler(self.readSource, ^{
NSLog(@" });
// Start reading the file.
dispatch_resume(self.readSource);
return YES;
}
- (void)tearDownMulticastSendReceiveState
{
if ( self.multicastSendTimer ) {
dispatch_source_cancel(self.multicastSendTimer);
self.multicastSendTimer = nil;
}
if ( _sendSocketFd ) {
close (_sendSocketFd);
_sendSocketFd = 0;
}
if ( _receiveSocketFd ) {
close (_receiveSocketFd);
_receiveSocketFd = 0;
}
if ( _multicastAddr ) {
freeaddrinfo(_multicastAddr);
_multicastAddr = nil;
}
if ( self.readSource ) {
dispatch_source_cancel(self.readSource);
self.readSource = nil;
}
}
// Sends a multicast message string composed of the local IP plus the known remote IP
- (BOOL)sendHandshakeMulticast
{
if (!_sendSocketFd) return NO;
NSString *messageString = [NSString stringWithFormat:@"
//NSLog(@"
const char *msg = [messageString UTF8String];
if (sendto(_sendSocketFd, msg, strlen(msg), 0, _multicastAddr->ai_addr, _multicastAddr->ai_addrlen) < 0) {
NSLog(@" return NO;
}
return YES;
}
- (BOOL)receiveHandshakeMulticast
{
const int kBufferSize = 256;
char msgbuf[kBufferSize];
bzero(&msgbuf, kBufferSize);
struct sockaddr_in6 rcv_addr;
int rcv_addr_len=sizeof(rcv_addr);
if ((recvfrom(_receiveSocketFd, msgbuf, kBufferSize, 0,(struct sockaddr *) &rcv_addr,(socklen_t *)&rcv_addr_len)) < 0) {
NSLog(@" return NO;
}
//NSLog(@"
NSString *receivedString = [NSString stringWithFormat:@" if (![receivedString hasPrefix:kHandshakeMessagePrefix]) {
NSLog(@" return NO;
}
NSArray *receivedComponents = [receivedString componentsSeparatedByString:@":"];
NSUInteger componentsCount = [receivedComponents count];
if (componentsCount != 3) {
NSLog(@" return NO;
}
NSString *messageLocalIP = receivedComponents[1];
NSString *messageRemoteIP = receivedComponents[2];
// only process messages we didn't send
if (![self.localIP isEqualToString:messageLocalIP]) {
// remember the message's sender IP as the remote IP
self.remoteIP = messageLocalIP;
if ([self.localIP isEqualToString:messageRemoteIP]) {
// The message sent by another host contains our own IP as a remote IP
// This means the sender knows about us and the handshake is done
self.didCompleteHandshake = YES;
}
}
return YES;
}
- (BOOL)setUpMulticastSendTimer
{
self.multicastSendTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, self.handshakeWorkQueue);
if (self.multicastSendTimer)
{
dispatch_source_set_timer(self.multicastSendTimer, dispatch_walltime(NULL, 0), 1.0 * NSEC_PER_SEC, 0.1 * NSEC_PER_SEC);
dispatch_source_set_event_handler(self.multicastSendTimer, ^ {
[self sendHandshakeMulticast];
if (self.didCompleteHandshake) {
if (self.handshakeCompletionBlock) {
self.handshakeCompletionBlock(YES,self.localInterfaceName,self.remoteIP);
}
[self tearDownMulticastSendReceiveState];
};
});
dispatch_resume(self.multicastSendTimer);
}
return YES;
}
- (void)performHandshakeWithCompletionBlock:(MCDeviceHandshakeCompletion)completion
{
if ( !self.handshakeWorkQueue )
self.handshakeWorkQueue = dispatch_queue_create("com.apple.hid.handshakeworkqueue" , DISPATCH_QUEUE_SERIAL);
self.handshakeCompletionBlock = completion;
BOOL didStart = NO;
if ([self discoverLocalInterface]) {
if ([self setUpMulticastSendSocket]) {
if ([self setUpMulticastReceiveSocket]) {
if ([self setUpReceiveDispatchSource]) {
didStart = [self setUpMulticastSendTimer];
}
}
}
}
if (!didStart) {
[self tearDownMulticastSendReceiveState];
if (self.handshakeCompletionBlock) {
// TODO - do a much better job at propagating an error code when an error happens.
self.handshakeCompletionBlock(NO,nil,nil);
}
}
}
@end