Merge pull request #67917 from Faless/mp/4.x_auth

[MP] Add peer authentication support to the default MultiplayerAPI.
This commit is contained in:
Rémi Verschelde 2022-11-02 18:53:38 +01:00
commit 30e4e7c083
No known key found for this signature in database
GPG Key ID: C3336907360768E1
9 changed files with 290 additions and 78 deletions

View File

@ -1,11 +1,11 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="MultiplayerPeer" inherits="PacketPeer" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
<brief_description>
A high-level network interface to simplify multiplayer interactions.
Abstract class for specialized [PacketPeer]s used by the [MultiplayerAPI].
</brief_description>
<description>
Manages the connection to multiplayer peers. Assigns unique IDs to each client connected to the server. See also [MultiplayerAPI].
[b]Note:[/b] The high-level multiplayer API protocol is an implementation detail and isn't meant to be used by non-Godot servers. It may change without notice.
Manages the connection with one or more remote peers acting as server or client and assigning unique IDs to each of them. See also [MultiplayerAPI].
[b]Note:[/b] The [MultiplayerAPI] protocol is an implementation detail and isn't meant to be used by non-Godot servers. It may change without notice.
[b]Note:[/b] When exporting to Android, make sure to enable the [code]INTERNET[/code] permission in the Android export preset before exporting the project or using one-click deploy. Otherwise, network communication of any kind will be blocked by Android.
</description>
<tutorials>
@ -97,49 +97,34 @@
</member>
</members>
<signals>
<signal name="connection_failed">
<description>
Emitted when a connection attempt fails.
</description>
</signal>
<signal name="connection_succeeded">
<description>
Emitted when a connection attempt succeeds.
</description>
</signal>
<signal name="peer_connected">
<param index="0" name="id" type="int" />
<description>
Emitted by the server when a client connects.
Emitted when a remote peer connects.
</description>
</signal>
<signal name="peer_disconnected">
<param index="0" name="id" type="int" />
<description>
Emitted by the server when a client disconnects.
</description>
</signal>
<signal name="server_disconnected">
<description>
Emitted by clients when the server disconnects.
Emitted when a remote peer has disconnected.
</description>
</signal>
</signals>
<constants>
<constant name="CONNECTION_DISCONNECTED" value="0" enum="ConnectionStatus">
The ongoing connection disconnected.
The MultiplayerPeer is disconnected.
</constant>
<constant name="CONNECTION_CONNECTING" value="1" enum="ConnectionStatus">
A connection attempt is ongoing.
The MultiplayerPeer is currently connecting to a server.
</constant>
<constant name="CONNECTION_CONNECTED" value="2" enum="ConnectionStatus">
The connection attempt succeeded.
This MultiplayerPeer is connected.
</constant>
<constant name="TARGET_PEER_BROADCAST" value="0">
Packets are sent to the server and then redistributed to other peers.
Packets are sent to all connected peers.
</constant>
<constant name="TARGET_PEER_SERVER" value="1">
Packets are sent to the server alone.
Packets are sent to the remote peer acting as server.
</constant>
<constant name="TRANSFER_MODE_UNRELIABLE" value="0" enum="TransferMode">
Packets are not acknowledged, no resend attempts are made for lost packets. Packets may arrive in any order. Potentially faster than [constant TRANSFER_MODE_UNRELIABLE_ORDERED]. Use for non-critical data, and always consider whether the order matters.

View File

