From 8337b3e708ac6dc985bd4bb711cd4a0d5356d7a6 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Sun, 14 Jul 2019 19:17:17 -0700 Subject: [PATCH] Add support for zero configuration IPv6 streaming --- Limelight/Database/TemporaryHost.h | 1 + Limelight/Database/TemporaryHost.m | 4 + .../Limelight.xcdatamodeld/.xccurrentversion | 2 +- .../Moonlight v1.4.xcdatamodel/contents | 41 ++++++ Limelight/Network/DiscoveryManager.m | 3 + Limelight/Network/DiscoveryWorker.m | 5 +- Limelight/Network/MDNSManager.m | 130 +++++++++++++++++- Limelight/Network/WakeOnLanManager.m | 6 +- .../ViewControllers/MainFrameViewController.m | 5 +- Moonlight.xcodeproj/project.pbxproj | 4 +- 10 files changed, 193 insertions(+), 8 deletions(-) create mode 100644 Limelight/Limelight.xcdatamodeld/Moonlight v1.4.xcdatamodel/contents diff --git a/Limelight/Database/TemporaryHost.h b/Limelight/Database/TemporaryHost.h index f029427..5f8679b 100644 --- a/Limelight/Database/TemporaryHost.h +++ b/Limelight/Database/TemporaryHost.h @@ -20,6 +20,7 @@ @property (nonatomic, nullable, retain) NSString *address; @property (nonatomic, nullable, retain) NSString *externalAddress; @property (nonatomic, nullable, retain) NSString *localAddress; +@property (nonatomic, nullable, retain) NSString *ipv6Address; @property (nonatomic, nullable, retain) NSString *mac; @property (nonatomic) int serverCodecModeSupport; diff --git a/Limelight/Database/TemporaryHost.m b/Limelight/Database/TemporaryHost.m index 765dd53..2a4e7e7 100644 --- a/Limelight/Database/TemporaryHost.m +++ b/Limelight/Database/TemporaryHost.m @@ -26,6 +26,7 @@ self.address = host.address; self.externalAddress = host.externalAddress; self.localAddress = host.localAddress; + self.ipv6Address = host.ipv6Address; self.mac = host.mac; self.name = host.name; self.uuid = host.uuid; @@ -60,6 +61,9 @@ if (self.localAddress != nil) { parentHost.localAddress = self.localAddress; } + if (self.ipv6Address != nil) { + parentHost.ipv6Address = self.ipv6Address; + } if (self.mac != nil) { parentHost.mac = self.mac; } diff --git a/Limelight/Limelight.xcdatamodeld/.xccurrentversion b/Limelight/Limelight.xcdatamodeld/.xccurrentversion index fe08733..8055370 100644 --- a/Limelight/Limelight.xcdatamodeld/.xccurrentversion +++ b/Limelight/Limelight.xcdatamodeld/.xccurrentversion @@ -3,6 +3,6 @@ _XCCurrentVersionName - Moonlight v1.3.xcdatamodel + Moonlight v1.4.xcdatamodel diff --git a/Limelight/Limelight.xcdatamodeld/Moonlight v1.4.xcdatamodel/contents b/Limelight/Limelight.xcdatamodeld/Moonlight v1.4.xcdatamodel/contents new file mode 100644 index 0000000..ce5077c --- /dev/null +++ b/Limelight/Limelight.xcdatamodeld/Moonlight v1.4.xcdatamodel/contents @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Limelight/Network/DiscoveryManager.m b/Limelight/Network/DiscoveryManager.m index af25e95..51d8d5f 100644 --- a/Limelight/Network/DiscoveryManager.m +++ b/Limelight/Network/DiscoveryManager.m @@ -130,6 +130,9 @@ if (host.localAddress != nil) { existingHost.localAddress = host.localAddress; } + if (host.ipv6Address != nil) { + existingHost.ipv6Address = host.ipv6Address; + } if (host.externalAddress != nil) { existingHost.externalAddress = host.externalAddress; } diff --git a/Limelight/Network/DiscoveryWorker.m b/Limelight/Network/DiscoveryWorker.m index 9095146..e4dc084 100644 --- a/Limelight/Network/DiscoveryWorker.m +++ b/Limelight/Network/DiscoveryWorker.m @@ -52,6 +52,9 @@ static const float POLL_RATE = 2.0f; // Poll every 2 seconds if (_host.externalAddress != nil) { [array addObject:_host.externalAddress]; } + if (_host.ipv6Address != nil) { + [array addObject:_host.ipv6Address]; + } // Remove duplicate addresses from the list. // This is done using an array rather than a set @@ -117,7 +120,7 @@ static const float POLL_RATE = 2.0f; // Poll every 2 seconds _host.online = receivedResponse; if (receivedResponse) { - Log(LOG_D, @"Received response from: %@\n{\n\t address:%@ \n\t localAddress:%@ \n\t externalAddress:%@ \n\t uuid:%@ \n\t mac:%@ \n\t pairState:%d \n\t online:%d \n\t activeAddress:%@ \n}", _host.name, _host.address, _host.localAddress, _host.externalAddress, _host.uuid, _host.mac, _host.pairState, _host.online, _host.activeAddress); + Log(LOG_D, @"Received response from: %@\n{\n\t address:%@ \n\t localAddress:%@ \n\t externalAddress:%@ \n\t ipv6Address:%@ \n\t uuid:%@ \n\t mac:%@ \n\t pairState:%d \n\t online:%d \n\t activeAddress:%@ \n}", _host.name, _host.address, _host.localAddress, _host.externalAddress, _host.ipv6Address, _host.uuid, _host.mac, _host.pairState, _host.online, _host.activeAddress); } } diff --git a/Limelight/Network/MDNSManager.m b/Limelight/Network/MDNSManager.m index 07a3a72..9b30e9b 100644 --- a/Limelight/Network/MDNSManager.m +++ b/Limelight/Network/MDNSManager.m @@ -63,11 +63,137 @@ static NSString* NV_SERVICE_TYPE = @"_nvstream._tcp"; [mDNSBrowser stop]; } ++ (NSString*)sockAddrToString:(NSData*)addrData { + char addrStr[INET6_ADDRSTRLEN]; + struct sockaddr* addr = (struct sockaddr*)[addrData bytes]; + if (addr->sa_family == AF_INET) { + inet_ntop(addr->sa_family, &((struct sockaddr_in*)addr)->sin_addr, addrStr, sizeof(addrStr)); + } + else { + inet_ntop(addr->sa_family, &((struct sockaddr_in6*)addr)->sin6_addr, addrStr, sizeof(addrStr)); + } + return [NSString stringWithFormat: @"%s", addrStr]; +} + ++ (BOOL)isAddress:(uint8_t*)address inSubnet:(uint8_t*)subnet netmask:(int)bits { + for (int i = 0; i < bits; i++) { + uint8_t mask = 1 << (i % 8); + if ((address[i / 8] & mask) != (subnet[i / 8] & mask)) { + return NO; + } + } + return YES; +} + ++ (BOOL)isLocalIpv6Address:(NSData*)addrData { + struct sockaddr_in6* sin6 = (struct sockaddr_in6*)[addrData bytes]; + if (sin6->sin6_family != AF_INET6) { + return NO; + } + + uint8_t* addrBytes = sin6->sin6_addr.s6_addr; + uint8_t prefix[2]; + + // fe80::/10 + prefix[0] = 0xfe; + prefix[1] = 0x80; + if ([MDNSManager isAddress:addrBytes inSubnet:prefix netmask:10]) { + // Link-local + return YES; + } + + // fec0::/10 + prefix[0] = 0xfe; + prefix[1] = 0xc0; + if ([MDNSManager isAddress:addrBytes inSubnet:prefix netmask:10]) { + // Site local + return YES; + } + + // fc00::/7 + prefix[0] = 0xfc; + prefix[1] = 0x00; + if ([MDNSManager isAddress:addrBytes inSubnet:prefix netmask:7]) { + // ULA + return YES; + } + + return NO; +} + ++ (NSString*)getBestIpv6Address:(NSArray*)addresses { + for (NSData* addrData in addresses) { + struct sockaddr_in6* sin6 = (struct sockaddr_in6*)[addrData bytes]; + if (sin6->sin6_family != AF_INET6) { + continue; + } + + if ([MDNSManager isLocalIpv6Address:addrData]) { + // Skip non-global addresses + continue; + } + + uint8_t* addrBytes = sin6->sin6_addr.s6_addr; + uint8_t prefix[2]; + + // 2002::/16 + prefix[0] = 0x20; + prefix[1] = 0x02; + if ([MDNSManager isAddress:addrBytes inSubnet:prefix netmask:16]) { + Log(LOG_I, @"Ignoring 6to4 address: %@", [MDNSManager sockAddrToString:addrData]); + continue; + } + + // 2001::/32 + prefix[0] = 0x20; + prefix[1] = 0x01; + if ([MDNSManager isAddress:addrBytes inSubnet:prefix netmask:32]) { + Log(LOG_I, @"Ignoring Teredo address: %@", [MDNSManager sockAddrToString:addrData]); + continue; + } + + return [MDNSManager sockAddrToString:addrData]; + } + + return nil; +} + - (void)netServiceDidResolveAddress:(NSNetService *)service { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - Log(LOG_D, @"Resolved address: %@ -> %@", service, service.hostName); + NSArray* addresses = [service addresses]; + + for (NSData* addrData in addresses) { + Log(LOG_I, @"Resolved address: %@ -> %@", [service hostName], [MDNSManager sockAddrToString: addrData]); + } + TemporaryHost* host = [[TemporaryHost alloc] init]; + // First, look for an IPv4 record for the local address + for (NSData* addrData in addresses) { + struct sockaddr_in* sin = (struct sockaddr_in*)[addrData bytes]; + if (sin->sin_family != AF_INET) { + continue; + } + + host.localAddress = [MDNSManager sockAddrToString:addrData]; + Log(LOG_I, @"Local address chosen: %@ -> %@", [service hostName], host.localAddress); + break; + } + + if (host.localAddress == nil) { + // If we didn't find an IPv4 record, look for a local IPv6 record + for (NSData* addrData in addresses) { + if ([MDNSManager isLocalIpv6Address:addrData]) { + host.localAddress = [MDNSManager sockAddrToString:addrData]; + Log(LOG_I, @"Local address chosen: %@ -> %@", [service hostName], host.localAddress); + break; + } + } + } + + host.ipv6Address = [MDNSManager getBestIpv6Address:addresses]; + Log(LOG_I, @"IPv6 address chosen: %@ -> %@", [service hostName], host.ipv6Address); + // Since we discovered this host over mDNS, we know we're on the same network // as the PC and we can use our current WAN address as a likely candidate // for our PC's external address. @@ -82,7 +208,7 @@ static NSString* NV_SERVICE_TYPE = @"_nvstream._tcp"; Log(LOG_E, @"STUN failed to get WAN address: %d", err); } - host.activeAddress = host.localAddress = service.hostName; + host.activeAddress = host.localAddress; host.name = service.hostName; [self.callback updateHost:host]; }); diff --git a/Limelight/Network/WakeOnLanManager.m b/Limelight/Network/WakeOnLanManager.m index 91c7e67..afb2a72 100644 --- a/Limelight/Network/WakeOnLanManager.m +++ b/Limelight/Network/WakeOnLanManager.m @@ -33,7 +33,7 @@ static const int ports[numPorts] = {7, 9, 47998, 47999, 48000}; + (void) wakeHost:(TemporaryHost*)host { NSData* wolPayload = [WakeOnLanManager createPayload:host]; - for (int i = 0; i < 4; i++) { + for (int i = 0; i < 5; i++) { const char* address; struct addrinfo hints, *res, *curr; @@ -44,7 +44,9 @@ static const int ports[numPorts] = {7, 9, 47998, 47999, 48000}; address = [host.externalAddress UTF8String]; } else if (i == 2 && host.address != nil) { address = [host.address UTF8String]; - } else if (i == 3) { + } else if (i == 3 && host.ipv6Address != nil) { + address = [host.ipv6Address UTF8String]; + } else if (i == 4) { address = "255.255.255.255"; } else { // Requested address wasn't present diff --git a/Limelight/ViewControllers/MainFrameViewController.m b/Limelight/ViewControllers/MainFrameViewController.m index 3f8a965..c15a700 100644 --- a/Limelight/ViewControllers/MainFrameViewController.m +++ b/Limelight/ViewControllers/MainFrameViewController.m @@ -861,6 +861,9 @@ static NSMutableSet* hostList; if (host.activeAddress == nil) { host.activeAddress = host.address; } + if (host.activeAddress == nil) { + host.activeAddress = host.ipv6Address; + } } } } @@ -869,7 +872,7 @@ static NSMutableSet* hostList; dispatch_async(dispatch_get_main_queue(), ^{ Log(LOG_D, @"New host list:"); for (TemporaryHost* host in hosts) { - Log(LOG_D, @"Host: \n{\n\t name:%@ \n\t address:%@ \n\t localAddress:%@ \n\t externalAddress:%@ \n\t uuid:%@ \n\t mac:%@ \n\t pairState:%d \n\t online:%d \n\t activeAddress:%@ \n}", host.name, host.address, host.localAddress, host.externalAddress, host.uuid, host.mac, host.pairState, host.online, host.activeAddress); + Log(LOG_D, @"Host: \n{\n\t name:%@ \n\t address:%@ \n\t localAddress:%@ \n\t externalAddress:%@ \n\t ipv6Address:%@ \n\t uuid:%@ \n\t mac:%@ \n\t pairState:%d \n\t online:%d \n\t activeAddress:%@ \n}", host.name, host.address, host.localAddress, host.externalAddress, host.ipv6Address, host.uuid, host.mac, host.pairState, host.online, host.activeAddress); } @synchronized(hostList) { [hostList removeAllObjects]; diff --git a/Moonlight.xcodeproj/project.pbxproj b/Moonlight.xcodeproj/project.pbxproj index ba89ca0..b9aac21 100644 --- a/Moonlight.xcodeproj/project.pbxproj +++ b/Moonlight.xcodeproj/project.pbxproj @@ -166,6 +166,7 @@ 9832D1341BBCD5C50036EF48 /* TemporaryApp.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = TemporaryApp.h; path = Database/TemporaryApp.h; sourceTree = ""; }; 9832D1351BBCD5C50036EF48 /* TemporaryApp.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = TemporaryApp.m; path = Database/TemporaryApp.m; sourceTree = ""; }; 98517B1B21CE0A9000481377 /* Moonlight v1.3.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Moonlight v1.3.xcdatamodel"; sourceTree = ""; }; + 98608BDD22DC0C2C000E5672 /* Moonlight v1.4.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Moonlight v1.4.xcdatamodel"; sourceTree = ""; }; 9865DC3B2132922E0005B9B9 /* GameController.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = GameController.framework; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS11.4.sdk/System/Library/Frameworks/GameController.framework; sourceTree = DEVELOPER_DIR; }; 986CCE6C2133E45300168291 /* Moonlight v1.2.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Moonlight v1.2.xcdatamodel"; sourceTree = ""; }; 98878AE0206A226D00586E90 /* OSPortabilityDefs.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OSPortabilityDefs.h; sourceTree = ""; }; @@ -1509,6 +1510,7 @@ FB290D0519B2C406004C83CF /* Limelight.xcdatamodeld */ = { isa = XCVersionGroup; children = ( + 98608BDD22DC0C2C000E5672 /* Moonlight v1.4.xcdatamodel */, 98517B1B21CE0A9000481377 /* Moonlight v1.3.xcdatamodel */, 986CCE6C2133E45300168291 /* Moonlight v1.2.xcdatamodel */, 98132E8C20BC9A62007A053F /* Moonlight v1.1.xcdatamodel */, @@ -1518,7 +1520,7 @@ FB4678F21A51BDCB00377732 /* Limelight 0.3.0.xcdatamodel */, FB290D0619B2C406004C83CF /* Limelight.xcdatamodel */, ); - currentVersion = 98517B1B21CE0A9000481377 /* Moonlight v1.3.xcdatamodel */; + currentVersion = 98608BDD22DC0C2C000E5672 /* Moonlight v1.4.xcdatamodel */; path = Limelight.xcdatamodeld; sourceTree = ""; versionGroupType = wrapper.xcdatamodel;