citra-emu
/
citra-canary
Archived
1
0
Fork 0

Libnetwork: add password protected rooms, guid, and error fixes (#3068)

* Network: Add password protected roomsand GUID

* Limit chat message size
This commit is contained in:
B3n30 2017-11-19 19:52:37 +01:00 committed by GitHub
parent 4071da5012
commit c0a7afaa5c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 139 additions and 33 deletions

View File

@ -4,8 +4,10 @@
#include <algorithm> #include <algorithm>
#include <atomic> #include <atomic>
#include <iomanip>
#include <mutex> #include <mutex>
#include <random> #include <random>
#include <sstream>
#include <thread> #include <thread>
#include "enet/enet.h" #include "enet/enet.h"
#include "network/packet.h" #include "network/packet.h"
@ -13,9 +15,6 @@
namespace Network { namespace Network {
/// Maximum number of concurrent connections allowed to this room.
static constexpr u32 MaxConcurrentConnections = 10;
class Room::RoomImpl { class Room::RoomImpl {
public: public:
// This MAC address is used to generate a 'Nintendo' like Mac address. // This MAC address is used to generate a 'Nintendo' like Mac address.
@ -27,6 +26,8 @@ public:
std::atomic<State> state{State::Closed}; ///< Current state of the room. std::atomic<State> state{State::Closed}; ///< Current state of the room.
RoomInformation room_information; ///< Information about this room. RoomInformation room_information; ///< Information about this room.
std::string password; ///< The password required to connect to this room.
struct Member { struct Member {
std::string nickname; ///< The nickname of the member. std::string nickname; ///< The nickname of the member.
GameInfo game_info; ///< The current game of the member GameInfo game_info; ///< The current game of the member
@ -81,6 +82,11 @@ public:
*/ */
void SendVersionMismatch(ENetPeer* client); void SendVersionMismatch(ENetPeer* client);
/**
* Sends a ID_ROOM_WRONG_PASSWORD message telling the client that the password is wrong.
*/
void SendWrongPassword(ENetPeer* client);
/** /**
* Notifies the member that its connection attempt was successful, * Notifies the member that its connection attempt was successful,
* and it is now part of the room. * and it is now part of the room.
@ -99,6 +105,8 @@ public:
* <MessageID>ID_ROOM_INFORMATION * <MessageID>ID_ROOM_INFORMATION
* <String> room_name * <String> room_name
* <u32> member_slots: The max number of clients allowed in this room * <u32> member_slots: The max number of clients allowed in this room
* <String> uid
* <u16> port
* <u32> num_members: the number of currently joined clients * <u32> num_members: the number of currently joined clients
* This is followed by the following three values for each member: * This is followed by the following three values for each member:
* <String> nickname of that member * <String> nickname of that member
@ -136,13 +144,18 @@ public:
* to all other clients. * to all other clients.
*/ */
void HandleClientDisconnection(ENetPeer* client); void HandleClientDisconnection(ENetPeer* client);
/**
* Creates a random ID in the form 12345678-1234-1234-1234-123456789012
*/
void CreateUniqueID();
}; };
// RoomImpl // RoomImpl
void Room::RoomImpl::ServerLoop() { void Room::RoomImpl::ServerLoop() {
while (state != State::Closed) { while (state != State::Closed) {
ENetEvent event; ENetEvent event;
if (enet_host_service(server, &event, 100) > 0) { if (enet_host_service(server, &event, 50) > 0) {
switch (event.type) { switch (event.type) {
case ENET_EVENT_TYPE_RECEIVE: case ENET_EVENT_TYPE_RECEIVE:
switch (event.packet->data[0]) { switch (event.packet->data[0]) {
@ -188,6 +201,14 @@ void Room::RoomImpl::HandleJoinRequest(const ENetEvent* event) {
u32 client_version; u32 client_version;
packet >> client_version; packet >> client_version;
std::string pass;
packet >> pass;
if (pass != password) {
SendWrongPassword(event->peer);
return;
}
if (!IsValidNickname(nickname)) { if (!IsValidNickname(nickname)) {
SendNameCollision(event->peer); SendNameCollision(event->peer);
return; return;
@ -260,6 +281,16 @@ void Room::RoomImpl::SendMacCollision(ENetPeer* client) {
enet_host_flush(server); enet_host_flush(server);
} }
void Room::RoomImpl::SendWrongPassword(ENetPeer* client) {
Packet packet;
packet << static_cast<u8>(IdWrongPassword);
ENetPacket* enet_packet =
enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE);
enet_peer_send(client, 0, enet_packet);
enet_host_flush(server);
}
void Room::RoomImpl::SendVersionMismatch(ENetPeer* client) { void Room::RoomImpl::SendVersionMismatch(ENetPeer* client) {
Packet packet; Packet packet;
packet << static_cast<u8>(IdVersionMismatch); packet << static_cast<u8>(IdVersionMismatch);
@ -301,6 +332,9 @@ void Room::RoomImpl::BroadcastRoomInformation() {
packet << static_cast<u8>(IdRoomInformation); packet << static_cast<u8>(IdRoomInformation);
packet << room_information.name; packet << room_information.name;
packet << room_information.member_slots; packet << room_information.member_slots;
packet << room_information.uid;
packet << room_information.port;
packet << room_information.preferred_game;
packet << static_cast<u32>(members.size()); packet << static_cast<u32>(members.size());
{ {
@ -382,6 +416,9 @@ void Room::RoomImpl::HandleChatPacket(const ENetEvent* event) {
return; // Received a chat message from a unknown sender return; // Received a chat message from a unknown sender
} }
// Limit the size of chat messages to MaxMessageSize
message.resize(MaxMessageSize);
Packet out_packet; Packet out_packet;
out_packet << static_cast<u8>(IdChatMessage); out_packet << static_cast<u8>(IdChatMessage);
out_packet << sending_member->nickname; out_packet << sending_member->nickname;
@ -433,12 +470,28 @@ void Room::RoomImpl::HandleClientDisconnection(ENetPeer* client) {
BroadcastRoomInformation(); BroadcastRoomInformation();
} }
void Room::RoomImpl::CreateUniqueID() {
std::uniform_int_distribution<> dis(0, 9999);
std::ostringstream stream;
stream << std::setfill('0') << std::setw(4) << dis(random_gen);
stream << std::setfill('0') << std::setw(4) << dis(random_gen) << "-";
stream << std::setfill('0') << std::setw(4) << dis(random_gen) << "-";
stream << std::setfill('0') << std::setw(4) << dis(random_gen) << "-";
stream << std::setfill('0') << std::setw(4) << dis(random_gen) << "-";
stream << std::setfill('0') << std::setw(4) << dis(random_gen);
stream << std::setfill('0') << std::setw(4) << dis(random_gen);
stream << std::setfill('0') << std::setw(4) << dis(random_gen);
room_information.uid = stream.str();
}
// Room // Room
Room::Room() : room_impl{std::make_unique<RoomImpl>()} {} Room::Room() : room_impl{std::make_unique<RoomImpl>()} {}
Room::~Room() = default; Room::~Room() = default;
void Room::Create(const std::string& name, const std::string& server_address, u16 server_port) { bool Room::Create(const std::string& name, const std::string& server_address, u16 server_port,
const std::string& password, const u32 max_connections,
const std::string& preferred_game, u64 preferred_game_id) {
ENetAddress address; ENetAddress address;
address.host = ENET_HOST_ANY; address.host = ENET_HOST_ANY;
if (!server_address.empty()) { if (!server_address.empty()) {
@ -446,13 +499,22 @@ void Room::Create(const std::string& name, const std::string& server_address, u1
} }
address.port = server_port; address.port = server_port;
room_impl->server = enet_host_create(&address, MaxConcurrentConnections, NumChannels, 0, 0); room_impl->server = enet_host_create(&address, max_connections, NumChannels, 0, 0);
// TODO(B3N30): Allow specifying the maximum number of concurrent connections. if (!room_impl->server) {
return false;
}
room_impl->state = State::Open; room_impl->state = State::Open;
room_impl->room_information.name = name; room_impl->room_information.name = name;
room_impl->room_information.member_slots = MaxConcurrentConnections; room_impl->room_information.member_slots = max_connections;
room_impl->room_information.port = server_port;
room_impl->room_information.preferred_game = preferred_game;
room_impl->room_information.preferred_game_id = preferred_game_id;
room_impl->password = password;
room_impl->CreateUniqueID();
room_impl->StartLoop(); room_impl->StartLoop();
return true;
} }
Room::State Room::GetState() const { Room::State Room::GetState() const {
@ -474,7 +536,11 @@ std::vector<Room::Member> Room::GetRoomMemberList() const {
member_list.push_back(member); member_list.push_back(member);
} }
return member_list; return member_list;
}; }
bool Room::HasPassword() const {
return !room_impl->password.empty();
}
void Room::Destroy() { void Room::Destroy() {
room_impl->state = State::Closed; room_impl->state = State::Closed;

View File

@ -14,12 +14,22 @@ namespace Network {
constexpr u32 network_version = 1; ///< The version of this Room and RoomMember constexpr u32 network_version = 1; ///< The version of this Room and RoomMember
constexpr u16 DefaultRoomPort = 1234; constexpr u16 DefaultRoomPort = 24872;
constexpr u32 MaxMessageSize = 500;
/// Maximum number of concurrent connections allowed to this room.
static constexpr u32 MaxConcurrentConnections = 254;
constexpr size_t NumChannels = 1; // Number of channels used for the connection constexpr size_t NumChannels = 1; // Number of channels used for the connection
struct RoomInformation { struct RoomInformation {
std::string name; ///< Name of the server std::string name; ///< Name of the server
u32 member_slots; ///< Maximum number of members in this room u32 member_slots; ///< Maximum number of members in this room
std::string uid; ///< The unique ID of the room
u16 port; ///< The port of this room
std::string preferred_game; ///< Game to advertise that you want to play
u64 preferred_game_id; ///< Title ID for the advertised game
}; };
struct GameInfo { struct GameInfo {
@ -46,6 +56,7 @@ enum RoomMessageTypes : u8 {
IdNameCollision, IdNameCollision,
IdMacCollision, IdMacCollision,
IdVersionMismatch, IdVersionMismatch,
IdWrongPassword,
IdCloseRoom IdCloseRoom
}; };
@ -81,12 +92,19 @@ public:
*/ */
std::vector<Member> GetRoomMemberList() const; std::vector<Member> GetRoomMemberList() const;
/**
* Checks if the room is password protected
*/
bool HasPassword() const;
/** /**
* Creates the socket for this room. Will bind to default address if * Creates the socket for this room. Will bind to default address if
* server is empty string. * server is empty string.
*/ */
void Create(const std::string& name, const std::string& server = "", bool Create(const std::string& name, const std::string& server = "",
u16 server_port = DefaultRoomPort); u16 server_port = DefaultRoomPort, const std::string& password = "",
const u32 max_connections = MaxConcurrentConnections,
const std::string& preferred_game = "", u64 preferred_game_id = 0);
/** /**
* Destroys the socket * Destroys the socket

View File

@ -74,10 +74,12 @@ public:
* nickname and preferred mac. * nickname and preferred mac.
* @params nickname The desired nickname. * @params nickname The desired nickname.
* @params preferred_mac The preferred MAC address to use in the room, the NoPreferredMac tells * @params preferred_mac The preferred MAC address to use in the room, the NoPreferredMac tells
* @params password The password for the room
* the server to assign one for us. * the server to assign one for us.
*/ */
void SendJoinRequest(const std::string& nickname, void SendJoinRequest(const std::string& nickname,
const MacAddress& preferred_mac = NoPreferredMac); const MacAddress& preferred_mac = NoPreferredMac,
const std::string& password = "");
/** /**
* Extracts a MAC Address from a received ENet packet. * Extracts a MAC Address from a received ENet packet.
@ -161,6 +163,9 @@ void RoomMember::RoomMemberImpl::MemberLoop() {
case IdVersionMismatch: case IdVersionMismatch:
SetState(State::WrongVersion); SetState(State::WrongVersion);
break; break;
case IdWrongPassword:
SetState(State::WrongPassword);
break;
case IdCloseRoom: case IdCloseRoom:
SetState(State::LostConnection); SetState(State::LostConnection);
break; break;
@ -196,12 +201,14 @@ void RoomMember::RoomMemberImpl::Send(Packet&& packet) {
} }
void RoomMember::RoomMemberImpl::SendJoinRequest(const std::string& nickname, void RoomMember::RoomMemberImpl::SendJoinRequest(const std::string& nickname,
const MacAddress& preferred_mac) { const MacAddress& preferred_mac,
const std::string& password) {
Packet packet; Packet packet;
packet << static_cast<u8>(IdJoinRequest); packet << static_cast<u8>(IdJoinRequest);
packet << nickname; packet << nickname;
packet << preferred_mac; packet << preferred_mac;
packet << network_version; packet << network_version;
packet << password;
Send(std::move(packet)); Send(std::move(packet));
} }
@ -215,8 +222,13 @@ void RoomMember::RoomMemberImpl::HandleRoomInformationPacket(const ENetEvent* ev
RoomInformation info{}; RoomInformation info{};
packet >> info.name; packet >> info.name;
packet >> info.member_slots; packet >> info.member_slots;
packet >> info.uid;
packet >> info.port;
packet >> info.preferred_game;
room_information.name = info.name; room_information.name = info.name;
room_information.member_slots = info.member_slots; room_information.member_slots = info.member_slots;
room_information.port = info.port;
room_information.preferred_game = info.preferred_game;
u32 num_members; u32 num_members;
packet >> num_members; packet >> num_members;
@ -260,10 +272,6 @@ void RoomMember::RoomMemberImpl::HandleWifiPackets(const ENetEvent* event) {
packet >> wifi_packet.channel; packet >> wifi_packet.channel;
packet >> wifi_packet.transmitter_address; packet >> wifi_packet.transmitter_address;
packet >> wifi_packet.destination_address; packet >> wifi_packet.destination_address;
u32 data_length;
packet >> data_length;
packet >> wifi_packet.data; packet >> wifi_packet.data;
Invoke<WifiPacket>(wifi_packet); Invoke<WifiPacket>(wifi_packet);
@ -348,14 +356,13 @@ RoomMember::CallbackHandle<T> RoomMember::RoomMemberImpl::Bind(
} }
// RoomMember // RoomMember
RoomMember::RoomMember() : room_member_impl{std::make_unique<RoomMemberImpl>()} { RoomMember::RoomMember() : room_member_impl{std::make_unique<RoomMemberImpl>()} {}
room_member_impl->client = enet_host_create(nullptr, 1, NumChannels, 0, 0);
ASSERT_MSG(room_member_impl->client != nullptr, "Could not create client");
}
RoomMember::~RoomMember() { RoomMember::~RoomMember() {
ASSERT_MSG(!IsConnected(), "RoomMember is being destroyed while connected"); ASSERT_MSG(!IsConnected(), "RoomMember is being destroyed while connected");
enet_host_destroy(room_member_impl->client); if (room_member_impl->loop_thread) {
Leave();
}
} }
RoomMember::State RoomMember::GetState() const { RoomMember::State RoomMember::GetState() const {
@ -380,18 +387,22 @@ RoomInformation RoomMember::GetRoomInformation() const {
} }
void RoomMember::Join(const std::string& nick, const char* server_addr, u16 server_port, void RoomMember::Join(const std::string& nick, const char* server_addr, u16 server_port,
u16 client_port, const MacAddress& preferred_mac) { u16 client_port, const MacAddress& preferred_mac,
const std::string& password) {
// If the member is connected, kill the connection first // If the member is connected, kill the connection first
if (room_member_impl->loop_thread && room_member_impl->loop_thread->joinable()) { if (room_member_impl->loop_thread && room_member_impl->loop_thread->joinable()) {
room_member_impl->SetState(State::Error); Leave();
room_member_impl->loop_thread->join();
room_member_impl->loop_thread.reset();
} }
// If the thread isn't running but the ptr still exists, reset it // If the thread isn't running but the ptr still exists, reset it
else if (room_member_impl->loop_thread) { else if (room_member_impl->loop_thread) {
room_member_impl->loop_thread.reset(); room_member_impl->loop_thread.reset();
} }
if (!room_member_impl->client) {
room_member_impl->client = enet_host_create(nullptr, 1, NumChannels, 0, 0);
ASSERT_MSG(room_member_impl->client != nullptr, "Could not create client");
}
ENetAddress address{}; ENetAddress address{};
enet_address_set_host(&address, server_addr); enet_address_set_host(&address, server_addr);
address.port = server_port; address.port = server_port;
@ -409,9 +420,10 @@ void RoomMember::Join(const std::string& nick, const char* server_addr, u16 serv
room_member_impl->nickname = nick; room_member_impl->nickname = nick;
room_member_impl->SetState(State::Joining); room_member_impl->SetState(State::Joining);
room_member_impl->StartLoop(); room_member_impl->StartLoop();
room_member_impl->SendJoinRequest(nick, preferred_mac); room_member_impl->SendJoinRequest(nick, preferred_mac, password);
SendGameInfo(room_member_impl->current_game_info); SendGameInfo(room_member_impl->current_game_info);
} else { } else {
enet_peer_disconnect(room_member_impl->server, 0);
room_member_impl->SetState(State::CouldNotConnect); room_member_impl->SetState(State::CouldNotConnect);
} }
} }
@ -480,6 +492,9 @@ void RoomMember::Leave() {
room_member_impl->SetState(State::Idle); room_member_impl->SetState(State::Idle);
room_member_impl->loop_thread->join(); room_member_impl->loop_thread->join();
room_member_impl->loop_thread.reset(); room_member_impl->loop_thread.reset();
enet_host_destroy(room_member_impl->client);
room_member_impl->client = nullptr;
} }
template void RoomMember::Unbind(CallbackHandle<WifiPacket>); template void RoomMember::Unbind(CallbackHandle<WifiPacket>);

View File

@ -16,7 +16,13 @@ namespace Network {
/// Information about the received WiFi packets. /// Information about the received WiFi packets.
/// Acts as our own 802.11 header. /// Acts as our own 802.11 header.
struct WifiPacket { struct WifiPacket {
enum class PacketType : u8 { Beacon, Data, Authentication, AssociationResponse }; enum class PacketType : u8 {
Beacon,
Data,
Authentication,
AssociationResponse,
Deauthentication
};
PacketType type; ///< The type of 802.11 frame. PacketType type; ///< The type of 802.11 frame.
std::vector<u8> data; ///< Raw 802.11 frame data, starting at the management frame header std::vector<u8> data; ///< Raw 802.11 frame data, starting at the management frame header
/// for management frames. /// for management frames.
@ -49,6 +55,7 @@ public:
NameCollision, ///< Somebody is already using this name NameCollision, ///< Somebody is already using this name
MacCollision, ///< Somebody is already using that mac-address MacCollision, ///< Somebody is already using that mac-address
WrongVersion, ///< The room version is not the same as for this RoomMember WrongVersion, ///< The room version is not the same as for this RoomMember
WrongPassword, ///< The password doesn't match the one from the Room
CouldNotConnect ///< The room is not responding to a connection attempt CouldNotConnect ///< The room is not responding to a connection attempt
}; };
@ -109,8 +116,8 @@ public:
* This may fail if the username is already taken. * This may fail if the username is already taken.
*/ */
void Join(const std::string& nickname, const char* server_addr = "127.0.0.1", void Join(const std::string& nickname, const char* server_addr = "127.0.0.1",
const u16 serverPort = DefaultRoomPort, const u16 clientPort = 0, const u16 server_port = DefaultRoomPort, const u16 client_port = 0,
const MacAddress& preferred_mac = NoPreferredMac); const MacAddress& preferred_mac = NoPreferredMac, const std::string& password = "");
/** /**
* Sends a WiFi packet to the room. * Sends a WiFi packet to the room.