// Copyright (c) Microsoft Corporation. // Copyright (c) Rafael Rivera. // Licensed under the MIT License. See LICENSE in the project root for license information. using System; using System.Linq; using System.Net; using System.Net.NetworkInformation; using System.Net.Sockets; using System.Runtime.InteropServices; using System.Threading; namespace HoloToolkit.Unity { /// <summary> /// Emulator Utility Class /// </summary> public static class XdeGuestLocator { [StructLayout(LayoutKind.Sequential, Pack = 4)] struct XdePeerHostIdentifier { public Guid GuestDiscoveryGUID; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)] public byte[] GuestMACAddress; public int PeerDiscoveryPort; } [StructLayout(LayoutKind.Sequential, Pack = 4)] struct XdePeerGuestIdentifier { public Guid GuestDiscoveryGUID; public int GuestTcpPort; public int GuestSvcVersion; } public static bool IsSearching { get; private set; } public static bool HasData { get; private set; } public static IPAddress GuestIpAddress { get; private set; } static XdeGuestLocator() { HasData = false; IsSearching = false; } public static void FindGuestAddressAsync() { if (IsSearching) { return; } ThreadPool.QueueUserWorkItem(FindGuestAddress); } private static void FindGuestAddress(object state) { IsSearching = true; HasData = false; GuestIpAddress = IPAddress.None; UnicastIPAddressInformation internalSwitchAddressInfo = null; try { internalSwitchAddressInfo = GetInternalSwitchAddressInfo(); } catch (Exception) { UnityEngine.Debug.LogError("Failed to locate internal switch adapter"); } if (internalSwitchAddressInfo != null) { using (var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp)) { try { // Bind to next available UDP port for a listen operation socket.Blocking = true; socket.ReceiveTimeout = (int)TimeSpan.FromSeconds(5).TotalMilliseconds; socket.Bind(new IPEndPoint(internalSwitchAddressInfo.Address, 0)); var localPort = (socket.LocalEndPoint as IPEndPoint).Port; // Send out a probe to 'devices' connected to the internal switch // listening on port 3553 (Microsoft Device Emulator specific) var broadcastAddress = GetBroadcastAddressForAddress(internalSwitchAddressInfo.Address, internalSwitchAddressInfo.IPv4Mask); var broadcastTarget = new IPEndPoint(broadcastAddress, 3553); // // WORKAROUND: We don't have easy access to WMI to go querying // for virtual machine information so we just cover finding // the first 255 potential candidates xx 00 - xx FF. // // It sounds like a lot but we're talking super tiny // payloads on an internal interface. It's very fast. // for (int i = 0; i <= 0xFF; i++) { var probe = GenerateProbe(localPort, i); socket.SendTo(probe, broadcastTarget); } // Return the endpoint information for the first 'device' that replies // (we don't necessarily care about the returned identifier info) var responseBytes = new byte[Marshal.SizeOf(typeof(XdePeerGuestIdentifier))]; EndPoint guestEndpoint = new IPEndPoint(broadcastAddress, 0); socket.ReceiveFrom(responseBytes, ref guestEndpoint); GuestIpAddress = (guestEndpoint as IPEndPoint).Address; HasData = true; } catch (SocketException) { // Do nothing, our probe went unanswered or failed } } } IsSearching = false; } private static UnicastIPAddressInformation GetInternalSwitchAddressInfo() { var internalSwitch = GetInternalNetworkSwitchInterface(); return internalSwitch.GetIPProperties().UnicastAddresses.Where(a => a.Address.AddressFamily == AddressFamily.InterNetwork).FirstOrDefault(); } private static NetworkInterface GetInternalNetworkSwitchInterface() { return NetworkInterface.GetAllNetworkInterfaces().Where(i => i.Name.Contains("Windows Phone Emulator")).FirstOrDefault(); } private static IPAddress GetBroadcastAddressForAddress(IPAddress address, IPAddress mask) { var addressInt = BitConverter.ToInt32(address.GetAddressBytes(), 0); var maskInt = BitConverter.ToInt32(mask.GetAddressBytes(), 0); return new IPAddress(BitConverter.GetBytes((addressInt | ~maskInt))); } private static byte[] GenerateProbe(int port, int machineIndex) { var identifier = new XdePeerHostIdentifier(); identifier.PeerDiscoveryPort = port; identifier.GuestDiscoveryGUID = new Guid("{963ef858-2efe-4eb4-8d2d-fed5408e6441}"); identifier.GuestMACAddress = new byte[] { 0x02, 0xDE, 0xDE, 0xDE, 0xDE, (byte)machineIndex }; return GetStructureBytes(identifier); } private static byte[] GetStructureBytes(object obj) { var bytes = new byte[Marshal.SizeOf(obj)]; var handle = GCHandle.Alloc(bytes, GCHandleType.Pinned); Marshal.StructureToPtr(obj, handle.AddrOfPinnedObject(), false); handle.Free(); return bytes; } } }