Skip to content

Commit

Permalink
Add connection troubleshooting information and add Hamachi detection
Browse files Browse the repository at this point in the history
- Add "more info" button on client when it fails to connect
- Add "more info" button in "Manage Server" menu when the port is not reachable
- Troubleshooting panel shows info about port forwarding, Windows Firewall and using Hamachi or ZeroTier
- Detect Hamachi through the availability of the 25.0.0.0/8 subnet
- When Hamachi is detected, display IP address in Host Game and Manage Game panels
- Improve connection check messages, display different messages when Hamachi is detected
  • Loading branch information
kaenganxt committed Oct 15, 2022
1 parent e15544d commit 62720fa
Show file tree
Hide file tree
Showing 5 changed files with 168 additions and 27 deletions.
11 changes: 11 additions & 0 deletions src/csm/Networking/Client.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,12 @@ public class Client
/// </summary>
public string ConnectionMessage { get; set; } = "Unknown error";

/// <summary>
/// If the join game panel should show connection troubleshooting
/// information.
/// </summary>
public bool ShowTroubleshooting { get; set; } = false;

private bool MainMenuEventProcessing = false;

public Client()
Expand All @@ -78,6 +84,7 @@ public Client()
/// <returns>True if the client is connected to the server, false if not</returns>
public bool Connect(ClientConfig clientConfig)
{
ShowTroubleshooting = false;
// Let the user know that we are trying to connect to a server
Log.Info($"Attempting to connect to server at {clientConfig.HostAddress}:{clientConfig.Port}...");

Expand Down Expand Up @@ -123,6 +130,7 @@ public bool Connect(ClientConfig clientConfig)
catch (Exception ex)
{
ConnectionMessage = "Failed to connect.";
ShowTroubleshooting = true;
Log.Error($"Failed to connect to {Config.HostAddress}:{Config.Port}", ex);
Chat.Instance.PrintGameMessage(Chat.MessageType.Error, $"Failed to connect: {ex.Message}");
Disconnect();
Expand Down Expand Up @@ -268,7 +276,10 @@ private void ListenerOnPeerConnectedEvent(NetPeer peer)
private void ListenerOnPeerDisconnectedEvent(NetPeer peer, DisconnectInfo disconnectInfo)
{
if (Status == ClientStatus.Connecting)
{
ConnectionMessage = "Failed to connect!";
ShowTroubleshooting = true;
}

// Log the error message
Log.Info($"Disconnected from server. Message: {disconnectInfo.Reason}, Code: {disconnectInfo.SocketErrorCode}");
Expand Down
27 changes: 23 additions & 4 deletions src/csm/Networking/Server.cs
Original file line number Diff line number Diff line change
Expand Up @@ -121,21 +121,29 @@ public bool StartServer(ServerConfig serverConfig)

private void CheckPort()
{
string vpnIp = IpAddress.GetVPNIpAddress();
PortState state = IpAddress.CheckPort(Config.Port);
string message;
bool portOpen = false;
switch (state.status)
{
case HttpStatusCode.ServiceUnavailable: // Could not reach port
if (automaticSuccess)
if (vpnIp != null)
{
message =
"Server is not reachable from the internet. Players can connect over Hamachi using the IP address " +
vpnIp;
portOpen = true; // This is an OK state
}
else if (automaticSuccess)
{
message =
"It was tried to forward the port automatically, but the server is not reachable from the internet. Manual port forwarding is required.";
"It was tried to forward the port automatically, but the server is not reachable from the internet. Manual port forwarding is required. See the \"Manage Server\" menu for more info.";
}
else
{
message =
"Port could not be forwarded automatically and server is not reachable from the internet. Manual port forwarding is required.";
"Port could not be forwarded automatically and server is not reachable from the internet. Manual port forwarding is required. See the \"Manage Server\" menu for more info.";
}
break;
case HttpStatusCode.OK: // Success
Expand All @@ -149,9 +157,20 @@ private void CheckPort()
{
message = "Server is reachable from the internet!";
}

if (vpnIp != null)
{
message += " Players can also connect over Hamachi, although you don't need it.";
}
break;
default: // Something failed
if (automaticSuccess)
if (vpnIp != null)
{
message =
"Error while checking server port from the internet. Players should still be able to connect over the Hamachi IP address " +
vpnIp;
}
else if (automaticSuccess)
{
message = "Port was forwarded automatically, but couldn't be checked due to error: " +
state.message;
Expand Down
15 changes: 15 additions & 0 deletions src/csm/Panels/JoinGamePanel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public class JoinGamePanel : UIPanel

private UIButton _connectButton;
private UIButton _closeButton;
private UIButton _troubleshootingButton;

private UICheckBox _passwordBox;
private UICheckBox _rememberBox;
Expand Down Expand Up @@ -106,6 +107,15 @@ public override void Start()
_connectionStatus.textAlignment = UIHorizontalAlignment.Center;
_connectionStatus.textColor = new Color32(255, 0, 0, 255);

_troubleshootingButton = this.CreateButton("?", new Vector2(170, -420), 25, 20);
_troubleshootingButton.isVisible = false;
_troubleshootingButton.eventClick += (component, param) =>
{
MessagePanel panel = PanelManager.ShowPanel<MessagePanel>();
int.TryParse(_portField.text, out int port);
panel.DisplayTroubleshooting(false, port);
};

ModCompat.BuildModInfo(this);

eventVisibilityChanged += (comp, visible) =>
Expand All @@ -126,6 +136,7 @@ private void OnConnectButtonClick(UIComponent uiComponent, UIMouseEventParameter

_connectionStatus.textColor = new Color32(255, 255, 0, 255);
_connectionStatus.text = "Connecting...";
_troubleshootingButton.isVisible = false;

if (string.IsNullOrEmpty(_portField.text) || string.IsNullOrEmpty(_ipAddressField.text))
{
Expand Down Expand Up @@ -164,6 +175,10 @@ private void OnConnectButtonClick(UIComponent uiComponent, UIMouseEventParameter
{
_connectionStatus.textColor = new Color32(255, 0, 0, 255);
_connectionStatus.text = MultiplayerManager.Instance.CurrentClient.ConnectionMessage;
if (MultiplayerManager.Instance.CurrentClient.ShowTroubleshooting)
{
_troubleshootingButton.isVisible = true;
}
}
else
{
Expand Down
74 changes: 51 additions & 23 deletions src/csm/Panels/ManageGamePanel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,12 @@ public class ManageGamePanel : UIPanel
private UITextField _portField;
private UITextField _localIpField;
private UITextField _externalIpField;
private UITextField _vpnIpField;
private UILabel _vpnLabel;
private UILabel _portState;

private UIButton _closeButton;
private UIButton _troubleshootingButton;

private int _portVal;
private string _localIpVal, _externalIpVal, _vpnIpVal;
Expand All @@ -28,10 +31,8 @@ public override void Start()
backgroundSprite = "GenericPanel";
color = new Color32(110, 110, 110, 250);

_vpnIpVal = IpAddress.GetVPNIpAddress();

width = 360;
height = _vpnIpVal == null ? 420 : 495;
height = 420;
relativePosition = PanelManager.GetCenterPosition(this);

// Title Label
Expand Down Expand Up @@ -64,30 +65,33 @@ public override void Start()
// External IP label
this.CreateLabel("External IP:", new Vector2(10, -225));

_portState = this.CreateLabel("", new Vector2(140, -225));
_portState = this.CreateLabel("", new Vector2(120, -225));
_troubleshootingButton = this.CreateButton("?", new Vector2(325, -225), 25, 20);
_troubleshootingButton.isVisible = false;
_troubleshootingButton.eventClick += (component, param) =>
{
MessagePanel panel = PanelManager.ShowPanel<MessagePanel>();
panel.DisplayTroubleshooting(true, _portVal, _vpnIpVal != null);
};

// External IP field
_externalIpVal = IpAddress.GetExternalIpAddress();
_externalIpField = this.CreateTextField(_externalIpVal, new Vector2(10, -250));
_externalIpField = this.CreateTextField("", new Vector2(10, -250));
_externalIpField.selectOnFocus = true;
_externalIpField.eventTextChanged += (ui, value) =>
{
_externalIpField.text = _externalIpVal;
};

if (_vpnIpVal != null)
// VPN IP label
_vpnLabel = this.CreateLabel("Hamachi IP:", new Vector2(10, -300));
_vpnLabel.isVisible = false;
_vpnIpField = this.CreateTextField("", new Vector2(10, -325));
_vpnIpField.selectOnFocus = true;
_vpnIpField.isVisible = false;
_vpnIpField.eventTextChanged += (ui, value) =>
{
// VPN IP label
this.CreateLabel("Hamachi IP:", new Vector2(10, -300));

// VPN IP field
UITextField vpnIpField = this.CreateTextField(_vpnIpVal, new Vector2(10, -325));
vpnIpField.selectOnFocus = true;
vpnIpField.eventTextChanged += (ui, value) =>
{
vpnIpField.text = _vpnIpVal;
};
}
_vpnIpField.text = _vpnIpVal;
};

// Close this dialog
_closeButton = this.CreateButton("Close", new Vector2(10, _vpnIpVal == null ? -340 : -415));
Expand All @@ -96,26 +100,49 @@ public override void Start()
isVisible = false;
};

new Thread(CheckPort).Start();
new Thread(UpdateWindow).Start();

eventVisibilityChanged += (component, visible) =>
{
if (!visible)
return;
_portVal = MultiplayerManager.Instance.CurrentServer.Config.Port;
_portField.text = _portVal.ToString();
new Thread(CheckPort).Start();
new Thread(UpdateWindow).Start();
};
}

private void CheckPort()
private void UpdateWindow()
{
_vpnIpVal = IpAddress.GetVPNIpAddress();
_localIpVal = IpAddress.GetLocalIpAddress();
_externalIpVal = IpAddress.GetExternalIpAddress();

Singleton<SimulationManager>.instance.m_ThreadingWrapper.QueueMainThread(() =>
{
_localIpField.text = _localIpVal;
_externalIpField.text = _externalIpVal;
_portState.text = "Checking port...";
_portState.textColor = new Color32(255, 255, 0, 255);
_portState.tooltip = "Checking if port is reachable from the internet...";
_troubleshootingButton.isVisible = false;
_portVal = MultiplayerManager.Instance.CurrentServer.Config.Port;
_portField.text = _portVal.ToString();
height = _vpnIpVal == null ? 420 : 495;
_closeButton.position = new Vector2(10, _vpnIpVal == null ? -340 : -415);
if (_vpnIpVal == null)
{
_vpnLabel.isVisible = false;
_vpnIpField.isVisible = false;
}
else
{
_vpnLabel.isVisible = true;
_vpnIpField.isVisible = true;
_vpnIpField.text = _vpnIpVal;
}
});

PortState state = IpAddress.CheckPort(_portVal);
Expand All @@ -127,6 +154,7 @@ private void CheckPort()
_portState.text = "Port is not reachable!";
_portState.textColor = new Color32(255, 0, 0, 255);
_portState.tooltip = state.message;
_troubleshootingButton.isVisible = true;
break;
case HttpStatusCode.OK: // Success
_portState.text = "Port is reachable!";
Expand Down
68 changes: 68 additions & 0 deletions src/csm/Panels/MessagePanel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -230,5 +230,73 @@ public void DisplayNoUpdateAvailable()

Show(true);
}

public void DisplayTroubleshooting(bool isHost, int port=4230, bool hasVpn=false)
{
SetTitle("Troubleshooting");

string message;

if (isHost)
{
message = "When the port is not reachable, this can have\n" +
"various reasons. You can try the following steps:\n\n" +
" - Forward the CSM port: You need to\n" +
" forward the port (" + port + " UDP)\n" +
" in your router. How this works depends on\n" +
" the router or internet provider.\n" +
" -> If it doesn't work yet, you might\n" +
" need to allow the port through the local\n" +
" Firewall (e.g. Windows Defender Firewall).\n" +
" You can find more info on the Internet.\n" +
" -> You can always check again if the\n" +
" port is working in the \"Manage Server\" menu.";
if (hasVpn)
{
message += "\n\n - Since Hamachi seems to be running,\n" +
" players can also connect using your\n" +
" Hamachi IP.";
}
else
{
message += "\n\n - Alternatively, you can use a VPN solution\n" +
" like Hamachi or ZeroTier.\n" +
" Follow the instructions of the respective\n" +
" tool. Then players can connect using the\n" +
" displayed IP address of the tool.\n" +
" You can then ignore any error messages\n" +
" regarding port forwarding.";
}
}
else
{
message = "When the \"Failed to connect\" message appears,\n" +
"this can have various reasons. The hosting player\n" +
"can try the following steps:\n\n" +
"- Check if the port is reachable in the\n" +
" \"Manage Server\" menu\n" +
" - If yes, make sure you connect using the IP\n" +
" shown as \"External IP\" in that menu\n\n" +
"- If not, you have two possibilities:\n" +
" 1. Forward the CSM port: The hosting player\n" +
" needs to forward the port (" + port + " UDP)\n" +
" in their router. How this works depends on\n" +
" the router or internet provider.\n" +
" -> If it doesn't work yet, the host might\n" +
" need to allow the port through the local\n" +
" Firewall (e.g. Windows Defender Firewall).\n" +
" You can find more info on the Internet.\n" +
" -> The host can always check again if the\n" +
" port is working in the \"Manage Server\" menu.\n\n" +
" 2. Use a VPN solution like Hamachi or ZeroTier.\n" +
" Follow the instructions of the respective\n" +
" tool. Then you can connect using the\n" +
" displayed IP address of the tool.";
}

SetMessage(message);

Show(true);
}
}
}

0 comments on commit 62720fa

Please sign in to comment.