mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 03:30:53 +01:00
NetManager connection handshake improvement. (#773)
* Reworked the net manager connection sequence. * Correctly handle IPv4/IPv6 prioritization and fallback. * Making a proper handshake for an auth system further down the line should be much easier now too. * Added feedback to connection sequence in the main menu.
This commit is contained in:
committed by
GitHub
parent
b5635201c2
commit
25a485aea7
@@ -27,6 +27,7 @@ using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
@@ -156,6 +157,34 @@ namespace Lidgren.Network
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<IPAddress[]> ResolveAsync(string ipOrHost)
|
||||
{
|
||||
if (string.IsNullOrEmpty(ipOrHost))
|
||||
throw new ArgumentException("Supplied string must not be empty", "ipOrHost");
|
||||
|
||||
ipOrHost = ipOrHost.Trim();
|
||||
|
||||
if (IPAddress.TryParse(ipOrHost, out var ipAddress))
|
||||
{
|
||||
if (ipAddress.AddressFamily == AddressFamily.InterNetwork
|
||||
|| ipAddress.AddressFamily == AddressFamily.InterNetworkV6)
|
||||
{
|
||||
return new[] {ipAddress};
|
||||
}
|
||||
throw new ArgumentException("This method will not currently resolve other than IPv4 or IPv6 addresses");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var entry = await Task.Factory.FromAsync(Dns.BeginGetHostEntry, Dns.EndGetHostEntry, ipOrHost, null);
|
||||
return entry.AddressList;
|
||||
}
|
||||
catch (SocketException ex)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get IPv4 address from notation (xxx.xxx.xxx.xxx) or hostname
|
||||
/// </summary>
|
||||
|
||||
@@ -100,7 +100,7 @@ mouse_default_cursor_shape = 1
|
||||
size_flags_horizontal = 1
|
||||
size_flags_vertical = 1
|
||||
custom_fonts/font = SubResource( 1 )
|
||||
text = "127.0.0.1"
|
||||
text = "localhost"
|
||||
focus_mode = 2
|
||||
context_menu_enabled = false
|
||||
placeholder_text = "ip Address"
|
||||
|
||||
@@ -15,6 +15,8 @@ using SS14.Client.Interfaces.State;
|
||||
using SS14.Client.ResourceManagement;
|
||||
using SS14.Client.UserInterface.Controls;
|
||||
using SS14.Client.UserInterface.CustomControls;
|
||||
using SS14.Shared.Interfaces.Network;
|
||||
using SS14.Shared.Network;
|
||||
using SS14.Shared.Utility;
|
||||
|
||||
namespace SS14.Client.State.States
|
||||
@@ -25,16 +27,15 @@ namespace SS14.Client.State.States
|
||||
// Instantiated dynamically through the StateManager.
|
||||
public class MainScreen : State
|
||||
{
|
||||
[Dependency]
|
||||
readonly IBaseClient _client;
|
||||
[Dependency]
|
||||
readonly IUserInterfaceManager userInterfaceManager;
|
||||
[Dependency]
|
||||
readonly IStateManager stateManager;
|
||||
[Dependency] private readonly IBaseClient _client;
|
||||
[Dependency] private readonly IUserInterfaceManager userInterfaceManager;
|
||||
[Dependency] private readonly IStateManager stateManager;
|
||||
[Dependency] private readonly IClientNetManager _netManager;
|
||||
|
||||
private MainMenuControl _mainMenuControl;
|
||||
|
||||
private OptionsMenu OptionsMenu;
|
||||
private Button ConnectButton;
|
||||
|
||||
// ReSharper disable once InconsistentNaming
|
||||
private static readonly Regex IPv6Regex = new Regex(@"\[(.*:.*:.*)](?::(\d+))?");
|
||||
@@ -50,7 +51,8 @@ namespace SS14.Client.State.States
|
||||
var VBox = _mainMenuControl.GetChild("VBoxContainer");
|
||||
VBox.GetChild<Button>("ExitButton").OnPressed += ExitButtonPressed;
|
||||
VBox.GetChild<Button>("OptionsButton").OnPressed += OptionsButtonPressed;
|
||||
VBox.GetChild<Button>("ConnectButton").OnPressed += ConnectButtonPressed;
|
||||
ConnectButton = VBox.GetChild<Button>("ConnectButton");
|
||||
ConnectButton.OnPressed += ConnectButtonPressed;
|
||||
VBox.GetChild<LineEdit>("IPBox").OnTextEntered += IPBoxEntered;
|
||||
|
||||
_client.RunLevelChanged += RunLevelChanged;
|
||||
@@ -66,6 +68,7 @@ namespace SS14.Client.State.States
|
||||
public override void Shutdown()
|
||||
{
|
||||
_client.RunLevelChanged -= RunLevelChanged;
|
||||
_netManager.ConnectFailed -= _onConnectFailed;
|
||||
|
||||
_mainMenuControl.Dispose();
|
||||
OptionsMenu.Dispose();
|
||||
@@ -94,6 +97,8 @@ namespace SS14.Client.State.States
|
||||
|
||||
private void TryConnect(string address)
|
||||
{
|
||||
ConnectButton.Disabled = true;
|
||||
_netManager.ConnectFailed += _onConnectFailed;
|
||||
try
|
||||
{
|
||||
ParseAddress(address, out var ip, out var port);
|
||||
@@ -103,6 +108,7 @@ namespace SS14.Client.State.States
|
||||
{
|
||||
userInterfaceManager.Popup($"Unable to connect: {e.Message}", "Connection error.");
|
||||
Logger.Warning(e.ToString());
|
||||
_netManager.ConnectFailed -= _onConnectFailed;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,6 +118,11 @@ namespace SS14.Client.State.States
|
||||
{
|
||||
stateManager.RequestStateChange<GameScreen>();
|
||||
}
|
||||
else if (args.NewLevel == ClientRunLevel.Initialize)
|
||||
{
|
||||
ConnectButton.Disabled = false;
|
||||
_netManager.ConnectFailed -= _onConnectFailed;
|
||||
}
|
||||
}
|
||||
|
||||
private void ParseAddress(string address, out string ip, out ushort port)
|
||||
@@ -131,6 +142,7 @@ namespace SS14.Client.State.States
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// See if the IP includes a port.
|
||||
var split = address.Split(':');
|
||||
ip = address;
|
||||
@@ -151,6 +163,12 @@ namespace SS14.Client.State.States
|
||||
}
|
||||
}
|
||||
|
||||
private void _onConnectFailed(object _, NetConnectFailArgs args)
|
||||
{
|
||||
userInterfaceManager.Popup($"Failed to connect:\n{args.Reason}");
|
||||
_netManager.ConnectFailed -= _onConnectFailed;
|
||||
}
|
||||
|
||||
private class MainMenuControl : Control
|
||||
{
|
||||
protected override ResourcePath ScenePath => new ResourcePath("/Scenes/MainMenu/MainMenu.tscn");
|
||||
|
||||
@@ -34,6 +34,7 @@ namespace SS14.Shared.Interfaces.Network
|
||||
/// <summary>
|
||||
/// Disconnects from the server. This does not Restart() the client networking. Make sure
|
||||
/// to Initialize(true) networking before calling this.
|
||||
/// Also cancels in-progress connection attempts.
|
||||
/// </summary>
|
||||
/// <param name="reason">The reason why disconnect was called.</param>
|
||||
void ClientDisconnect(string reason);
|
||||
|
||||
@@ -57,6 +57,11 @@ namespace SS14.Shared.Network
|
||||
/// </summary>
|
||||
public class NetConnectFailArgs : EventArgs
|
||||
{
|
||||
|
||||
public NetConnectFailArgs(string reason)
|
||||
{
|
||||
Reason = reason;
|
||||
}
|
||||
|
||||
public string Reason { get; }
|
||||
}
|
||||
}
|
||||
|
||||
317
SS14.Shared/Network/NetManager.ClientConnect.cs
Normal file
317
SS14.Shared/Network/NetManager.ClientConnect.cs
Normal file
@@ -0,0 +1,317 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Lidgren.Network;
|
||||
using SS14.Shared.Log;
|
||||
using SS14.Shared.Utility;
|
||||
using Timer = SS14.Shared.Timers.Timer;
|
||||
|
||||
namespace SS14.Shared.Network
|
||||
{
|
||||
public partial class NetManager
|
||||
{
|
||||
private CancellationTokenSource _cancelConnectTokenSource;
|
||||
|
||||
private readonly Dictionary<NetConnection, (CancellationTokenRegistration reg, TaskCompletionSource<string> tcs)> _awaitingStatusChange
|
||||
= new Dictionary<NetConnection, (CancellationTokenRegistration, TaskCompletionSource<string>)>();
|
||||
|
||||
private readonly
|
||||
Dictionary<NetConnection, (CancellationTokenRegistration, TaskCompletionSource<NetIncomingMessage>)>
|
||||
_awaitingData =
|
||||
new Dictionary<NetConnection, (CancellationTokenRegistration, TaskCompletionSource<NetIncomingMessage>)
|
||||
>();
|
||||
|
||||
/// <inheritdoc />
|
||||
public async void ClientConnect(string host, int port, string userNameRequest)
|
||||
{
|
||||
DebugTools.Assert(!IsServer, "Should never be called on the server.");
|
||||
if (_clientConnectionState == ClientConnectionState.Connected)
|
||||
{
|
||||
throw new InvalidOperationException("The client is already connected to a server.");
|
||||
}
|
||||
|
||||
if (_clientConnectionState != ClientConnectionState.NotConnecting)
|
||||
{
|
||||
throw new InvalidOperationException("A connect attempt is already in progress. Cancel it first.");
|
||||
}
|
||||
|
||||
_cancelConnectTokenSource = new CancellationTokenSource();
|
||||
var mainCancelToken = _cancelConnectTokenSource.Token;
|
||||
_clientConnectionState = ClientConnectionState.ResolvingHost;
|
||||
|
||||
Logger.DebugS("net", "Attempting to connect to {0} port {1}", host, port);
|
||||
|
||||
// Get list of potential IP addresses for the domain.
|
||||
var endPoints = await NetUtility.ResolveAsync(host);
|
||||
|
||||
if (mainCancelToken.IsCancellationRequested)
|
||||
{
|
||||
_clientConnectionState = ClientConnectionState.NotConnecting;
|
||||
return;
|
||||
}
|
||||
|
||||
if (endPoints == null)
|
||||
{
|
||||
OnConnectFailed($"Unable to resolve domain '{host}'");
|
||||
_clientConnectionState = ClientConnectionState.NotConnecting;
|
||||
return;
|
||||
}
|
||||
|
||||
// Try to get an IPv6 and IPv4 address.
|
||||
var ipv6 = endPoints.FirstOrDefault(a => a.AddressFamily == AddressFamily.InterNetworkV6);
|
||||
var ipv4 = endPoints.FirstOrDefault(a => a.AddressFamily == AddressFamily.InterNetwork);
|
||||
|
||||
if (ipv4 == null && ipv6 == null)
|
||||
{
|
||||
OnConnectFailed($"Domain '{host}' has no associated IP addresses");
|
||||
_clientConnectionState = ClientConnectionState.NotConnecting;
|
||||
return;
|
||||
}
|
||||
|
||||
_clientConnectionState = ClientConnectionState.EstablishingConnection;
|
||||
|
||||
IPAddress first;
|
||||
IPAddress second = null;
|
||||
if (ipv6 != null)
|
||||
{
|
||||
// If there's an IPv6 address try it first then the IPv4.
|
||||
first = ipv6;
|
||||
second = ipv4;
|
||||
}
|
||||
else
|
||||
{
|
||||
first = ipv4;
|
||||
}
|
||||
|
||||
Logger.DebugS("net", "First attempt IP address is {0}, second attempt {1}", first, second);
|
||||
|
||||
NetPeer CreatePeerForIp(IPAddress address)
|
||||
{
|
||||
var config = _getBaseNetPeerConfig();
|
||||
if (address.AddressFamily == AddressFamily.InterNetworkV6)
|
||||
{
|
||||
config.LocalAddress = IPAddress.IPv6Any;
|
||||
}
|
||||
else
|
||||
{
|
||||
config.LocalAddress = IPAddress.Any;
|
||||
}
|
||||
|
||||
var peer = new NetPeer(config);
|
||||
peer.Start();
|
||||
_netPeers.Add(peer);
|
||||
return peer;
|
||||
}
|
||||
|
||||
// Create first peer.
|
||||
var firstPeer = CreatePeerForIp(first);
|
||||
var firstConnection = firstPeer.Connect(new IPEndPoint(first, port));
|
||||
NetPeer secondPeer = null;
|
||||
NetConnection secondConnection = null;
|
||||
string secondReason = null;
|
||||
|
||||
async Task ConnectSecondDelayed(CancellationToken cancellationToken)
|
||||
{
|
||||
DebugTools.AssertNotNull(second);
|
||||
// Connecting via second peer is delayed by 25ms to give an advantage to IPv6, if it works.
|
||||
await Task.Delay(25, cancellationToken);
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
secondPeer = CreatePeerForIp(second);
|
||||
secondConnection = secondPeer.Connect(new IPEndPoint(second, port));
|
||||
|
||||
secondReason = await AwaitStatusChange(secondConnection, cancellationToken);
|
||||
}
|
||||
|
||||
NetPeer winningPeer;
|
||||
NetConnection winningConnection;
|
||||
string firstReason = null;
|
||||
try
|
||||
{
|
||||
if (second != null)
|
||||
{
|
||||
// We have two addresses to try.
|
||||
var cancellation = CancellationTokenSource.CreateLinkedTokenSource(mainCancelToken);
|
||||
var firstPeerChanged = AwaitStatusChange(firstConnection, cancellation.Token);
|
||||
var secondPeerChanged = ConnectSecondDelayed(cancellation.Token);
|
||||
|
||||
var firstChange = await Task.WhenAny(firstPeerChanged, secondPeerChanged);
|
||||
|
||||
if (firstChange == firstPeerChanged)
|
||||
{
|
||||
Logger.DebugS("net", "First peer status changed.");
|
||||
// First peer responded first.
|
||||
if (firstConnection.Status == NetConnectionStatus.Connected)
|
||||
{
|
||||
// First peer won!
|
||||
Logger.DebugS("net", "First peer succeeded.");
|
||||
cancellation.Cancel();
|
||||
if (secondPeer != null)
|
||||
{
|
||||
secondPeer.Shutdown("First connection attempt won.");
|
||||
_toCleanNetPeers.Add(secondPeer);
|
||||
}
|
||||
|
||||
winningPeer = firstPeer;
|
||||
winningConnection = firstConnection;
|
||||
}
|
||||
else
|
||||
{
|
||||
// First peer failed, try the second one I guess.
|
||||
Logger.DebugS("net", "First peer failed.");
|
||||
firstPeer.Shutdown("You failed.");
|
||||
_toCleanNetPeers.Add(firstPeer);
|
||||
firstReason = firstPeerChanged.Result;
|
||||
await secondPeerChanged;
|
||||
winningPeer = secondPeer;
|
||||
winningConnection = secondConnection;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (secondConnection.Status == NetConnectionStatus.Connected)
|
||||
{
|
||||
// Second peer won!
|
||||
Logger.DebugS("net", "Second peer succeeded.");
|
||||
cancellation.Cancel();
|
||||
firstPeer.Shutdown("Second connection attempt won.");
|
||||
winningPeer = secondPeer;
|
||||
winningConnection = secondConnection;
|
||||
}
|
||||
else
|
||||
{
|
||||
// First peer failed, try the second one I guess.
|
||||
Logger.DebugS("net", "Second peer failed.");
|
||||
secondPeer.Shutdown("You failed.");
|
||||
_toCleanNetPeers.Add(secondPeer);
|
||||
firstReason = await firstPeerChanged;
|
||||
winningPeer = firstPeer;
|
||||
winningConnection = firstConnection;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Only one address to try. Pretty straight forward.
|
||||
firstReason = await AwaitStatusChange(firstConnection, mainCancelToken);
|
||||
winningPeer = firstPeer;
|
||||
winningConnection = firstConnection;
|
||||
}
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
firstPeer.Shutdown("Cancelled");
|
||||
_toCleanNetPeers.Add(firstPeer);
|
||||
if (secondPeer != null)
|
||||
{
|
||||
// ReSharper disable once PossibleNullReferenceException
|
||||
secondPeer.Shutdown("Cancelled");
|
||||
_toCleanNetPeers.Add(secondPeer);
|
||||
}
|
||||
|
||||
_clientConnectionState = ClientConnectionState.NotConnecting;
|
||||
return;
|
||||
}
|
||||
|
||||
// winningPeer can still be failed at this point.
|
||||
// If it is, neither succeeded. RIP.
|
||||
if (winningConnection.Status != NetConnectionStatus.Connected)
|
||||
{
|
||||
winningPeer.Shutdown("You failed");
|
||||
_toCleanNetPeers.Add(winningPeer);
|
||||
OnConnectFailed(secondReason ?? firstReason);
|
||||
_clientConnectionState = ClientConnectionState.NotConnecting;
|
||||
return;
|
||||
}
|
||||
|
||||
_clientConnectionState = ClientConnectionState.Handshake;
|
||||
|
||||
// We're connected start handshaking.
|
||||
|
||||
var userNameRequestMsg = winningPeer.CreateMessage(userNameRequest);
|
||||
winningPeer.SendMessage(userNameRequestMsg, winningConnection, NetDeliveryMethod.ReliableOrdered);
|
||||
|
||||
try
|
||||
{
|
||||
// Await response.
|
||||
var response = await AwaitData(winningConnection, mainCancelToken);
|
||||
var receivedUsername = response.ReadString();
|
||||
var channel = new NetChannel(this, winningConnection, new NetSessionId(receivedUsername));
|
||||
_channels.Add(winningConnection, channel);
|
||||
|
||||
var confirmConnectionMsg = winningPeer.CreateMessage("ok");
|
||||
winningPeer.SendMessage(confirmConnectionMsg, winningConnection, NetDeliveryMethod.ReliableOrdered);
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
winningPeer.Shutdown("Cancelled");
|
||||
_toCleanNetPeers.Add(secondPeer);
|
||||
_clientConnectionState = ClientConnectionState.NotConnecting;
|
||||
return;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
OnConnectFailed(e.Message);
|
||||
Logger.ErrorS("net", "Exception during handshake: {0}", e);
|
||||
winningPeer.Shutdown("Something happened.");
|
||||
_toCleanNetPeers.Add(secondPeer);
|
||||
_clientConnectionState = ClientConnectionState.NotConnecting;
|
||||
return;
|
||||
}
|
||||
|
||||
_clientConnectionState = ClientConnectionState.Connected;
|
||||
Logger.DebugS("net", "Handshake completed, connection established.");
|
||||
}
|
||||
|
||||
private Task<string> AwaitStatusChange(NetConnection connection, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (_awaitingStatusChange.ContainsKey(connection))
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
var tcs = new TaskCompletionSource<string>();
|
||||
CancellationTokenRegistration reg = default;
|
||||
if (cancellationToken != default)
|
||||
{
|
||||
reg = cancellationToken.Register(() =>
|
||||
{
|
||||
_awaitingStatusChange.Remove(connection);
|
||||
tcs.TrySetCanceled();
|
||||
});
|
||||
}
|
||||
|
||||
_awaitingStatusChange.Add(connection, (reg, tcs));
|
||||
return tcs.Task;
|
||||
}
|
||||
|
||||
private Task<NetIncomingMessage> AwaitData(NetConnection connection, CancellationToken cancellationToken= default)
|
||||
{
|
||||
if (_awaitingData.ContainsKey(connection))
|
||||
{
|
||||
throw new InvalidOperationException("Cannot await data twice.");
|
||||
}
|
||||
|
||||
var tcs = new TaskCompletionSource<NetIncomingMessage>();
|
||||
CancellationTokenRegistration reg = default;
|
||||
if (cancellationToken != default)
|
||||
{
|
||||
reg = cancellationToken.Register(() =>
|
||||
{
|
||||
_awaitingData.Remove(connection);
|
||||
tcs.TrySetCanceled();
|
||||
});
|
||||
}
|
||||
|
||||
_awaitingData.Add(connection, (reg, tcs));
|
||||
return tcs.Task;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Lidgren.Network;
|
||||
using SS14.Shared.Configuration;
|
||||
using SS14.Shared.Interfaces.Configuration;
|
||||
@@ -29,7 +30,7 @@ namespace SS14.Shared.Network
|
||||
/// <summary>
|
||||
/// Manages all network connections and packet IO.
|
||||
/// </summary>
|
||||
public class NetManager : IClientNetManager, IServerNetManager, IDisposable
|
||||
public partial class NetManager : IClientNetManager, IServerNetManager, IDisposable
|
||||
{
|
||||
private readonly Dictionary<Type, ProcessMessage> _callbacks = new Dictionary<Type, ProcessMessage>();
|
||||
|
||||
@@ -57,6 +58,7 @@ namespace SS14.Shared.Network
|
||||
/// The list of network peers we are listening on.
|
||||
/// </summary>
|
||||
private readonly List<NetPeer> _netPeers = new List<NetPeer>();
|
||||
private readonly List<NetPeer> _toCleanNetPeers = new List<NetPeer>();
|
||||
|
||||
/// <inheritdoc />
|
||||
public int Port => _config.GetCVar<int>("net.port");
|
||||
@@ -72,6 +74,8 @@ namespace SS14.Shared.Network
|
||||
|
||||
public bool IsRunning => _netPeers.Count != 0;
|
||||
|
||||
private ClientConnectionState _clientConnectionState;
|
||||
|
||||
public NetworkStats Statistics
|
||||
{
|
||||
get
|
||||
@@ -221,6 +225,9 @@ namespace SS14.Shared.Network
|
||||
}
|
||||
|
||||
_strings.Reset();
|
||||
|
||||
_cancelConnectTokenSource.Cancel();
|
||||
_clientConnectionState = ClientConnectionState.NotConnecting;
|
||||
}
|
||||
|
||||
public void ProcessPackets()
|
||||
@@ -228,6 +235,7 @@ namespace SS14.Shared.Network
|
||||
foreach (var peer in _netPeers)
|
||||
{
|
||||
NetIncomingMessage msg;
|
||||
var recycle = true;
|
||||
while ((msg = peer.ReadMessage()) != null)
|
||||
{
|
||||
switch (msg.MessageType)
|
||||
@@ -253,7 +261,7 @@ namespace SS14.Shared.Network
|
||||
break;
|
||||
|
||||
case NetIncomingMessageType.Data:
|
||||
DispatchNetMessage(msg);
|
||||
recycle = DispatchNetMessage(msg);
|
||||
break;
|
||||
|
||||
case NetIncomingMessageType.StatusChanged:
|
||||
@@ -269,43 +277,20 @@ namespace SS14.Shared.Network
|
||||
break;
|
||||
}
|
||||
|
||||
peer.Recycle(msg);
|
||||
if (recycle)
|
||||
{
|
||||
peer.Recycle(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void ClientConnect(string host, int port, string userNameRequest)
|
||||
{
|
||||
DebugTools.Assert(!IsServer, "Should never be called on the server.");
|
||||
DebugTools.Assert(!IsConnected);
|
||||
|
||||
if (IsRunning)
|
||||
if (_toCleanNetPeers.Count != 0)
|
||||
{
|
||||
ClientDisconnect("Client left server.");
|
||||
foreach (var peer in _toCleanNetPeers)
|
||||
{
|
||||
_netPeers.Remove(peer);
|
||||
}
|
||||
}
|
||||
|
||||
// Set up NetPeer.
|
||||
var endPoint = NetUtility.Resolve(host, port);
|
||||
|
||||
Logger.InfoS("net", $"Connecting to {endPoint}...");
|
||||
|
||||
var config = _getBaseNetPeerConfig();
|
||||
if (endPoint.AddressFamily == AddressFamily.InterNetworkV6)
|
||||
{
|
||||
config.LocalAddress = IPAddress.IPv6Any;
|
||||
}
|
||||
else
|
||||
{
|
||||
config.LocalAddress = IPAddress.Any;
|
||||
}
|
||||
|
||||
var peer = new NetPeer(config);
|
||||
peer.Start();
|
||||
_netPeers.Add(peer);
|
||||
var hail = peer.CreateMessage();
|
||||
hail.Write(userNameRequest);
|
||||
peer.Connect(host, port, hail);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -353,22 +338,40 @@ namespace SS14.Shared.Network
|
||||
private void HandleStatusChanged(NetIncomingMessage msg)
|
||||
{
|
||||
var sender = msg.SenderConnection;
|
||||
msg.ReadByte();
|
||||
var reason = msg.ReadString();
|
||||
Logger.DebugS("net", $"{sender.RemoteEndPoint}: Status changed to {sender.Status}");
|
||||
|
||||
if (_awaitingStatusChange.TryGetValue(sender, out var resume))
|
||||
{
|
||||
resume.Item1.Dispose();
|
||||
resume.Item2.SetResult(reason);
|
||||
_awaitingStatusChange.Remove(sender);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (sender.Status)
|
||||
{
|
||||
case NetConnectionStatus.Connected:
|
||||
HandleConnected(sender);
|
||||
if (IsServer)
|
||||
{
|
||||
HandleHandshake(sender);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case NetConnectionStatus.Disconnected:
|
||||
if (_channels.ContainsKey(sender))
|
||||
HandleDisconnect(msg);
|
||||
else if (sender.RemoteUniqueIdentifier == 0
|
||||
) // is this the best way to detect an unsuccessful connect?
|
||||
if (_awaitingData.TryGetValue(sender, out var awaitInfo))
|
||||
{
|
||||
Logger.InfoS("net", $"{sender.RemoteEndPoint}: Failed to connect");
|
||||
OnConnectFailed();
|
||||
awaitInfo.Item1.Dispose();
|
||||
awaitInfo.Item2.TrySetException(
|
||||
new Exception($"Disconnected: {reason}"));
|
||||
_awaitingData.Remove(sender);
|
||||
}
|
||||
|
||||
if (_channels.ContainsKey(sender))
|
||||
{
|
||||
HandleDisconnect(msg);
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -377,18 +380,33 @@ namespace SS14.Shared.Network
|
||||
|
||||
private void HandleApproval(NetIncomingMessage message)
|
||||
{
|
||||
var sender = message.SenderConnection;
|
||||
var ip = sender.RemoteEndPoint;
|
||||
var name = message.ReadString();
|
||||
// TODO: Maybe preemptively refuse connections here in some cases?
|
||||
if (message.SenderConnection.Status != NetConnectionStatus.RespondedAwaitingApproval)
|
||||
{
|
||||
// This can happen if the approval message comes in after the state changes to disconnected.
|
||||
// In that case just ignore it.
|
||||
return;
|
||||
}
|
||||
message.SenderConnection.Approve();
|
||||
}
|
||||
|
||||
private async void HandleHandshake(NetConnection connection)
|
||||
{
|
||||
var userNamePacket = await AwaitData(connection);
|
||||
var requestedUsername = userNamePacket.ReadString();
|
||||
|
||||
if (!UsernameHelpers.IsNameValid(requestedUsername))
|
||||
{
|
||||
connection.Disconnect("Username is invalid (contains illegal characters/too long).");
|
||||
return;
|
||||
}
|
||||
|
||||
var endPoint = connection.RemoteEndPoint;
|
||||
var name = requestedUsername;
|
||||
var origName = name;
|
||||
var iterations = 1;
|
||||
|
||||
if (!UsernameHelpers.IsNameValid(name))
|
||||
{
|
||||
sender.Deny("Username is invalid (contains illegal characters/too long).");
|
||||
}
|
||||
|
||||
while (_assignedSessions.Values.Any(u => u.Username == name))
|
||||
while (_assignedSessions.Values.Any(u => u.Username == requestedUsername))
|
||||
{
|
||||
// This is shit but I don't care.
|
||||
name = $"{origName}_{++iterations}";
|
||||
@@ -396,30 +414,33 @@ namespace SS14.Shared.Network
|
||||
|
||||
var session = new NetSessionId(name);
|
||||
|
||||
if (OnConnecting(ip, session))
|
||||
if (OnConnecting(endPoint, session))
|
||||
{
|
||||
_assignedSessions.Add(sender, session);
|
||||
var msg = message.SenderConnection.Peer.CreateMessage();
|
||||
_assignedSessions.Add(connection, session);
|
||||
var msg = connection.Peer.CreateMessage();
|
||||
msg.Write(name);
|
||||
sender.Approve(msg);
|
||||
connection.Peer.SendMessage(msg, connection, NetDeliveryMethod.ReliableOrdered);
|
||||
}
|
||||
else
|
||||
{
|
||||
sender.Deny("Server is full.");
|
||||
connection.Disconnect("Sorry, denied. Why? Couldn't tell you, I didn't implement a deny reason.");
|
||||
return;
|
||||
}
|
||||
|
||||
var okMsg = await AwaitData(connection);
|
||||
if (okMsg.ReadString() != "ok")
|
||||
{
|
||||
connection.Disconnect("You should say ok.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Handshake complete!
|
||||
HandleInitialHandshakeComplete(connection);
|
||||
}
|
||||
|
||||
private void HandleConnected(NetConnection sender)
|
||||
private void HandleInitialHandshakeComplete(NetConnection sender)
|
||||
{
|
||||
NetSessionId session;
|
||||
if (IsClient)
|
||||
{
|
||||
session = new NetSessionId(sender.RemoteHailMessage.ReadString());
|
||||
}
|
||||
else
|
||||
{
|
||||
session = _assignedSessions[sender];
|
||||
}
|
||||
var session = _assignedSessions[sender];
|
||||
|
||||
var channel = new NetChannel(this, sender, session);
|
||||
_channels.Add(sender, channel);
|
||||
@@ -428,9 +449,7 @@ namespace SS14.Shared.Network
|
||||
|
||||
Logger.InfoS("net", $"{channel.RemoteEndPoint}: Connected");
|
||||
|
||||
// client is connected after string packet get received
|
||||
if (IsServer)
|
||||
OnConnected(channel);
|
||||
OnConnected(channel);
|
||||
}
|
||||
|
||||
private void HandleDisconnect(NetIncomingMessage message)
|
||||
@@ -465,22 +484,31 @@ namespace SS14.Shared.Network
|
||||
channel.Disconnect(reason);
|
||||
}
|
||||
|
||||
private void DispatchNetMessage(NetIncomingMessage msg)
|
||||
private bool DispatchNetMessage(NetIncomingMessage msg)
|
||||
{
|
||||
var peer = msg.SenderConnection.Peer;
|
||||
if (peer.Status == NetPeerStatus.ShutdownRequested)
|
||||
return;
|
||||
return true;
|
||||
|
||||
if (peer.Status == NetPeerStatus.NotRunning)
|
||||
return;
|
||||
return true;
|
||||
|
||||
if (!IsConnected)
|
||||
return;
|
||||
return true;
|
||||
|
||||
if (_awaitingData.TryGetValue(msg.SenderConnection, out var info))
|
||||
{
|
||||
var (cancel, tcs) = info;
|
||||
_awaitingData.Remove(msg.SenderConnection);
|
||||
cancel.Dispose();
|
||||
tcs.TrySetResult(msg);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (msg.LengthBytes < 1)
|
||||
{
|
||||
Logger.WarningS("net", $"{msg.SenderConnection.RemoteEndPoint}: Received empty packet.");
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
var id = msg.ReadByte();
|
||||
@@ -488,13 +516,13 @@ namespace SS14.Shared.Network
|
||||
if (!_strings.TryGetString(id, out string name))
|
||||
{
|
||||
Logger.WarningS("net", $"{msg.SenderConnection.RemoteEndPoint}: No string in table with ID {id}.");
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!_messages.TryGetValue(name, out Type packetType))
|
||||
{
|
||||
Logger.WarningS("net", $"{msg.SenderConnection.RemoteEndPoint}: No message with Name {name}.");
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
var channel = GetChannel(msg.SenderConnection);
|
||||
@@ -515,10 +543,11 @@ namespace SS14.Shared.Network
|
||||
{
|
||||
Logger.WarningS("net",
|
||||
$"{msg.SenderConnection.RemoteEndPoint}: Received packet {id}:{name}, but callback was not registered.");
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
callback?.Invoke(instance);
|
||||
return true;
|
||||
}
|
||||
|
||||
#region NetMessages
|
||||
@@ -571,6 +600,7 @@ namespace SS14.Shared.Network
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
peer.SendMessage(packet, peer.Connections, method, 0);
|
||||
}
|
||||
}
|
||||
@@ -629,9 +659,9 @@ namespace SS14.Shared.Network
|
||||
return !args.Deny;
|
||||
}
|
||||
|
||||
protected virtual void OnConnectFailed()
|
||||
protected virtual void OnConnectFailed(string reason)
|
||||
{
|
||||
var args = new NetConnectFailArgs();
|
||||
var args = new NetConnectFailArgs(reason);
|
||||
ConnectFailed?.Invoke(this, args);
|
||||
}
|
||||
|
||||
@@ -673,6 +703,34 @@ namespace SS14.Shared.Network
|
||||
throw new ArgumentOutOfRangeException(nameof(group), group, null);
|
||||
}
|
||||
}
|
||||
|
||||
private enum ClientConnectionState
|
||||
{
|
||||
/// <summary>
|
||||
/// We are not connected and not trying to get connected either. Quite lonely huh.
|
||||
/// </summary>
|
||||
NotConnecting,
|
||||
|
||||
/// <summary>
|
||||
/// Resolving the DNS query for the address of the server.
|
||||
/// </summary>
|
||||
ResolvingHost,
|
||||
|
||||
/// <summary>
|
||||
/// Attempting to establish a connection to the server.
|
||||
/// </summary>
|
||||
EstablishingConnection,
|
||||
|
||||
/// <summary>
|
||||
/// Connection established, going through regular handshake business.
|
||||
/// </summary>
|
||||
Handshake,
|
||||
|
||||
/// <summary>
|
||||
/// Connection is solid and handshake is done go wild.
|
||||
/// </summary>
|
||||
Connected
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -208,6 +208,7 @@
|
||||
<Compile Include="Network\Messages\MsgViewVariablesReqData.cs" />
|
||||
<Compile Include="Network\Messages\MsgViewVariablesReqSession.cs" />
|
||||
<Compile Include="Network\NetChannelArgs.cs" />
|
||||
<Compile Include="Network\NetManager.ClientConnect.cs" />
|
||||
<Compile Include="Network\NetMessageArgs.cs" />
|
||||
<Compile Include="Network\Messages\MsgChat.cs" />
|
||||
<Compile Include="Network\Messages\MsgConCmd.cs" />
|
||||
|
||||
Reference in New Issue
Block a user