@ -159,10 +159,7 @@ void ENetMultiplayerPeer::_disconnect_inactive_peers() {
if (hosts.has(P)) {
hosts.erase(P);
}
if (active_mode == MODE_CLIENT) {
ERR_CONTINUE(P != TARGET_PEER_SERVER);
emit_signal(SNAME("server_disconnected"));
}
ERR_CONTINUE(active_mode == MODE_CLIENT && P != TARGET_PEER_SERVER);
emit_signal(SNAME("peer_disconnected"), P);
}
}
@ -186,14 +183,10 @@ void ENetMultiplayerPeer::poll() {
if (ret == ENetConnection::EVENT_CONNECT) {
connection_status = CONNECTION_CONNECTED;
emit_signal(SNAME("peer_connected"), 1);
emit_signal(SNAME("connection_succeeded"));
} else if (ret == ENetConnection::EVENT_DISCONNECT) {
if (connection_status == CONNECTION_CONNECTED) {
// Client just disconnected from server.
emit_signal(SNAME("server_disconnected"));
emit_signal(SNAME("peer_disconnected"), 1);
} else {
emit_signal(SNAME("connection_failed"));
}
close();
} else if (ret == ENetConnection::EVENT_RECEIVE) {

View File

@ -19,6 +19,35 @@
Clears the current SceneMultiplayer network state (you shouldn't call this unless you know what you are doing).
</description>
</method>
<method name="complete_auth">
<return type="int" enum="Error" />
<param index="0" name="id" type="int" />
<description>
Mark the authentication step as completed for the remote peer identified by [param id]. The [signal MultiplayerAPI.peer_connected] signal will be emitted for this peer once the remote side also completes the authentication. No further authentication messages are expected to be received from this peer.
If a peer disconnects before completing authentication, either due to a network issue, the [member auth_timeout] expiring, or manually calling [method disconnect_peer], the [signal peer_authentication_failed] signal will be emitted instead of [signal MultiplayerAPI.peer_disconnected].
</description>
</method>
<method name="disconnect_peer">
<return type="void" />
<param index="0" name="id" type="int" />
<description>
Disconnects the peer identified by [param id], removing it from the list of connected peers, and closing the underlying connection with it.
</description>
</method>
<method name="get_authenticating_peers">
<return type="PackedInt32Array" />
<description>
Returns the IDs of the peers currently trying to authenticate with this [MultiplayerAPI].
</description>
</method>
<method name="send_auth">
<return type="int" enum="Error" />
<param index="0" name="id" type="int" />
<param index="1" name="data" type="PackedByteArray" />
<description>
Sends the specified [param data] to the remote peer identified by [param id] as part of an authentication message. This can be used to authenticate peers, and control when [signal MultiplayerAPI.peer_connected] is emitted (and the remote peer accepted as one of the connected peers).
</description>
</method>
<method name="send_bytes">
<return type="int" enum="Error" />
<param index="0" name="bytes" type="PackedByteArray" />
@ -35,6 +64,12 @@
If [code]true[/code], the MultiplayerAPI will allow encoding and decoding of object during RPCs.
[b]Warning:[/b] Deserialized objects can contain code which gets executed. Do not use this option if the serialized object comes from untrusted sources to avoid potential security threat such as remote code execution.
</member>
<member name="auth_callback" type="Callable" setter="set_auth_callback" getter="get_auth_callback">
The callback to execute when when receiving authentication data sent via [method send_auth]. If the [Callable] is empty (default), peers will be automatically accepted as soon as they connect.
</member>
<member name="auth_timeout" type="float" setter="set_auth_timeout" getter="get_auth_timeout" default="3.0">
If set to a value greater than [code]0.0[/code], the maximum amount of time peers can stay in the authenticating state, after which the authentication will automatically fail. See the [signal peer_authenticating] and [signal peer_authentication_failed] signals.
</member>
<member name="refuse_new_connections" type="bool" setter="set_refuse_new_connections" getter="is_refusing_new_connections" default="false">
If [code]true[/code], the MultiplayerAPI's [member MultiplayerAPI.multiplayer_peer] refuses new incoming connections.
</member>
@ -48,6 +83,18 @@
</member>
</members>
<signals>
<signal name="peer_authenticating">
<param index="0" name="id" type="int" />
<description>
Emitted when this MultiplayerAPI's [member MultiplayerAPI.multiplayer_peer] connects to a new peer and a valid [member auth_callback] is set. In this case, the [signal MultiplayerAPI.peer_connected] will not be emitted until [method complete_auth] is called with given peer [param id]. While in this state, the peer will not be included in the list returned by [method MultiplayerAPI.get_peers] (but in the one returned by [method get_authenticating_peers]), and only authentication data will be sent or received. See [method send_auth] for sending authentication data.
</description>
</signal>
<signal name="peer_authentication_failed">
<param index="0" name="id" type="int" />
<description>
Emitted when this MultiplayerAPI's [member MultiplayerAPI.multiplayer_peer] disconnects from a peer for which authentication had not yet completed. See [signal peer_authenticating].
</description>
</signal>
<signal name="peer_packet">
<param index="0" name="id" type="int" />
<param index="1" name="packet" type="PackedByteArray" />

View File

@ -51,14 +51,32 @@ void SceneMultiplayer::profile_bandwidth(const String &p_inout, int p_size) {
}
#endif
void SceneMultiplayer::_update_status() {
MultiplayerPeer::ConnectionStatus status = multiplayer_peer.is_valid() ? multiplayer_peer->get_connection_status() : MultiplayerPeer::CONNECTION_DISCONNECTED;
if (last_connection_status != status) {
if (status == MultiplayerPeer::CONNECTION_DISCONNECTED) {
if (last_connection_status == MultiplayerPeer::CONNECTION_CONNECTING) {
emit_signal(SNAME("connection_failed"));
} else {
emit_signal(SNAME("server_disconnected"));
}
clear();
}
last_connection_status = status;
}
}
Error SceneMultiplayer::poll() {
if (!multiplayer_peer.is_valid() || multiplayer_peer->get_connection_status() == MultiplayerPeer::CONNECTION_DISCONNECTED) {
return ERR_UNCONFIGURED;
_update_status();
if (last_connection_status == MultiplayerPeer::CONNECTION_DISCONNECTED) {
return OK;
}
multiplayer_peer->poll();
if (!multiplayer_peer.is_valid()) { // It's possible that polling might have resulted in a disconnection, so check here.
_update_status();
if (last_connection_status != MultiplayerPeer::CONNECTION_CONNECTED) {
// We might be still connecting, or polling might have resulted in a disconnection.
return OK;
}
@ -73,8 +91,48 @@ Error SceneMultiplayer::poll() {
Error err = multiplayer_peer->get_packet(&packet, len);
ERR_FAIL_COND_V_MSG(err != OK, err, vformat("Error getting packet! %d", err));
if (pending_peers.has(sender)) {
if (pending_peers[sender].local) {
// If the auth is over, admit the peer at the first packet.
pending_peers.erase(sender);
_admit_peer(sender);
} else {
ERR_CONTINUE(len < 2 || (packet[0] & CMD_MASK) != NETWORK_COMMAND_SYS || packet[1] != SYS_COMMAND_AUTH);
// Auth message.
PackedByteArray pba;
pba.resize(len - 2);
if (pba.size()) {
memcpy(pba.ptrw(), &packet[2], len - 2);
// User callback
const Variant sv = sender;
const Variant pbav = pba;
const Variant *argv[2] = { &sv, &pbav };
Variant ret;
Callable::CallError ce;
auth_callback.callp(argv, 2, ret, ce);
ERR_CONTINUE_MSG(ce.error != Callable::CallError::CALL_OK, "Failed to call authentication callback");
} else {
// Remote complete notification.
pending_peers[sender].remote = true;
if (pending_peers[sender].local) {
pending_peers.erase(sender);
_admit_peer(sender);
}
}
continue; // Auth in progress.
}
}
ERR_CONTINUE(!connected_peers.has(sender));
if (len && (packet[0] & CMD_MASK) == NETWORK_COMMAND_SYS) {
// Sys messages are processed separately since they might call _process_packet themselves.
if (len > 1 && packet[1] == SYS_COMMAND_AUTH) {
ERR_CONTINUE(len != 2);
// If we are here, we already admitted the peer locally, and this is just a confirmation packet.
continue;
}
_process_sys(sender, packet, len, mode, channel);
} else {
remote_sender_id = sender;
@ -82,17 +140,42 @@ Error SceneMultiplayer::poll() {
remote_sender_id = 0;
}
if (!multiplayer_peer.is_valid()) {
return OK; // It's also possible that a packet or RPC caused a disconnection, so also check here.
_update_status();
if (last_connection_status != MultiplayerPeer::CONNECTION_CONNECTED) { // It's possible that processing a packet might have resulted in a disconnection, so check here.
return OK;
}
}
if (pending_peers.size() && auth_timeout) {
HashSet<int> to_drop;
uint64_t time = OS::get_singleton()->get_ticks_msec();
for (const KeyValue<int, PendingPeer> &pending : pending_peers) {
if (pending.value.time + auth_timeout <= time) {
multiplayer_peer->disconnect_peer(pending.key);
to_drop.insert(pending.key);
}
}
for (const int &P : to_drop) {
// Each signal might trigger a disconnection.
pending_peers.erase(P);
emit_signal(SNAME("peer_authentication_failed"), P);
}
}
_update_status();
if (last_connection_status != MultiplayerPeer::CONNECTION_CONNECTED) { // Signals might have triggered disconnection.
return OK;
}
replicator->on_network_process();
return OK;
}
void SceneMultiplayer::clear() {
last_connection_status = MultiplayerPeer::CONNECTION_DISCONNECTED;
pending_peers.clear();
connected_peers.clear();
packet_cache.clear();
replicator->on_reset();
cache->clear();
relay_buffer->clear();
}
@ -117,9 +200,6 @@ void SceneMultiplayer::set_multiplayer_peer(const Ref<MultiplayerPeer> &p_peer)
if (multiplayer_peer.is_valid()) {
multiplayer_peer->disconnect("peer_connected", callable_mp(this, &SceneMultiplayer::_add_peer));
multiplayer_peer->disconnect("peer_disconnected", callable_mp(this, &SceneMultiplayer::_del_peer));
multiplayer_peer->disconnect("connection_succeeded", callable_mp(this, &SceneMultiplayer::_connected_to_server));
multiplayer_peer->disconnect("connection_failed", callable_mp(this, &SceneMultiplayer::_connection_failed));
multiplayer_peer->disconnect("server_disconnected", callable_mp(this, &SceneMultiplayer::_server_disconnected));
clear();
}
@ -128,11 +208,8 @@ void SceneMultiplayer::set_multiplayer_peer(const Ref<MultiplayerPeer> &p_peer)
if (multiplayer_peer.is_valid()) {
multiplayer_peer->connect("peer_connected", callable_mp(this, &SceneMultiplayer::_add_peer));
multiplayer_peer->connect("peer_disconnected", callable_mp(this, &SceneMultiplayer::_del_peer));
multiplayer_peer->connect("connection_succeeded", callable_mp(this, &SceneMultiplayer::_connected_to_server));
multiplayer_peer->connect("connection_failed", callable_mp(this, &SceneMultiplayer::_connection_failed));
multiplayer_peer->connect("server_disconnected", callable_mp(this, &SceneMultiplayer::_server_disconnected));
}
replicator->on_reset();
_update_status();
}
Ref<MultiplayerPeer> SceneMultiplayer::get_multiplayer_peer() {
@ -193,18 +270,19 @@ Error SceneMultiplayer::send_command(int p_to, const uint8_t *p_packet, int p_pa
const Vector<uint8_t> data = relay_buffer->get_data_array();
return multiplayer_peer->put_packet(data.ptr(), relay_buffer->get_position());
}
if (p_to < 0) {
if (p_to > 0) {
ERR_FAIL_COND_V(!connected_peers.has(p_to), ERR_BUG);
multiplayer_peer->set_target_peer(p_to);
return multiplayer_peer->put_packet(p_packet, p_packet_len);
} else {
for (const int &pid : connected_peers) {
if (pid == -p_to) {
if (p_to && pid == -p_to) {
continue;
}
multiplayer_peer->set_target_peer(pid);
multiplayer_peer->put_packet(p_packet, p_packet_len);
}
return OK;
} else {
multiplayer_peer->set_target_peer(p_to);
return multiplayer_peer->put_packet(p_packet, p_packet_len);
}
}
@ -215,7 +293,7 @@ void SceneMultiplayer::_process_sys(int p_from, const uint8_t *p_packet, int p_p
switch (sys_cmd_type) {
case SYS_COMMAND_ADD_PEER: {
ERR_FAIL_COND(!server_relay || !multiplayer_peer->is_server_relay_supported() || get_unique_id() == 1 || p_from != 1);
_add_peer(peer);
_admit_peer(peer); // Relayed peers are automatically accepted.
} break;
case SYS_COMMAND_DEL_PEER: {
ERR_FAIL_COND(!server_relay || !multiplayer_peer->is_server_relay_supported() || get_unique_id() == 1 || p_from != 1);
@ -273,6 +351,17 @@ void SceneMultiplayer::_process_sys(int p_from, const uint8_t *p_packet, int p_p
}
void SceneMultiplayer::_add_peer(int p_id) {
if (auth_callback.is_valid()) {
pending_peers[p_id] = PendingPeer();
pending_peers[p_id].time = OS::get_singleton()->get_ticks_msec();
emit_signal(SNAME("peer_authenticating"), p_id);
return;
} else {
_admit_peer(p_id);
}
}
void SceneMultiplayer::_admit_peer(int p_id) {
if (server_relay && get_unique_id() == 1 && multiplayer_peer->is_server_relay_supported()) {
// Notify others of connection, and send connected peers to newly connected one.
uint8_t buf[SYS_CMD_SIZE];
@ -295,10 +384,21 @@ void SceneMultiplayer::_add_peer(int p_id) {
connected_peers.insert(p_id);
cache->on_peer_change(p_id, true);
replicator->on_peer_change(p_id, true);
if (p_id == 1) {
emit_signal(SNAME("connected_to_server"));
}
emit_signal(SNAME("peer_connected"), p_id);
}
void SceneMultiplayer::_del_peer(int p_id) {
if (pending_peers.has(p_id)) {
pending_peers.erase(p_id);
emit_signal(SNAME("peer_authentication_failed"), p_id);
return;
} else if (!connected_peers.has(p_id)) {
return;
}
if (server_relay && get_unique_id() == 1 && multiplayer_peer->is_server_relay_supported()) {
// Notify others of disconnection.
uint8_t buf[SYS_CMD_SIZE];
@ -322,17 +422,14 @@ void SceneMultiplayer::_del_peer(int p_id) {
emit_signal(SNAME("peer_disconnected"), p_id);
}
void SceneMultiplayer::_connected_to_server() {
emit_signal(SNAME("connected_to_server"));
}
void SceneMultiplayer::_connection_failed() {
emit_signal(SNAME("connection_failed"));
}
void SceneMultiplayer::_server_disconnected() {
replicator->on_reset();
emit_signal(SNAME("server_disconnected"));
void SceneMultiplayer::disconnect_peer(int p_id) {
ERR_FAIL_COND(multiplayer_peer.is_null() || multiplayer_peer->get_connection_status() != MultiplayerPeer::CONNECTION_CONNECTED);
if (pending_peers.has(p_id)) {
pending_peers.erase(p_id);
} else if (connected_peers.has(p_id)) {
connected_peers.has(p_id);
}
multiplayer_peer->disconnect_peer(p_id);
}
Error SceneMultiplayer::send_bytes(Vector<uint8_t> p_data, int p_to, MultiplayerPeer::TransferMode p_mode, int p_channel) {
@ -353,6 +450,61 @@ Error SceneMultiplayer::send_bytes(Vector<uint8_t> p_data, int p_to, Multiplayer
return send_command(p_to, packet_cache.ptr(), p_data.size() + 1);
}
Error SceneMultiplayer::send_auth(int p_to, Vector<uint8_t> p_data) {
ERR_FAIL_COND_V(multiplayer_peer.is_null() || multiplayer_peer->get_connection_status() != MultiplayerPeer::CONNECTION_CONNECTED, ERR_UNCONFIGURED);
ERR_FAIL_COND_V(!pending_peers.has(p_to), ERR_INVALID_PARAMETER);
ERR_FAIL_COND_V(p_data.size() < 1, ERR_INVALID_PARAMETER);
ERR_FAIL_COND_V_MSG(pending_peers[p_to].local, ERR_FILE_CANT_WRITE, "The authentication session was previously marked as completed, no more authentication data can be sent.");
ERR_FAIL_COND_V_MSG(pending_peers[p_to].remote, ERR_FILE_CANT_WRITE, "The remote peer notified that the authentication session was completed, no more authentication data can be sent.");
if (packet_cache.size() < p_data.size() + 2) {
packet_cache.resize(p_data.size() + 2);
}
packet_cache.write[0] = NETWORK_COMMAND_SYS;
packet_cache.write[1] = SYS_COMMAND_AUTH;
memcpy(&packet_cache.write[2], p_data.ptr(), p_data.size());
multiplayer_peer->set_target_peer(p_to);
multiplayer_peer->set_transfer_channel(0);
multiplayer_peer->set_transfer_mode(MultiplayerPeer::TRANSFER_MODE_RELIABLE);
return multiplayer_peer->put_packet(packet_cache.ptr(), p_data.size() + 2);
}
Error SceneMultiplayer::complete_auth(int p_peer) {
ERR_FAIL_COND_V(multiplayer_peer.is_null() || multiplayer_peer->get_connection_status() != MultiplayerPeer::CONNECTION_CONNECTED, ERR_UNCONFIGURED);
ERR_FAIL_COND_V(!pending_peers.has(p_peer), ERR_INVALID_PARAMETER);
ERR_FAIL_COND_V_MSG(pending_peers[p_peer].local, ERR_FILE_CANT_WRITE, "The authentication session was already marked as completed.");
pending_peers[p_peer].local = true;
// Notify the remote peer that the authentication has completed.
uint8_t buf[2] = { NETWORK_COMMAND_SYS, SYS_COMMAND_AUTH };
Error err = multiplayer_peer->put_packet(buf, 2);
// The remote peer already reported the authentication as completed, so admit the peer.
// May generate new packets, so it must happen after sending confirmation.
if (pending_peers[p_peer].remote) {
pending_peers.erase(p_peer);
_admit_peer(p_peer);
}
return err;
}
void SceneMultiplayer::set_auth_callback(Callable p_callback) {
auth_callback = p_callback;
}
Callable SceneMultiplayer::get_auth_callback() const {
return auth_callback;
}
void SceneMultiplayer::set_auth_timeout(double p_timeout) {
ERR_FAIL_COND_MSG(p_timeout < 0, "Timeout must be greater or equal to 0 (where 0 means no timeout)");
auth_timeout = uint64_t(p_timeout * 1000);
}
double SceneMultiplayer::get_auth_timeout() const {
return double(auth_timeout) / 1000.0;
}
void SceneMultiplayer::_process_raw(int p_from, const uint8_t *p_packet, int p_packet_len) {
ERR_FAIL_COND_MSG(p_packet_len < 2, "Invalid packet received. Size too small.");
@ -392,6 +544,16 @@ Vector<int> SceneMultiplayer::get_peer_ids() {
return ret;
}
Vector<int> SceneMultiplayer::get_authenticating_peer_ids() {
Vector<int> out;
out.resize(pending_peers.size());
int idx = 0;
for (const KeyValue<int, PendingPeer> &E : pending_peers) {
out.write[idx++] = E.key;
}
return out;
}
void SceneMultiplayer::set_allow_object_decoding(bool p_enable) {
allow_object_decoding = p_enable;
}
@ -453,6 +615,18 @@ void SceneMultiplayer::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_root_path", "path"), &SceneMultiplayer::set_root_path);
ClassDB::bind_method(D_METHOD("get_root_path"), &SceneMultiplayer::get_root_path);
ClassDB::bind_method(D_METHOD("clear"), &SceneMultiplayer::clear);
ClassDB::bind_method(D_METHOD("disconnect_peer", "id"), &SceneMultiplayer::disconnect_peer);
ClassDB::bind_method(D_METHOD("get_authenticating_peers"), &SceneMultiplayer::get_authenticating_peer_ids);
ClassDB::bind_method(D_METHOD("send_auth", "id", "data"), &SceneMultiplayer::send_auth);
ClassDB::bind_method(D_METHOD("complete_auth", "id"), &SceneMultiplayer::complete_auth);
ClassDB::bind_method(D_METHOD("set_auth_callback", "callback"), &SceneMultiplayer::set_auth_callback);
ClassDB::bind_method(D_METHOD("get_auth_callback"), &SceneMultiplayer::get_auth_callback);
ClassDB::bind_method(D_METHOD("set_auth_timeout", "timeout"), &SceneMultiplayer::set_auth_timeout);
ClassDB::bind_method(D_METHOD("get_auth_timeout"), &SceneMultiplayer::get_auth_timeout);
ClassDB::bind_method(D_METHOD("set_refuse_new_connections", "refuse"), &SceneMultiplayer::set_refuse_new_connections);
ClassDB::bind_method(D_METHOD("is_refusing_new_connections"), &SceneMultiplayer::is_refusing_new_connections);
ClassDB::bind_method(D_METHOD("set_allow_object_decoding", "enable"), &SceneMultiplayer::set_allow_object_decoding);
@ -462,12 +636,16 @@ void SceneMultiplayer::_bind_methods() {
ClassDB::bind_method(D_METHOD("send_bytes", "bytes", "id", "mode", "channel"), &SceneMultiplayer::send_bytes, DEFVAL(MultiplayerPeer::TARGET_PEER_BROADCAST), DEFVAL(MultiplayerPeer::TRANSFER_MODE_RELIABLE), DEFVAL(0));
ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "root_path"), "set_root_path", "get_root_path");
ADD_PROPERTY(PropertyInfo(Variant::CALLABLE, "auth_callback"), "set_auth_callback", "get_auth_callback");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "auth_timeout", PROPERTY_HINT_RANGE, "0,30,0.1,or_greater,suffix:s"), "set_auth_timeout", "get_auth_timeout");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "allow_object_decoding"), "set_allow_object_decoding", "is_object_decoding_allowed");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "refuse_new_connections"), "set_refuse_new_connections", "is_refusing_new_connections");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "server_relay"), "set_server_relay_enabled", "is_server_relay_enabled");
ADD_PROPERTY_DEFAULT("refuse_new_connections", false);
ADD_SIGNAL(MethodInfo("peer_authenticating", PropertyInfo(Variant::INT, "id")));
ADD_SIGNAL(MethodInfo("peer_authentication_failed", PropertyInfo(Variant::INT, "id")));
ADD_SIGNAL(MethodInfo("peer_packet", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::PACKED_BYTE_ARRAY, "packet")));
}

View File

@ -53,6 +53,7 @@ public:
};
enum SysCommands {
SYS_COMMAND_AUTH,
SYS_COMMAND_ADD_PEER,
SYS_COMMAND_DEL_PEER,
SYS_COMMAND_RELAY,
@ -76,7 +77,17 @@ public:
};
private:
struct PendingPeer {
bool local = false;
bool remote = false;
uint64_t time = 0;
};
Ref<MultiplayerPeer> multiplayer_peer;
MultiplayerPeer::ConnectionStatus last_connection_status = MultiplayerPeer::CONNECTION_DISCONNECTED;
HashMap<int, PendingPeer> pending_peers; // true if locally finalized.
Callable auth_callback;
uint64_t auth_timeout = 3000;
HashSet<int> connected_peers;
int remote_sender_id = 0;
int remote_sender_override = 0;
@ -100,10 +111,9 @@ protected:
void _process_sys(int p_from, const uint8_t *p_packet, int p_packet_len, MultiplayerPeer::TransferMode p_mode, int p_channel);
void _add_peer(int p_id);
void _admit_peer(int p_id);
void _del_peer(int p_id);
void _connected_to_server();
void _connection_failed();
void _server_disconnected();
void _update_status();
public:
virtual void set_multiplayer_peer(const Ref<MultiplayerPeer> &p_peer) override;
@ -125,6 +135,16 @@ public:
void set_root_path(const NodePath &p_path);
NodePath get_root_path() const;
void disconnect_peer(int p_id);
Error send_auth(int p_to, Vector<uint8_t> p_bytes);
Error complete_auth(int p_peer);
void set_auth_callback(Callable p_callback);
Callable get_auth_callback() const;
void set_auth_timeout(double p_timeout);
double get_auth_timeout() const;
Vector<int> get_authenticating_peer_ids();
Error send_command(int p_to, const uint8_t *p_packet, int p_packet_len); // Used internally to relay packets when needed.
Error send_bytes(Vector<uint8_t> p_data, int p_to = MultiplayerPeer::TARGET_PEER_BROADCAST, MultiplayerPeer::TransferMode p_mode = MultiplayerPeer::TRANSFER_MODE_RELIABLE, int p_channel = 0);
String get_rpc_md5(const Object *p_obj);

View File

@ -6,7 +6,7 @@
<description>
This class constructs a full mesh of [WebRTCPeerConnection] (one connection for each peer) that can be used as a [member MultiplayerAPI.multiplayer_peer].
You can add each [WebRTCPeerConnection] via [method add_peer] or remove them via [method remove_peer]. Peers must be added in [constant WebRTCPeerConnection.STATE_NEW] state to allow it to create the appropriate channels. This class will not create offers nor set descriptions, it will only poll them, and notify connections and disconnections.
[signal MultiplayerPeer.connection_succeeded] and [signal MultiplayerPeer.server_disconnected] will not be emitted unless the peer is created using [method create_client]. Beside that data transfer works like in a [MultiplayerPeer].
When creating the peer via [method create_client] or [method create_server] the [method MultiplayerPeer.is_server_relay_supported] method will return [code]true[/code] enabling peer exchange and packet relaying when supported by the [MultiplayerAPI] implementation.
[b]Note:[/b] When exporting to Android, make sure to enable the [code]INTERNET[/code] permission in the Android export preset before exporting the project or using one-click deploy. Otherwise, network communication of any kind will be blocked by Android.
</description>
<tutorials>

View File

@ -341,11 +341,6 @@ void WebRTCMultiplayerPeer::remove_peer(int p_peer_id) {
peer->connected = false;
emit_signal(SNAME("peer_disconnected"), p_peer_id);
if (network_mode == MODE_CLIENT && p_peer_id == TARGET_PEER_SERVER) {
if (connection_status == CONNECTION_CONNECTING) {
emit_signal(SNAME("connection_failed"));
} else {
emit_signal(SNAME("server_disconnected"));
}
connection_status = CONNECTION_DISCONNECTED;
}
}

View File

@ -264,9 +264,7 @@ void WebSocketMultiplayerPeer::_poll_client() {
}
} else if (peer->get_ready_state() == WebSocketPeer::STATE_CLOSED) {
if (connection_status == CONNECTION_CONNECTED) {
emit_signal(SNAME("server_disconnected"));
} else {
emit_signal(SNAME("connection_failed"));
emit_signal(SNAME("peer_disconnected"), 1);
}
_clear();
return;
@ -276,7 +274,6 @@ void WebSocketMultiplayerPeer::_poll_client() {
ERR_FAIL_COND(!pending_peers.has(1)); // Bug.
if (OS::get_singleton()->get_ticks_msec() - pending_peers[1].time > handshake_timeout) {
print_verbose(vformat("WebSocket handshake timed out after %.3f seconds.", handshake_timeout * 0.001));
emit_signal(SNAME("connection_failed"));
_clear();
return;
}

View File

@ -123,9 +123,6 @@ void MultiplayerPeer::_bind_methods() {
ADD_SIGNAL(MethodInfo("peer_connected", PropertyInfo(Variant::INT, "id")));
ADD_SIGNAL(MethodInfo("peer_disconnected", PropertyInfo(Variant::INT, "id")));
ADD_SIGNAL(MethodInfo("server_disconnected"));
ADD_SIGNAL(MethodInfo("connection_succeeded"));
ADD_SIGNAL(MethodInfo("connection_failed"));
}
/*************/