protected void NegotiateChannels(Connection.Tcp connection) { Message.Base msg; if (connection.Receive(out msg)) { if (msg.IsType <Message.Negotiation.Start>()) { connection.Parameters.guid = Guid.NewGuid(); SaveMonitor(connection); // Send a new Guid for these connections and the number of associated channels Message.Negotiation.New n = new Message.Negotiation.New { guid = connection.Parameters.guid, nbChannels = (ushort)Math.Min(channels != null ? channels.Count : 0, ushort.MaxValue) }; connection.Send(n); if (n.nbChannels <= 0) { Drop(connection, "No channels configured."); } for (ushort i = 0; i < n.nbChannels; i++) { ServerChannel channel = channels[i]; Message.Negotiation.Parameters param = new Message.Negotiation.Parameters { guid = n.guid, channel = i, type = channel.type, heartbeat = channel.parameters.Heartbeat, autoReconnect = !channel.parameters.disableAutoReconnect }; connection.Send(param); } } else if (msg.IsType <Message.Negotiation.Channel.TCP>()) { Message.Negotiation.Channel.TCP tcp = (Message.Negotiation.Channel.TCP)msg; connection.SetConfig(tcp.guid, tcp.channel, channels[tcp.channel].parameters.Heartbeat); SaveChannel(connection); } else { Drop(connection, "Unsupported negotiation command '{0}'.", msg.GetType()); } } else { Drop(connection, "Expected to receive some negotiation command."); } }
protected void AcceptClient(IAsyncResult async_result) { try { if (state == State.RUNNING && listener != null && listener.Server != null && listener.Server.IsBound) { Message.Negotiation.Parameters param = new Message.Negotiation.Parameters { guid = Guid.Empty, channel = ushort.MaxValue, heartbeat = negotiation.parameters.Heartbeat, autoReconnect = !negotiation.parameters.disableAutoReconnect }; // Get the new connection Connection.Tcp connection = new Connection.Tcp(this, param, DisconnectionHandler, listener.EndAcceptTcpClient(async_result)); lock (initializedConnections) { initializedConnections.Add(connection); } connection.initialization = Task.Run(() => Connected(connection)); } } catch (ObjectDisposedException) { // Can happen rarely because the atomicity between IsBound and EndAcceptTcpClient is not verified. Therefore the // listener can be disposed in this time frame. In this case, we have nothing to do. Just exit to close cleanly the listener. } catch (Exception exception) { Debug.LogErrorFormat("{0}: {1}\n{2}", exception.GetType(), exception.Message, exception.StackTrace); } }
protected void ValidateCredentials(Connection.Tcp connection) { Message.Connection.Request request = new Message.Connection.Request { version = connection.version, username = credentials.username, password = credentials.password }; connection.Send(request); Message.Base v; if (connection.Receive(out v) && v.IsType <Message.Connection.Validation>()) { Message.Connection.Validation validation = (Message.Connection.Validation)v; // Check if the sent credentials are OK if (validation.accepted) { NegotiateChannels(connection); } else { Drop(connection, "Invalid credentials."); } } else { Drop(connection, "Expected to receive credentials validation."); } }
protected void ConnectUdp(Connection.Tcp connection, Message.Negotiation.Parameters param) { UdpConnectionParams udp_params = SendUdpParams(connection, param); lock (pendingUdpConnection) { pendingUdpConnection.Add(udp_params); } }
protected void ValidateCredentials(Connection.Tcp connection) { Message.Base req; if (connection.Receive(out req) && req.IsType <Message.Connection.Request>()) { Message.Connection.Request request = (Message.Connection.Request)req; connection.version = request.version; if (connection.version < maxSupportedVersion) { Debug.LogWarningFormat("Client does not support protocol version '{0}'. Using version '{1}' instead.", maxSupportedVersion, connection.version); } Message.Connection.Validation validation = new Message.Connection.Validation(); // Check if the credentials are valid if (request.username == credentials.username && request.password == credentials.password) { validation.accepted = true; // Notify the client that the credentials are valid connection.Send(validation); NegotiateChannels(connection); } else { string error_message = string.Format("Invalid connection credentials for user '{0}'. Dropping connection.", request.username); Debug.LogWarning(error_message); validation.accepted = false; // Notify the client that the credentials are wrong connection.Send(validation); // Drop the connection Close(connection); throw new DropException(error_message); } } else { Drop(connection, "Expected to receive negotiation connection request."); } }
protected void Connected(Connection.Tcp connection) { try { // We should be connected if (connection.client.Connected) { // Get the stream associated with this connection connection.stream = connection.client.GetStream(); Message.Connection.Parameters header = new Message.Connection.Parameters { version = maxSupportedVersion, encrypted = (serverCertificate != null) }; // Send greating message with protocol version and parameters connection.Send(header); if (serverCertificate != null) { // Create the SSL wraping stream connection.stream = new SslStream(connection.stream, false, new RemoteCertificateValidationCallback( (sender, certificate, chain, sslPolicyErrors) => (sslPolicyErrors & SslPolicyErrors.RemoteCertificateNotAvailable) == SslPolicyErrors.RemoteCertificateNotAvailable ), null, EncryptionPolicy.RequireEncryption); // Authenticate with the client ((SslStream)connection.stream).BeginAuthenticateAsServer(serverCertificate, Authenticated, connection); } else { // No encryption, the channel stay as is ValidateCredentials(connection); } } else { Debug.LogError("The connection from the client failed."); } } catch (DropException) { throw; } catch (Exception exception) { Debug.LogErrorFormat("{0}: {1}\n{2}", exception.GetType(), exception.Message, exception.StackTrace); } }
protected void ConnectTcp(Message.Negotiation.Parameters param) { // Create a new TCP client Connection.Tcp connection = new Connection.Tcp(this, param, DisconnectionHandler, new TcpClient()); if (param.guid != Guid.Empty) { lock (initializedConnections) { initializedConnections.Add(connection); } } // Start asynchronous connection to server connection.initialization = Task.Run(() => connection.client.BeginConnect(hostname, port, Connected, connection)); }
protected void Drop(Connection.Tcp connection, string message, params object[] values) { string error_message = string.Format(message, values); if (!error_message.EndsWith(".")) { error_message += "."; } error_message += " Dropping connection."; Debug.LogError(error_message); Close(connection); throw new DropException(error_message); }
protected void Authenticated(IAsyncResult async_result) { try { // Finalize the authentication as client for the SSL stream Connection.Tcp connection = (Connection.Tcp)async_result.AsyncState; ((SslStream)connection.stream).EndAuthenticateAsClient(async_result); ValidateCredentials(connection); } catch (DropException) { throw; } catch (Exception) { Debug.LogError("Authentication failed"); } }
protected void Authenticated(IAsyncResult async_result) { Connection.Tcp connection = null; try { // Finalize the authentication as server for the SSL stream connection = (Connection.Tcp)async_result.AsyncState; ((SslStream)connection.stream).EndAuthenticateAsServer(async_result); ValidateCredentials(connection); } catch (DropException) { throw; } catch (Exception) { Drop(connection, "Authentication failed."); } }
protected void NegotiateChannels(Connection.Tcp connection) { // Check if we must negotiate other channel or just open the current one if (connection.Remote == Guid.Empty) { connection.Send(new Message.Negotiation.Start()); Message.Base msg; if (connection.Receive(out msg) && msg.IsType <Message.Negotiation.New>()) { Message.Negotiation.New n = (Message.Negotiation.New)msg; connection.Parameters.guid = n.guid; ushort nb_channels = n.nbChannels; if (nb_channels > 0) { List <Message.Negotiation.Parameters> parameters = new List <Message.Negotiation.Parameters>(nb_channels); for (ushort i = 0; i < nb_channels; i++) { if (connection.Receive(out msg) && msg.IsType <Message.Negotiation.Parameters>()) { Message.Negotiation.Parameters param = (Message.Negotiation.Parameters)msg; parameters.Add(param); } else { Drop(connection, "Expected to receive channel parameterts for channel {0}.", i); } } SaveMonitor(connection); foreach (Message.Negotiation.Parameters param in parameters) { if (!Ready(param.guid, param.channel)) { switch (param.type) { case Channel.Type.TCP: ConnectTcp(param); break; case Channel.Type.UDP: ConnectUdp(connection, param); break; } } } state = State.RUNNING; } else { Drop(connection, "No channels configured."); } } else { Drop(connection, "Expected to receive the new connection negotiation parameters."); } } else { Message.Negotiation.Channel.TCP tcp = new Message.Negotiation.Channel.TCP { guid = connection.Remote, channel = connection.Channel }; connection.Send(tcp); SaveChannel(connection); } }
protected void Connected(IAsyncResult async_result) { try { // Finalize connection to server Connection.Tcp connection = (Connection.Tcp)async_result.AsyncState; connection.client.EndConnect(async_result); // We should be connected if (connection.client.Connected) { // Get the stream associated with this connection connection.stream = connection.client.GetStream(); Message.Base h; if (connection.Receive(out h) && h.IsType <Message.Connection.Parameters>()) { Message.Connection.Parameters header = (Message.Connection.Parameters)h; connection.version = header.version; if (connection.version > maxSupportedVersion) { Debug.LogWarningFormat("Usupported protocol version '{0}'. Using version '{1}' instead.", connection.version, maxSupportedVersion); connection.version = maxSupportedVersion; } if (header.encrypted) { // Create the SSL wraping stream connection.stream = new SslStream(connection.stream, false, new RemoteCertificateValidationCallback(ValidateServerCertificate), null, EncryptionPolicy.RequireEncryption); // Authenticate with the server ((SslStream)connection.stream).BeginAuthenticateAsClient(hostname, Authenticated, connection); } else { // No encryption, the channel stay as is ValidateCredentials(connection); } } else { Drop(connection, "Expected to receive connection greetings and parameters."); } } else { Drop(connection, "The connection to {0}:{1} failed.", hostname, port); } } catch (DropException) { throw; } catch (Exception exception) { Debug.LogErrorFormat("{0}: {1}\n{2}", exception.GetType(), exception.Message, exception.StackTrace); } }