Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use random ports for network client #124

Open
wants to merge 3 commits into
base: master
Choose a base branch
from

Conversation

cyberodactyl
Copy link

See issue #48.

Concerns:

  1. Does the name CreateSetupNetIO sounds clear enough?
  2. Client machine now spends a little bit more resources for checking port availability before using the ports
  3. Giving "port checking attempts parameter" a class constant may be a better idea than default parameter

Regarding 2, you would probably say "Then check for free ports only when initial CreateNetIO fails", well, the problem is, per my observation, a C4NetIO no longer appears to be usable once it fails to claim socket, perhaps we can pass a C4NetIO factory to the CreateSetupNetIO to create a new C4NetIO instance once it fails, but do we really need to overcomplicate here?

@maxmitti
Copy link
Member

maxmitti commented Aug 18, 2024

First of all, thank you for the effort, we appreciate it.

1. Does the name `CreateSetupNetIO` sounds clear enough?

I am not convinced, but let’s focus on more important things for now.

2. Client machine now spends a little bit more resources for checking port availability before using the ports

In itself doing more work is fine, as long as there is no easily noticable difference.

3. Giving "port checking attempts parameter" a class constant may be a better idea than default parameter

Yes, simply a constant would be better (a static constexpr variable, not #define). In my opinion it should even be a free constant, placed at the top of the .cpp file.

Regarding 2, you would probably say "Then check for free ports only when initial CreateNetIO fails", […]

That is correct ;)
I only looked at your changes on my phone now and am ignoring the implementation of C4NetIO for now, but only trying other ports on failure is what we want, especially because C4NetIO creation could also fail for other reasons. Another issue are race-conditions. In theory, between checking for port availability and actually using the port it could get occupied.

You are free and encouraged to modify C4NetIO directly.
A simple breakdown what I would expect:

  • Pass a value to C4NetIO indicating whether a fallback to other ports is desired.
  • Check the return value of bind() for the exact error code which is returned for occupied ports.
  • For clients it doesn’t really matter if the port is from a specific range, better let the OS choose it. AFAIK, this would mean that the code can simply continue without a successful bind().
    We can discuss whether a predictable port range would be preferred, but letting the OS choose should be done as a final try anyway.
  • For hosts a fallback may be desirable as well. We should discuss this. Should we want a fallback, it must be communicated to the user in case manual port forwarding is used. E.g. “WARNING: The specified TCP port 11112 is unavailable, using 12345 instead. This could affect your port forwarding configuration.”
  • Determine the actually used port number and correctly update the port number that is published to other clients. I am assuming thisis essential to avoid the repeated errors you mentioned in the chat. In general, please share such information in the PR as well.

As usual, I invite @Fulgen301 to join the discussion.

@Somebodyisnobody
Copy link
Contributor

First a big 😘 for caring about this issue!

I have some remarks to the host fallback:

For hosts a fallback may be desirable as well. We should discuss this. Should we want a fallback, it must be communicated to the user in case manual port forwarding is used. E.g. “WARNING: The specified TCP port 11112 is unavailable, using 12345 instead. This could affect your port forwarding configuration.”

A host fallback would be the icing on the cake. But if you inform the user I would give him the option to abort the lobby (before registering the lobby to league.clonkspot.org because this causes other processes to run like the Discord-Bot or other watches). So like WARNING: The specified TCP port 11112 is unavailable, using 12345 instead. <Ok> <Abort> <Retry?>

Please be aware, that almost every host is connected over a router with a firewall for IPv6. So we don't only have the network address translation table that we need to adjust for IPv4 (DNAT "port forwarding") but also a firewall for the nodes behind the SOHO-router for IPv6 (and IPv4). Therefore i would suggest the following message: This could affect your port forwarding or firewall configuration on your router.

Copy link
Member

@Fulgen301 Fulgen301 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The logic is backwards - you check whether a port is available, and then create a C4NeIO instance; this is a TOCTOU race condition, as the port may have been used by another application in the meantime. The correct way is to attempt to create the C4NetIO instance and determine a new port to use if it fails - or do what @maxmitti said and ignore bind errors since the OS can already choose a random port.

{
uint16_t port;

if (defaultPort == NULL)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Never use NULL in C++ - defaultPort also isn't a pointer.

}
}

T *netIO = CreateNetIO(this->logger, name, io, port, thread);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unnecessary this->. Additionally, you should use const as the variable isn't changed, and brace-initialization unless the variable's type is auto:

Suggested change
T *netIO = CreateNetIO(this->logger, name, io, port, thread);
T *const netIO{CreateNetIO(logger, name, io, port, thread)};

std::uint16_t C4Network2IO::DiscoverFreePort(const uint16_t attempts)
{
static constexpr uint16_t maxPort = 65535;
uint16_t port = NULL;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As above. Also, we use std:: for types wherever possible.

Suggested change
uint16_t port = NULL;
std::uint16_t port{0};


std::uint16_t C4Network2IO::DiscoverFreePort(const uint16_t attempts)
{
static constexpr uint16_t maxPort = 65535;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a constant and therefore should be spelled MaxPort; otherwise, same as above.

return false;
}

std::uint16_t C4Network2IO::DiscoverFreePort(const uint16_t attempts)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above.

static constexpr uint16_t maxPort = 65535;
uint16_t port = NULL;

for (uint16_t i = 0; i < attempts; ++i)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above.


for (uint16_t i = 0; i < attempts; ++i)
{
port = SafeRandom(maxPort);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can return 0, which isn't a valid port.

@@ -1248,6 +1264,43 @@ void C4Network2IO::SendConnPackets()
}
}

bool C4Network2IO::CheckPortAvailability(const uint16_t port)
{
const auto pNetIO = std::make_unique<C4NetIOUDP>();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can't check whether a TCP port is available by attempting to bind a UDP socket.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants