<Compile Include="UdpConnectionTests.cs" />
<Compile Include="MessageWriterTests.cs" />
<Compile Include="StressTests.cs" />
+ <Compile Include="UPnPTests.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Hazel\Hazel.csproj">
--- /dev/null
+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);
+ }
+ }
+}
/// <threadsafety static="true" instance="true"/>
public class ConnectionStatistics
{
+ private const int ExpectedMTU = 1200;
+
/// <summary>
/// The total number of messages sent.
/// </summary>
Interlocked.Add(ref dataBytesSent, dataLength);
Interlocked.Add(ref totalBytesSent, totalLength);
- if (totalLength > 576)
+ if (totalLength > ExpectedMTU)
{
Interlocked.Increment(ref fragmentableMessagesSent);
}
Interlocked.Add(ref dataBytesSent, dataLength);
Interlocked.Add(ref totalBytesSent, totalLength);
- if (totalLength > 1400)
+ if (totalLength > ExpectedMTU)
{
Interlocked.Increment(ref fragmentableMessagesSent);
}
Interlocked.Add(ref dataBytesSent, dataLength);
Interlocked.Add(ref totalBytesSent, totalLength);
- if (totalLength > 1400)
+ if (totalLength > ExpectedMTU)
{
Interlocked.Increment(ref fragmentableMessagesSent);
}
<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.
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>
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>
--- /dev/null
+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);
+ }
+}
--- /dev/null
+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
--- /dev/null
+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
}
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);
}
}
}
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);
}
}
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)
if (msg.Length == 0)
{
msg.Recycle();
- Disconnect("Received 0 bytes");
+ DisconnectInternal(HazelInternalErrors.ReceivedZeroBytes, "Received 0 bytes");
return;
}
}
catch (SocketException e)
{
- Disconnect("Socket exception during receive: " + e.Message);
+ DisconnectInternal(HazelInternalErrors.SocketExceptionReceive, "Socket exception during receive: " + e.Message);
}
catch (ObjectDisposedException)
{
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;
}
{
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();
}
{
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();
}
}
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");
}
}
}
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)
{
message.Recycle();
return;
}
- catch (InvalidOperationException) { return; } // Callback called twice, somehow...
catch (SocketException sx)
{
// Client no longer reachable, pretend it didn't happen
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()
{
}
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);
}
}
}
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);
}
}
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)
if (msg.Length == 0)
{
msg.Recycle();
- Disconnect("Received 0 bytes");
+ DisconnectInternal(HazelInternalErrors.ReceivedZeroBytes, "Received 0 bytes");
return;
}
}
catch (SocketException e)
{
- Disconnect("Socket exception during receive: " + e.Message);
+ DisconnectInternal(HazelInternalErrors.SocketExceptionReceive, "Socket exception during receive: " + e.Message);
}
catch (ObjectDisposedException)
{