]> git.deb.at Git - rhonda/impostor.hazel.git/commitdiff
Add some UPnP capabilities, add an intercept for internal disconnect messages
authorForest <forest@innersloth.com>
Sat, 11 Jan 2020 23:37:26 +0000 (15:37 -0800)
committerForest <forest@innersloth.com>
Sat, 11 Jan 2020 23:37:54 +0000 (15:37 -0800)
13 files changed:
Hazel.UnitTests/Hazel.UnitTests.csproj
Hazel.UnitTests/UPnPTests.cs [new file with mode: 0644]
Hazel/ConnectionStatistics.cs
Hazel/Hazel.csproj
Hazel/NetworkConnection.cs
Hazel/UPnP/ILogger.cs [new file with mode: 0644]
Hazel/UPnP/NetUtility.cs [new file with mode: 0644]
Hazel/UPnP/UPnPHelper.cs [new file with mode: 0644]
Hazel/Udp/UdpClientConnection.cs
Hazel/Udp/UdpConnection.KeepAlive.cs
Hazel/Udp/UdpConnection.Reliable.cs
Hazel/Udp/UdpConnectionListener.cs
Hazel/Udp/UnityUdpClientConnection.cs

index 88f9a7388c2494408be926a97a47407dec139d94..b2e55e30f06c855818923f3b583fe06333256907 100644 (file)
@@ -64,6 +64,7 @@
     <Compile Include="UdpConnectionTests.cs" />
     <Compile Include="MessageWriterTests.cs" />
     <Compile Include="StressTests.cs" />
+    <Compile Include="UPnPTests.cs" />
   </ItemGroup>
   <ItemGroup>
     <ProjectReference Include="..\Hazel\Hazel.csproj">
