-
Notifications
You must be signed in to change notification settings - Fork 2.1k
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
Add transfer_player function to request client move to new server #14129
base: master
Are you sure you want to change the base?
Conversation
On calling minetest.transfer_player(name, address, port), server sends a disconnect message of type SERVER_ACCESSDENIED_TRANSFER with the IP and port packed into customMessage. Clients that support this new message type will disconnect and reconnect to the new address:port, using the same username and password. Clients that do not support this new message will disconnect and display the destination server address in the disconnection message box.
IMHO there needs to be an option to disallow this on a client. Or the user on the client needs to OK through some dialog. |
Do you mean the global option in If it will be disabled by default, upon the game allowing it, can it be a good solution? |
What happens if I'm registered in the first server but in the second one a different user registered an account with my same nane and different password? |
You will be disconnected from the first server and connecting to the second server returns an auth error, so the effect will be similar to disconnecting from the first server. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Convenient feature, however, given that Minetest uses unencrypted UDP packets, I'm mildly concerned about "redirect" attacks. For example, hypothetically, couldn't a malicious server first harvest player IPs, then just constantly spam "transfer player" requests to these IPs to "steal" the users?
I've also tried to review the C++ code. Hopefully one of the core devs can review my review.
@@ -61,7 +61,17 @@ function ui.update() | |||
local formspec = {} | |||
|
|||
-- handle errors | |||
if gamedata ~= nil and gamedata.reconnect_requested then | |||
if gamedata ~= nil and gamedata.reconnect_requested and #gamedata.transfer_address > 0 then |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#gamedata.transfer_address > 0
would be slightly more readable as gamedata.transfer_address ~= ""
(also: why not use nil
to indicate an absent transfer address?)
@@ -6589,6 +6589,9 @@ Bans | |||
optional reason, this will not prefix with 'Kicked: ' like kick_player. | |||
If no reason is given, it will default to 'Disconnected.' | |||
* Returns boolean indicating success (false if player nonexistent) | |||
* `minetest.transfer_player(name, address, port)`: Disconnect a player and | |||
request the client re-connect at a new address and port. The connection | |||
to the new server will occur using the same player name and password. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nitpick: "will occur using the same player name" - what about the name
parameter?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is expected to be called by some mod on the server side. So that mod has to identify a player.
As there is name parameter for minetest.disconnect_player
and minetest.kick_player
, there is a name parameter for minetest.transfer_player
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I framed that poorly. I understand the API. I just find the wording confusing, since (for obvious reasons) we don't pass a password parameter, yet it says "using the same player name and password". Suggestion: Something along the lines of "[...] will occur using name
and the client-remembered password" maybe?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From my understanding: A password is remembered in some form of hash or something similar. This value is remembered and reused with the player's name to send the AUTH packet to the new server.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this PR will actually remember the password in plaintext, in client memory? (Which isn't optimal for security, btw.)
SRP still only sends hashes over the network though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have started working on it. I will create a PR to fix it as soon as possible.
This PR will be forced to wait for that PR to be merged, and I rebase this after it and fix the problem.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have started working on it. I will create a PR to fix it as soon as possible. This PR will be forced to wait for that PR to be merged, and I rebase this after it and fix the problem.
Won't this PR have to remember the password though to do the SRP auth with the server without requiring the user to re-enter their password? Or is there a way to just remember a hash or something similar? (Which would be ideal.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If there is a way to remember SRP auth without a password, it is an ideal situation. It looks promising.
Otherwise, the client will be forced to reenter the password.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See #14196.
Maybe, something more can be done in the main menu Lua scripts.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@@ -327,6 +327,9 @@ class Client : public con::PeerHandler, public InventoryManager, public IGameDef | |||
|
|||
bool reconnectRequested() const { return m_access_denied_reconnect; } | |||
|
|||
std::string getReconnectAddress() const { return m_access_denied_reconnect_address; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not return const std::string &
s here?
@@ -228,11 +231,14 @@ bool ClientLauncher::run(GameStartData &start_data, const Settings &cmd_args) | |||
core::rect<s32>(0, 0, 10000, 10000)); | |||
|
|||
bool game_has_run = launch_game(error_message, reconnect_requested, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel at this point the three reconnect
parameters might deserve their own struct
? It seems to me this would make the code somewhat less verbose.
@@ -724,6 +724,8 @@ class Game { | |||
const GameStartData &game_params, | |||
std::string &error_message, | |||
bool *reconnect, | |||
std::string &reconnect_address, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not make these const
?
@@ -4525,7 +4531,9 @@ void the_game(bool *kill, | |||
const GameStartData &start_data, | |||
std::string &error_message, | |||
ChatBackend &chat_backend, | |||
bool *reconnect_requested) // Used for local game | |||
bool *reconnect_requested, | |||
std::string &reconnect_address, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const
?
{ | ||
NO_MAP_LOCK_REQUIRED; | ||
|
||
const char *playerName = lua_tolstring(L, 1, NULL); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wouldn't readParam<std::string>
work?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably will work. I have rebased it, not rewritten it.
std::string addressPack; | ||
*pkt >> addressPack; | ||
|
||
m_access_denied_reason = accessDeniedStrings[denyCode]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Being network-facing code, I think this should be more careful. Shouldn't there be an index check here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that this is fine because of the condition above. denyCode
has to be equal to SERVER_ACCESSDENIED_TRANSFER
here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, that's true. (Though I don't see why we do the array lookup then - might as well hardcode the string here...)
m_access_denied_reason = accessDeniedStrings[denyCode]; | ||
m_access_denied_reconnect = true; | ||
|
||
size_t splitAt = addressPack.find(';'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suggest adding a check to deal with the case of ;
not being found.
auto addressPack = address + ";" + port; | ||
|
||
for (RemotePlayer *player : m_players) { | ||
if (name.compare(player->getName()) == 0) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't there some kind of "get player by name" function you could use?
I also think that this should ideally throw an error if no player with the given name is found (rather than failing silently).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It will be probably good to return false as l_disconnect_player
in l_server.cpp
does.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That depends on whether you would consider this a frequent, recoverable exception (that often does not need to be handled) or an "unrecoverable" "panic" / failure, IMO. I think passing a wrong player name is clearly a logic issue and hence of the latter kind, so should trigger an error. Modders can just check whether a player is online if they want the "lax" behavior (or they could use pcall
).
@appgurueu |
This PR should be blocked by PR #14196 until it is merged or refused. |
I feel this really needs #14226 or at least a way to pass (signed) data to the other server. I'm just thinking about possible use-cases for this, I don't really see many at all for transferring a player to a server unknown to the first. It in the worst case even come off as hostile from the point of view of the owner that's suddenly getting unexpected players that didn't join their server in the usual way. On the flip side, for servers where the owners know each other or the same person runs both servers, it might be very useful to pass meta-data or even skip login altogether since the first server already verified the user. |
@red-001 Transferring to another server can be used in worlds that have dimensions... like space and planets, normal world and nether... etc... |
Those all seem like examples were you would want to be able to pass some metadata with the player and skip login. Generic server to server communications are probably not related, I shouldn't have mentioned that PR. |
It is not related until you want to sync inventories for example. (fly to moon, mine metals, return back to planet and build some technic staff for example) |
That could be nice to have, but I don't think this is needed for this PR to be useful. There are plenty of other options for server owners willing to exchange data between their servers. Via trusted mods and luasocket or similar, they can implement whatever they please. The HTTP API is also an option. |
Per discussion on IRC, I don't think there is that much bad that an attacker can do by redirecting the player and they would need to be able to either intercept packets, or guess the player peer id, IP address and port number. Assuming the client won't attempt to register a new account on the server it's redirected to, it doesn't let an attacker than can read packets do anything new. Otherwise this could have allowed them to capture the verifier. Could reduce the risk of that by implementing #14988. Should be more difficult to correctly guess the peer_id, IP address, port number and sequence number.
Yeah I agree, I just meant there should be some way to pass associated data with the client. Keeps everything nice and in-sync and would allow for more creative access control setups. Could for instance have a separate realm that someone can join from the main server if they have the correct item (like the Nether in one of those other Voxel games) or a mini-games server you can be redirected to from the main one. Actually could remove the concern around redirects from blind injection attacks mentioned above if a "token" was mandatory instead of a password. |
Could we get this for 5.12? |
@MisterE123 Because of the change request here and the state of #14196, I do not expect this to move forward soon. |
This is the adoption of #11175.
I have rebased the original @grapereader code. Is compilable, and looks to works well on my Linux system.
This is related to #5813 and #4428 at least.
To do
This PR is a Work in Progress.
How to test
You can also host the game in the second Minetest instance with the same player_name and password as on the first server and reconnect by the same function from it.