/// <summary> /// Internal constructor for UDP connections /// </summary> /// <param name="connectionInfo"></param> /// <param name="defaultSendReceiveOptions"></param> /// <param name="level"></param> /// <param name="listenForIncomingPackets"></param> /// <param name="existingConnection"></param> internal UDPConnection(ConnectionInfo connectionInfo, SendReceiveOptions defaultSendReceiveOptions, UDPOptions level, bool listenForIncomingPackets, UDPConnection existingConnection = null) : base(connectionInfo, defaultSendReceiveOptions) { if (connectionInfo.ConnectionType != ConnectionType.UDP) { throw new ArgumentException("Provided connectionType must be UDP.", "connectionInfo"); } if (NetworkComms.LoggingEnabled) { NetworkComms.Logger.Trace("Creating new UDPConnection with " + connectionInfo); } if (connectionInfo.ApplicationLayerProtocol == ApplicationLayerProtocolStatus.Disabled && level != UDPOptions.None) { throw new ArgumentException("If the application layer protocol has been disabled the provided UDPOptions can only be UDPOptions.None."); } ConnectionUDPOptions = level; if (listenForIncomingPackets && existingConnection != null) { throw new Exception("Unable to listen for incoming packets if an existing client has been provided. This is to prevent possible multiple accidently listens on the same client."); } if (existingConnection == null) { if (connectionInfo.RemoteIPEndPoint.Address.Equals(IPAddress.Any) || connectionInfo.RemoteIPEndPoint.Address.Equals(IPAddress.IPv6Any)) { #if WINDOWS_PHONE || NETFX_CORE //We are creating an unbound endPoint, this is currently the rogue UDP sender and listeners only socket = new DatagramSocket(); if (listenForIncomingPackets) { socket.MessageReceived += socket_MessageReceived; } socket.BindEndpointAsync(new HostName(ConnectionInfo.LocalIPEndPoint.Address.ToString()), ConnectionInfo.LocalIPEndPoint.Port.ToString()).AsTask().Wait(); #else //We are creating an unbound endPoint, this is currently the rogue UDP sender and listeners only udpClient = new UdpClientWrapper(new UdpClient(ConnectionInfo.LocalIPEndPoint)); #endif } else { //If this is a specific connection we link to a default end point here isIsolatedUDPConnection = true; #if WINDOWS_PHONE || NETFX_CORE if (ConnectionInfo.LocalEndPoint == null || (ConnectionInfo.LocalIPEndPoint.Address == IPAddress.Any && connectionInfo.LocalIPEndPoint.Port == 0) || (ConnectionInfo.LocalIPEndPoint.Address == IPAddress.IPv6Any && connectionInfo.LocalIPEndPoint.Port == 0)) { socket = new DatagramSocket(); if (listenForIncomingPackets) { socket.MessageReceived += socket_MessageReceived; } socket.ConnectAsync(new HostName(ConnectionInfo.RemoteIPEndPoint.Address.ToString()), ConnectionInfo.RemoteIPEndPoint.Port.ToString()).AsTask().Wait(); } else { socket = new DatagramSocket(); if (listenForIncomingPackets) { socket.MessageReceived += socket_MessageReceived; } EndpointPair pair = new EndpointPair(new HostName(ConnectionInfo.LocalIPEndPoint.Address.ToString()), ConnectionInfo.LocalIPEndPoint.Port.ToString(), new HostName(ConnectionInfo.RemoteIPEndPoint.Address.ToString()), ConnectionInfo.RemoteIPEndPoint.Port.ToString()); socket.ConnectAsync(pair).AsTask().Wait(); } #else if (ConnectionInfo.LocalEndPoint == null) { udpClient = new UdpClientWrapper(new UdpClient(ConnectionInfo.RemoteEndPoint.AddressFamily)); } else { udpClient = new UdpClientWrapper(new UdpClient(ConnectionInfo.LocalIPEndPoint)); } //By calling connect we discard packets from anything other then the provided remoteEndPoint on our localEndPoint udpClient.Connect(ConnectionInfo.RemoteIPEndPoint); #endif } #if !WINDOWS_PHONE && !NETFX_CORE //NAT traversal does not work in .net 2.0 //Mono does not seem to have implemented AllowNatTraversal method and attempting the below method call will throw an exception //if (Type.GetType("Mono.Runtime") == null) //Allow NAT traversal by default for all udp clients // udpClientThreadSafe.AllowNatTraversal(true); if (listenForIncomingPackets) { StartIncomingDataListen(); } #endif } else { if (!existingConnection.ConnectionInfo.RemoteIPEndPoint.Address.Equals(IPAddress.Any)) { throw new Exception("If an existing udpClient is provided it must be unbound to a specific remoteEndPoint"); } #if WINDOWS_PHONE || NETFX_CORE //Using an exiting client allows us to send from the same port as for the provided existing connection this.socket = existingConnection.socket; #else //Using an exiting client allows us to send from the same port as for the provided existing connection this.udpClient = existingConnection.udpClient; #endif } IPEndPoint localEndPoint; #if WINDOWS_PHONE || NETFX_CORE localEndPoint = new IPEndPoint(IPAddress.Parse(socket.Information.LocalAddress.DisplayName.ToString()), int.Parse(socket.Information.LocalPort)); #else localEndPoint = udpClient.LocalIPEndPoint; #endif //We can update the localEndPoint so that it is correct if (!ConnectionInfo.LocalEndPoint.Equals(localEndPoint)) { //We should now be able to set the connectionInfo localEndPoint NetworkComms.UpdateConnectionReferenceByEndPoint(this, ConnectionInfo.RemoteIPEndPoint, localEndPoint); ConnectionInfo.UpdateLocalEndPointInfo(localEndPoint); } }
/// <summary> /// Incoming data listen async method /// </summary> /// <param name="ar">Call back state data</param> private void IncomingUDPPacketHandler(IAsyncResult ar) { try { UdpClientWrapper client = (UdpClientWrapper)ar.AsyncState; IPEndPoint remoteEndPoint = new IPEndPoint(IPAddress.None, 0); byte[] receivedBytes = client.EndReceive(ar, ref remoteEndPoint); //Received data after comms shutdown initiated. We should just close the connection if (NetworkComms.commsShutdown) { CloseConnection(false, -13); } if (NetworkComms.LoggingEnabled) { NetworkComms.Logger.Trace("Received " + receivedBytes.Length.ToString() + " bytes via UDP from " + remoteEndPoint.Address + ":" + remoteEndPoint.Port.ToString() + "."); } UDPConnection connection; HandshakeUDPDatagram possibleHandshakeUDPDatagram = new HandshakeUDPDatagram(receivedBytes); if (isIsolatedUDPConnection) { //This connection was created for a specific remoteEndPoint so we can handle the data internally connection = this; } else { ConnectionInfo desiredConnection = new ConnectionInfo(ConnectionType.UDP, remoteEndPoint, udpClient.LocalIPEndPoint, ConnectionInfo.ApplicationLayerProtocol, ConnectionInfo.ConnectionListener); try { //Look for an existing connection, if one does not exist we will create it //This ensures that all further processing knows about the correct endPoint connection = GetConnection(desiredConnection, ConnectionUDPOptions, ConnectionDefaultSendReceiveOptions, false, this, possibleHandshakeUDPDatagram); } catch (ConnectionShutdownException) { if ((ConnectionUDPOptions & UDPOptions.Handshake) == UDPOptions.Handshake) { if (NetworkComms.LoggingEnabled) { NetworkComms.Logger.Trace("Attempted to get connection " + desiredConnection + " but this caused a ConnectionShutdownException. Exception caught and ignored as should only happen if the connection was closed shortly after being created."); } connection = null; } else { throw; } } } if (connection != null && !possibleHandshakeUDPDatagram.DatagramHandled) { //We pass the data off to the specific connection //Lock on the packetbuilder locker as we may receive UDP packets in parallel from this host lock (connection.packetBuilder.Locker) { if (NetworkComms.LoggingEnabled) { NetworkComms.Logger.Trace(" ... " + receivedBytes.Length.ToString() + " bytes added to packetBuilder for " + connection.ConnectionInfo + ". Cached " + connection.packetBuilder.TotalBytesCached.ToString() + " bytes, expecting " + connection.packetBuilder.TotalBytesExpected.ToString() + " bytes."); } connection.packetBuilder.AddPartialPacket(receivedBytes.Length, receivedBytes); if (connection.packetBuilder.TotalBytesCached > 0) { connection.IncomingPacketHandleHandOff(connection.packetBuilder); } if (connection.packetBuilder.TotalPartialPacketCount > 0) { LogTools.LogException(new Exception("Packet builder had " + connection.packetBuilder.TotalBytesCached + " bytes remaining after a call to IncomingPacketHandleHandOff with connection " + connection.ConnectionInfo + ". Until sequenced packets are implemented this indicates a possible error."), "UDPConnectionError"); } } } client.BeginReceive(new AsyncCallback(IncomingUDPPacketHandler), client); } //On any error here we close the connection catch (NullReferenceException) { CloseConnection(true, 25); } catch (ArgumentNullException) { CloseConnection(true, 36); } catch (IOException) { CloseConnection(true, 26); } catch (ObjectDisposedException) { CloseConnection(true, 27); } catch (SocketException) { //Receive may throw a SocketException ErrorCode=10054 after attempting to send a datagram to an unreachable target. //We will try to get around this by ignoring the ICMP packet causing these problems on client creation CloseConnection(true, 28); } catch (InvalidOperationException) { CloseConnection(true, 29); } catch (ConnectionSetupException) { //Can occur if data is received as comms is being shutdown. //Method will attempt to create new connection which will throw ConnectionSetupException. CloseConnection(true, 50); } catch (Exception ex) { LogTools.LogException(ex, "Error_UDPConnectionIncomingPacketHandler"); CloseConnection(true, 30); } }