diff --git a/Hazel.UnitTests/UPnPTests.cs b/Hazel.UnitTests/UPnPTests.cs
new file mode 100644 (file)
index 0000000..460740d
--- /dev/null
@@ -0,0 +1,43 @@
+using System;
+using Hazel.UPnP;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace Hazel.UnitTests
+{
+    [TestClass]
+    public class UPnPTests
+    {
+        [TestMethod]
+        public void CanForwardPort()
+        {
+            using (UPnPHelper dut = new UPnPHelper(Logger.Instance))
+            {
+                Assert.IsTrue(dut.ForwardPort(22023, "Hazel Test"));
+            }
+        }
+
+        [TestMethod]
+        public void CanDeletePort()
+        {
+            using (UPnPHelper dut = new UPnPHelper(Logger.Instance))
+            {
+                Assert.IsTrue(dut.DeleteForwardingRule(22023));
+            }
+        }
+    }
+
+    public class Logger : ILogger
+    {
+        public static readonly ILogger Instance = new Logger();
+
+        public void LogError(string msg)
+        {
+            Console.WriteLine(msg);
+        }
+
+        public void LogInfo(string msg)
+        {
+            Console.WriteLine(msg);
+        }
+    }
+}
index 1e90105aa0420581a6db17868634d8c5e47e51aa..32ad0a021b062e6dab0d9c4c8a07f05bf4193b7f 100644 (file)
@@ -13,6 +13,8 @@ namespace Hazel
     /// <threadsafety static="true" instance="true"/>
     public class ConnectionStatistics
     {
+        private const int ExpectedMTU = 1200;
+
         /// <summary>
         ///     The total number of messages sent.
         /// </summary>
@@ -403,7 +405,7 @@ namespace Hazel
             Interlocked.Add(ref dataBytesSent, dataLength);
             Interlocked.Add(ref totalBytesSent, totalLength);
 
-            if (totalLength > 576)
+            if (totalLength > ExpectedMTU)
             {
                 Interlocked.Increment(ref fragmentableMessagesSent);
             }
@@ -423,7 +425,7 @@ namespace Hazel
             Interlocked.Add(ref dataBytesSent, dataLength);
             Interlocked.Add(ref totalBytesSent, totalLength);
 
-            if (totalLength > 1400)
+            if (totalLength > ExpectedMTU)
             {
                 Interlocked.Increment(ref fragmentableMessagesSent);
             }
@@ -443,7 +445,7 @@ namespace Hazel
             Interlocked.Add(ref dataBytesSent, dataLength);
             Interlocked.Add(ref totalBytesSent, totalLength);
 
-            if (totalLength > 1400)
+            if (totalLength > ExpectedMTU)
             {
                 Interlocked.Increment(ref fragmentableMessagesSent);
             }
index 07937143811987d340127e04eb025024b75adc07..7e81a65230acdb6812abd1649bb9dca5b52f1ae3 100644 (file)
     <Compile Include="Udp\UdpConnection.Reliable.cs" />
     <Compile Include="Udp\UdpConnectionListener.cs" />
     <Compile Include="Udp\UdpServerConnection.cs" />
+    <Compile Include="UPnP\ILogger.cs" />
+    <Compile Include="UPnP\NetUtility.cs" />
+    <Compile Include="UPnP\UPnPHelper.cs" />
   </ItemGroup>
+  <ItemGroup />
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
   <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
        Other similar extension points exist, see Microsoft.Common.targets.
index eb63fd2ae2935f0dd448926dcf8a6a44707477c6..a76bb9c13c3c1c6eed6a8ac6a329bb9773db0eba 100644 (file)
@@ -7,12 +7,27 @@ using System.Text;
 
 namespace Hazel
 {
+    public enum HazelInternalErrors
+    {
+        SocketExceptionSend,
+        SocketExceptionReceive,
+        ReceivedZeroBytes,
+        PingsWithoutResponse,
+        ReliablePacketWithoutResponse,
+        ConnectionDisconnected
+    }
+
     /// <summary>
     ///     Abstract base class for a <see cref="Connection"/> to a remote end point via a network protocol like TCP or UDP.
     /// </summary>
     /// <threadsafety static="true" instance="true"/>
     public abstract class NetworkConnection : Connection
     {
+        /// <summary>
+        /// An event that gives us a chance to send well-formed disconnect messages to clients when an internal disconnect happens.
+        /// </summary>
+        public Func<HazelInternalErrors, MessageWriter> OnInternalDisconnect;
+
         /// <summary>
         ///     The remote end point of this connection.
         /// </summary>
@@ -57,6 +72,37 @@ namespace Hazel
             this.Dispose();
         }
 
+        /// <summary>
+        /// Called when socket is disconnected internally
+        /// </summary>
+        internal void DisconnectInternal(HazelInternalErrors error, string reason)
+        {
+            var handler = this.OnInternalDisconnect;
+            if (handler != null)
+            {
+                MessageWriter messageToRemote = handler(error);
+                if (messageToRemote != null)
+                {
+                    try
+                    {
+                        Disconnect(reason, messageToRemote);
+                    }
+                    finally
+                    {
+                        messageToRemote.Recycle();
+                    }
+                }
+                else
+                {
+                    Disconnect(reason);
+                }
+            }
+            else
+            {
+                Disconnect(reason);
+            }
+        }
+
         /// <summary>
         ///     Called when the socket has been disconnected locally.
         /// </summary>
diff --git a/Hazel/UPnP/ILogger.cs b/Hazel/UPnP/ILogger.cs
new file mode 100644 (file)
index 0000000..3a217e1
--- /dev/null
@@ -0,0 +1,14 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Hazel.UPnP
+{
+    public interface ILogger
+    {
+        void LogInfo(string msg);
+        void LogError(string msg);
+    }
+}
diff --git a/Hazel/UPnP/NetUtility.cs b/Hazel/UPnP/NetUtility.cs
new file mode 100644 (file)
index 0000000..a6a0c5c
--- /dev/null
@@ -0,0 +1,101 @@
+using System;
+using System.Net;
+using System.Net.NetworkInformation;
+using System.Net.Sockets;
+
+namespace Hazel.UPnP
+{
+    internal class NetUtility
+    {
+        private static NetworkInterface GetNetworkInterface()
+        {
+            var computerProperties = IPGlobalProperties.GetIPGlobalProperties();
+            if (computerProperties == null)
+                return null;
+
+            var nics = NetworkInterface.GetAllNetworkInterfaces();
+            if (nics == null || nics.Length < 1)
+                return null;
+
+            NetworkInterface best = null;
+            foreach (NetworkInterface adapter in nics)
+            {
+                if (adapter.NetworkInterfaceType == NetworkInterfaceType.Loopback || adapter.NetworkInterfaceType == NetworkInterfaceType.Unknown)
+                    continue;
+                if (!adapter.Supports(NetworkInterfaceComponent.IPv4))
+                    continue;
+                if (best == null)
+                    best = adapter;
+                if (adapter.OperationalStatus != OperationalStatus.Up)
+                    continue;
+
+                // make sure this adapter has any ipv4 addresses
+                IPInterfaceProperties properties = adapter.GetIPProperties();
+                foreach (UnicastIPAddressInformation unicastAddress in properties.UnicastAddresses)
+                {
+                    if (unicastAddress != null && unicastAddress.Address != null && unicastAddress.Address.AddressFamily == AddressFamily.InterNetwork)
+                    {
+                        // Yes it does, return this network interface.
+                        return adapter;
+                    }
+                }
+            }
+            return best;
+        }
+
+        /// <summary>
+               /// Gets my local IPv4 address (not necessarily external) and subnet mask
+               /// </summary>
+               public static IPAddress GetMyAddress(out IPAddress mask)
+        {
+            var ni = GetNetworkInterface();
+            if (ni == null)
+            {
+                mask = null;
+                return null;
+            }
+
+            IPInterfaceProperties properties = ni.GetIPProperties();
+            foreach (UnicastIPAddressInformation unicastAddress in properties.UnicastAddresses)
+            {
+                if (unicastAddress != null && unicastAddress.Address != null && unicastAddress.Address.AddressFamily == AddressFamily.InterNetwork)
+                {
+                    mask = unicastAddress.IPv4Mask;
+                    return unicastAddress.Address;
+                }
+            }
+
+            mask = null;
+            return null;
+        }
+
+        public static IPAddress GetBroadcastAddress()
+        {
+            var ni = GetNetworkInterface();
+            if (ni == null)
+                return null;
+
+            var properties = ni.GetIPProperties();
+            foreach (UnicastIPAddressInformation unicastAddress in properties.UnicastAddresses)
+            {
+                if (unicastAddress != null && unicastAddress.Address != null && unicastAddress.Address.AddressFamily == AddressFamily.InterNetwork)
+                {
+                    var mask = unicastAddress.IPv4Mask;
+                    byte[] ipAdressBytes = unicastAddress.Address.GetAddressBytes();
+                    byte[] subnetMaskBytes = mask.GetAddressBytes();
+
+                    if (ipAdressBytes.Length != subnetMaskBytes.Length)
+                        throw new ArgumentException("Lengths of IP address and subnet mask do not match.");
+
+                    byte[] broadcastAddress = new byte[ipAdressBytes.Length];
+                    for (int i = 0; i < broadcastAddress.Length; i++)
+                    {
+                        broadcastAddress[i] = (byte)(ipAdressBytes[i] | (subnetMaskBytes[i] ^ 255));
+                    }
+                    return new IPAddress(broadcastAddress);
+                }
+            }
+            return IPAddress.Broadcast;
+        }
+    }
+}
\ No newline at end of file
diff --git a/Hazel/UPnP/UPnPHelper.cs b/Hazel/UPnP/UPnPHelper.cs
new file mode 100644 (file)
index 0000000..506ac70
--- /dev/null
@@ -0,0 +1,347 @@
+using System;
+using System.IO;
+using System.Xml;
+using System.Net;
+using System.Net.Sockets;
+using System.Threading;
+
+namespace Hazel.UPnP
+{
+    /// <summary>
+    /// Status of the UPnP capabilities
+    /// </summary>
+    public enum UPnPStatus
+    {
+        /// <summary>
+        /// Still discovering UPnP capabilities
+        /// </summary>
+        Discovering,
+
+        /// <summary>
+        /// UPnP is not available
+        /// </summary>
+        NotAvailable,
+
+        /// <summary>
+        /// UPnP is available and ready to use
+        /// </summary>
+        Available
+    }
+
+    public class UPnPHelper : IDisposable
+    {
+        private const int DiscoveryTimeOutMs = 1000;
+
+        private string serviceUrl;
+        private string serviceName = "";
+        
+        private ManualResetEvent discoveryComplete = new ManualResetEvent(false);
+        private Socket socket;
+
+        private DateTime discoveryResponseDeadline;
+
+        private EndPoint ep;
+        private byte[] buffer;
+
+        private ILogger logger;
+
+        /// <summary>
+        /// Status of the UPnP capabilities of this NetPeer
+        /// </summary>
+        public UPnPStatus Status { get; private set; }
+
+        public UPnPHelper(ILogger logger)
+        {
+            this.logger = logger;
+
+            this.socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
+            this.socket.EnableBroadcast = true;
+            this.socket.MulticastLoopback = false;
+
+            this.socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, 1);
+            this.socket.Bind(new IPEndPoint(IPAddress.Any, 0));
+
+            this.ep = new IPEndPoint(IPAddress.Any, 1900);
+            this.buffer = new byte[ushort.MaxValue];
+
+            ListenForUPnP();
+
+            this.Discover();
+        }
+
+        private void ListenForUPnP()
+        {
+            try
+            {
+                socket.BeginReceiveFrom(this.buffer, 0, this.buffer.Length, SocketFlags.None, ref ep, HandleMessage, null);
+            }
+            catch(Exception e)
+            {
+                this.logger.LogInfo("Exception listening for UPnP: " + e.Message);
+            }
+        }
+
+        private void HandleMessage(IAsyncResult ar)
+        {
+            int len;
+            try
+            {
+                len = this.socket.EndReceiveFrom(ar, ref ep);
+            }
+            catch
+            {
+                return;
+            }
+
+            string resp = System.Text.Encoding.UTF8.GetString(buffer, 0, len);
+            if (resp.Contains("upnp:rootdevice") || resp.Contains("UPnP/1.0"))
+            {
+                var locationStart = resp.IndexOf("location:", StringComparison.OrdinalIgnoreCase);
+                if (locationStart >= 0)
+                {
+                    locationStart += 10;
+                    var locationEnd = resp.IndexOf("\r", locationStart);
+
+                    resp = resp.Substring(locationStart, locationEnd - locationStart);
+                    if (!ExtractServiceUrl(resp))
+                    {
+                        ListenForUPnP();
+                    }
+                }
+                else
+                {
+                    ListenForUPnP();
+                }
+            }
+            else
+            {
+                ListenForUPnP();
+            }
+        }
+
+        internal void Discover()
+        {
+            string str =
+"M-SEARCH * HTTP/1.1\r\n" +
+"HOST: 239.255.255.250:1900\r\n" +
+"ST:upnp:rootdevice\r\n" +
+"MAN:\"ssdp:discover\"\r\n" +
+"MX:3\r\n\r\n";
+
+            discoveryResponseDeadline = DateTime.UtcNow.AddSeconds(6);
+            Status = UPnPStatus.Discovering;
+
+            byte[] buffer = System.Text.Encoding.UTF8.GetBytes(str);
+
+            this.logger.LogInfo("Attempting UPnP discovery");
+
+            socket.SendTo(buffer, new IPEndPoint(NetUtility.GetBroadcastAddress(), 1900));
+        }
+
+        internal bool ExtractServiceUrl(string resp)
+        {
+            try
+            {
+                XmlDocument desc = new XmlDocument();
+                using (var response = WebRequest.Create(resp).GetResponse())
+                {
+                    desc.Load(response.GetResponseStream());
+                }
+
+                XmlNamespaceManager nsMgr = new XmlNamespaceManager(desc.NameTable);
+                nsMgr.AddNamespace("tns", "urn:schemas-upnp-org:device-1-0");
+                XmlNode typen = desc.SelectSingleNode("//tns:device/tns:deviceType/text()", nsMgr);
+                if (!typen.Value.Contains("InternetGatewayDevice"))
+                    return false;
+
+                serviceName = "WANIPConnection";
+                XmlNode node = desc.SelectSingleNode("//tns:service[tns:serviceType=\"urn:schemas-upnp-org:service:" + serviceName + ":1\"]/tns:controlURL/text()", nsMgr);
+                if (node == null)
+                {
+                    //try another service name
+                    serviceName = "WANPPPConnection";
+                    node = desc.SelectSingleNode("//tns:service[tns:serviceType=\"urn:schemas-upnp-org:service:" + serviceName + ":1\"]/tns:controlURL/text()", nsMgr);
+                    if (node == null)
+                        return false;
+                }
+
+                serviceUrl = CombineUrls(resp, node.Value);
+                this.logger.LogInfo("UPnP service ready");
+                Status = UPnPStatus.Available;
+                discoveryComplete.Set();
+                return true;
+            }
+            catch (Exception e)
+            {
+                this.logger.LogError("Exception while parsing UPnP Service URL: " + e.Message);
+                return false;
+            }
+        }
+
+        private static string CombineUrls(string gatewayURL, string subURL)
+        {
+            // Is Control URL an absolute URL?
+            if (subURL.Contains("http:") || subURL.Contains("."))
+                return subURL;
+
+            gatewayURL = gatewayURL.Replace("http://", "");  // strip any protocol
+            int n = gatewayURL.IndexOf("/");
+            if (n >= 0)
+            {
+                gatewayURL = gatewayURL.Substring(0, n);  // Use first portion of URL
+            }
+
+            return "http://" + gatewayURL + subURL;
+        }
+
+        private bool CheckAvailability()
+        {
+            switch (Status)
+            {
+                case UPnPStatus.NotAvailable:
+                    return false;
+                case UPnPStatus.Available:
+                    return true;
+                case UPnPStatus.Discovering:
+                    while (!discoveryComplete.WaitOne(DiscoveryTimeOutMs))
+                    {
+                        if (DateTime.UtcNow > discoveryResponseDeadline)
+                        {
+                            Status = UPnPStatus.NotAvailable;
+                            return false;
+                        }
+                    }
+
+                    return true;
+            }
+
+            return false;
+        }
+
+        /// <summary>
+        /// Add a forwarding rule to the router using UPnP
+        /// </summary>
+        /// <param name="externalPort">The external, WAN facing, port</param>
+        /// <param name="description">A description for the port forwarding rule</param>
+        /// <param name="internalPort">The port on the client machine to send traffic to</param>
+        /// <param name="durationSeconds">The lease duration on the port forwarding rule, in seconds. 0 for indefinite.</param>
+        public bool ForwardPort(int externalPort, string description, int internalPort = 0, int durationSeconds = 0)
+        {
+            if (!CheckAvailability())
+                return false;
+
+            if (internalPort == 0)
+                internalPort = externalPort;
+
+            try
+            {
+                var client = NetUtility.GetMyAddress(out _);
+                if (client == null)
+                    return false;
+
+                SOAPRequest(serviceUrl,
+                    $"<u:AddPortMapping xmlns:u=\"urn:schemas-upnp-org:service:{serviceName}:1\">" +
+                    "<NewRemoteHost></NewRemoteHost>" +
+                    $"<NewExternalPort>{externalPort}</NewExternalPort>" +
+                    "<NewProtocol>UDP</NewProtocol>" +
+                    $"<NewInternalPort>{internalPort}</NewInternalPort>" +
+                    $"<NewInternalClient>{client}</NewInternalClient>" +
+                    "<NewEnabled>1</NewEnabled>" +
+                    $"<NewPortMappingDescription>{description}</NewPortMappingDescription>" +
+                    $"<NewLeaseDuration>{durationSeconds}</NewLeaseDuration>" +
+                    "</u:AddPortMapping>",
+                    "AddPortMapping");
+
+                this.logger.LogInfo("Sent UPnP port forward request.");
+                return true;
+            }
+            catch (Exception ex)
+            {
+                this.logger.LogError("UPnP port forward failed: " + ex.Message);
+                return false;
+            }
+        }
+
+        /// <summary>
+        /// Delete a forwarding rule from the router using UPnP
+        /// </summary>
+        /// <param name="externalPort">The external, 'internet facing', port</param>
+        public bool DeleteForwardingRule(int externalPort)
+        {
+            if (!CheckAvailability())
+                return false;
+
+            try
+            {
+                SOAPRequest(serviceUrl,
+                $"<u:DeletePortMapping xmlns:u=\"urn:schemas-upnp-org:service:{serviceName}:1\">" +
+                "<NewRemoteHost></NewRemoteHost>" +
+                $"<NewExternalPort>{externalPort}</NewExternalPort>" +
+                $"<NewProtocol>UDP</NewProtocol>" +
+                "</u:DeletePortMapping>", "DeletePortMapping");
+                return true;
+            }
+            catch (Exception ex)
+            {
+                // m_peer.LogWarning("UPnP delete forwarding rule failed: " + ex.Message);
+                return false;
+            }
+        }
+
+        /// <summary>
+        /// Retrieve the extern ip using UPnP
+        /// </summary>
+        public IPAddress GetExternalIP()
+        {
+            if (!CheckAvailability())
+                return null;
+            try
+            {
+                XmlDocument xdoc = SOAPRequest(serviceUrl, "<u:GetExternalIPAddress xmlns:u=\"urn:schemas-upnp-org:service:" + serviceName + ":1\">" +
+                "</u:GetExternalIPAddress>", "GetExternalIPAddress");
+                XmlNamespaceManager nsMgr = new XmlNamespaceManager(xdoc.NameTable);
+                nsMgr.AddNamespace("tns", "urn:schemas-upnp-org:device-1-0");
+                string IP = xdoc.SelectSingleNode("//NewExternalIPAddress/text()", nsMgr).Value;
+                return IPAddress.Parse(IP);
+            }
+            catch (Exception ex)
+            {
+                // m_peer.LogWarning("Failed to get external IP: " + ex.Message);
+                return null;
+            }
+        }
+
+        private XmlDocument SOAPRequest(string url, string soap, string function)
+        {
+            string req = 
+"<?xml version=\"1.0\"?>" +
+"<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">" +
+$"<s:Body>{soap}</s:Body>" +
+"</s:Envelope>";
+
+            WebRequest r = HttpWebRequest.Create(url);
+            r.Headers.Add("SOAPACTION", $"\"urn:schemas-upnp-org:service:{serviceName}:1#{function}\"");
+            r.ContentType = "text/xml; charset=\"utf-8\"";
+            r.Method = "POST";
+
+            byte[] b = System.Text.Encoding.UTF8.GetBytes(req);
+            r.ContentLength = b.Length;
+            r.GetRequestStream().Write(b, 0, b.Length);
+
+            using (WebResponse wres = r.GetResponse())
+            {
+                XmlDocument resp = new XmlDocument();
+                Stream ress = wres.GetResponseStream();
+                resp.Load(ress);
+                return resp;
+            }
+        }
+
+        public void Dispose()
+        {
+            this.discoveryComplete.Dispose();
+            try { this.socket.Shutdown(SocketShutdown.Both); } catch { }
+            this.socket.Dispose();
+        }
+    }
+}
\ No newline at end of file
index 1b3a22bf20aa7d6b160d07c2a8ad7ba6c9c37df4..de40a39d6c034e536e9475509269f1291107d05c 100644 (file)
@@ -99,7 +99,7 @@ namespace Hazel.Udp
             }
             catch (SocketException ex)
             {
-                Disconnect("Could not send data as a SocketException occurred: " + ex.Message);
+                DisconnectInternal(HazelInternalErrors.SocketExceptionSend, "Could not send data as a SocketException occurred: " + ex.Message);
             }
         }
 
@@ -116,7 +116,7 @@ namespace Hazel.Udp
             }
             catch (SocketException ex)
             {
-                Disconnect("Could not send data as a SocketException occurred: " + ex.Message);
+                DisconnectInternal(HazelInternalErrors.SocketExceptionSend, "Could not send data as a SocketException occurred: " + ex.Message);
             }
         }
 
@@ -236,7 +236,7 @@ namespace Hazel.Udp
             catch (SocketException e)
             {
                 msg.Recycle();
-                Disconnect("Socket exception while reading data: " + e.Message);
+                DisconnectInternal(HazelInternalErrors.SocketExceptionReceive, "Socket exception while reading data: " + e.Message);
                 return;
             }
             catch (Exception)
@@ -249,7 +249,7 @@ namespace Hazel.Udp
             if (msg.Length == 0)
             {
                 msg.Recycle();
-                Disconnect("Received 0 bytes");
+                DisconnectInternal(HazelInternalErrors.ReceivedZeroBytes, "Received 0 bytes");
                 return;
             }
 
@@ -260,7 +260,7 @@ namespace Hazel.Udp
             }
             catch (SocketException e)
             {
-                Disconnect("Socket exception during receive: " + e.Message);
+                DisconnectInternal(HazelInternalErrors.SocketExceptionReceive, "Socket exception during receive: " + e.Message);
             }
             catch (ObjectDisposedException)
             {
index 71babf07399a7a409d5f31ad68d948c9e714298d..69ceac21abd77a55a89f318af560c40378c40e2f 100644 (file)
@@ -79,7 +79,7 @@ namespace Hazel.Udp
                     if (this.pingsSinceAck >= this.MissingPingsUntilDisconnect)
                     {
                         this.DisposeKeepAliveTimer();
-                        this.Disconnect($"Sent {this.pingsSinceAck} pings that remote has not responded to.");
+                        this.DisconnectInternal(HazelInternalErrors.PingsWithoutResponse, $"Sent {this.pingsSinceAck} pings that remote has not responded to.");
                         return;
                     }
 
index 303d6705716bc205136f204505520a6f554054b0..5a7c440a6b6482a71d34e8243debb94d60429a48 100644 (file)
@@ -147,7 +147,7 @@ namespace Hazel.Udp
                     {
                         if (connection.reliableDataPacketsSent.TryRemove(this.Id, out Packet self))
                         {
-                            connection.Disconnect($"Reliable packet {self.Id} (size={this.Length}) was not ack'd after {lifetime}ms ({self.Retransmissions} resends)");
+                            connection.DisconnectInternal(HazelInternalErrors.ReliablePacketWithoutResponse, $"Reliable packet {self.Id} (size={this.Length}) was not ack'd after {lifetime}ms ({self.Retransmissions} resends)");
 
                             self.Recycle();
                         }
@@ -163,7 +163,7 @@ namespace Hazel.Udp
                         {
                             if (connection.reliableDataPacketsSent.TryRemove(this.Id, out Packet self))
                             {
-                                connection.Disconnect($"Reliable packet {self.Id} (size={this.Length}) was not ack'd after {self.Retransmissions} resends ({lifetime}ms)");
+                                connection.DisconnectInternal(HazelInternalErrors.ReliablePacketWithoutResponse, $"Reliable packet {self.Id} (size={this.Length}) was not ack'd after {self.Retransmissions} resends ({lifetime}ms)");
 
                                 self.Recycle();
                             }
@@ -180,7 +180,7 @@ namespace Hazel.Udp
                         }
                         catch (InvalidOperationException)
                         {
-                            connection.Disconnect("Could not resend data as connection is no longer connected");
+                            connection.DisconnectInternal(HazelInternalErrors.ConnectionDisconnected, "Could not resend data as connection is no longer connected");
                         }
                     }
                 }
index 920497bcd09eb8216d97aaf0bc60779747badb5e..03bc5df04b42ccb0c7757c477ee16ddd46189b2d 100644 (file)
@@ -95,12 +95,7 @@ namespace Hazel.Udp
             try
             {
                 message = MessageReader.GetSized(BufferSize);
-
-                var result = socket.BeginReceiveFrom(message.Buffer, 0, message.Buffer.Length, SocketFlags.None, ref remoteEP, ReadCallback, message);
-                if (result.CompletedSynchronously)
-                {
-                    this.Logger?.Invoke("Operation completed synchronously");
-                }
+                socket.BeginReceiveFrom(message.Buffer, 0, message.Buffer.Length, SocketFlags.None, ref remoteEP, ReadCallback, message);
             }
             catch (SocketException sx)
             {
@@ -139,7 +134,6 @@ namespace Hazel.Udp
                 message.Recycle();
                 return;
             }
-            catch (InvalidOperationException) { return; } // Callback called twice, somehow...
             catch (SocketException sx)
             {
                 // Client no longer reachable, pretend it didn't happen
index e067903fad590334fe6d154aafa2342e91fabf99..ff92ea58ae68e49ab5c9e0545292197ace97c2b4 100644 (file)
@@ -7,20 +7,14 @@ using System.Threading;
 namespace Hazel.Udp
 {
     /// <summary>
-    ///     Represents a client's connection to a server that uses the UDP protocol.
+    /// Unity doesn't always get along with thread pools well, so this interface will hopefully suit that case better.
+    /// Be very careful since this interface is likely unstable or actively changing
     /// </summary>
     /// <inheritdoc/>
     public class UnityUdpClientConnection : UdpConnection
     {
-        /// <summary>
-        ///     The socket we're connected via.
-        /// </summary>
         private Socket socket;
 
-        /// <summary>
-        ///     Creates a new UdpClientConnection.
-        /// </summary>
-        /// <param name="remoteEndPoint">A <see cref="NetworkEndPoint"/> to connect to.</param>
         public UnityUdpClientConnection(IPEndPoint remoteEndPoint, IPMode ipMode = IPMode.IPv4)
             : base()
         {
@@ -62,7 +56,7 @@ namespace Hazel.Udp
             }
             catch (SocketException ex)
             {
-                Disconnect("Could not send data as a SocketException occurred: " + ex.Message);
+                DisconnectInternal(HazelInternalErrors.SocketExceptionSend, "Could not send data as a SocketException occurred: " + ex.Message);
             }
         }
 
@@ -79,7 +73,7 @@ namespace Hazel.Udp
             }
             catch (SocketException ex)
             {
-                Disconnect("Could not send data as a SocketException occurred: " + ex.Message);
+                DisconnectInternal(HazelInternalErrors.SocketExceptionSend, "Could not send data as a SocketException occurred: " + ex.Message);
             }
         }
 
@@ -164,7 +158,7 @@ namespace Hazel.Udp
             catch (SocketException e)
             {
                 msg.Recycle();
-                Disconnect("Socket exception while reading data: " + e.Message);
+                DisconnectInternal(HazelInternalErrors.SocketExceptionReceive, "Socket exception while reading data: " + e.Message);
                 return;
             }
             catch (Exception)
@@ -177,7 +171,7 @@ namespace Hazel.Udp
             if (msg.Length == 0)
             {
                 msg.Recycle();
-                Disconnect("Received 0 bytes");
+                DisconnectInternal(HazelInternalErrors.ReceivedZeroBytes, "Received 0 bytes");
                 return;
             }
 
@@ -188,7 +182,7 @@ namespace Hazel.Udp
             }
             catch (SocketException e)
             {
-                Disconnect("Socket exception during receive: " + e.Message);
+                DisconnectInternal(HazelInternalErrors.SocketExceptionReceive, "Socket exception during receive: " + e.Message);
             }
             catch (ObjectDisposedException)
             {