From afb1012bcd7e7aea2428aadb195b04ef72fcf861 Mon Sep 17 00:00:00 2001 From: B3n30 Date: Sat, 30 Sep 2017 18:18:45 +0200 Subject: [PATCH] Services/UDS: Handle the rest of the connection sequence. (#2963) Services/UDS: Handle the rest of the connection sequence. --- src/core/hle/service/nwm/nwm_uds.cpp | 121 +++++++++++++++++++++++--- src/core/hle/service/nwm/uds_data.cpp | 80 ++++++++++++++++- src/core/hle/service/nwm/uds_data.h | 70 +++++++++++++-- 3 files changed, 251 insertions(+), 20 deletions(-) diff --git a/src/core/hle/service/nwm/nwm_uds.cpp b/src/core/hle/service/nwm/nwm_uds.cpp index 8ef0cda091..0aa63cc1e1 100644 --- a/src/core/hle/service/nwm/nwm_uds.cpp +++ b/src/core/hle/service/nwm/nwm_uds.cpp @@ -15,6 +15,7 @@ #include "core/hle/ipc_helpers.h" #include "core/hle/kernel/event.h" #include "core/hle/kernel/shared_memory.h" +#include "core/hle/lock.h" #include "core/hle/result.h" #include "core/hle/service/nwm/nwm_uds.h" #include "core/hle/service/nwm/uds_beacon.h" @@ -100,6 +101,20 @@ void SendPacket(Network::WifiPacket& packet) { // TODO(Subv): Implement. } +/* + * Returns an available index in the nodes array for the + * currently-hosted UDS network. + */ +static u16 GetNextAvailableNodeId() { + for (u16 index = 0; index < connection_status.max_nodes; ++index) { + if ((connection_status.node_bitmask & (1 << index)) == 0) + return index; + } + + // Any connection attempts to an already full network should have been refused. + ASSERT_MSG(false, "No available connection slots in the network"); +} + // Inserts the received beacon frame in the beacon queue and removes any older beacons if the size // limit is exceeded. void HandleBeaconFrame(const Network::WifiPacket& packet) { @@ -143,18 +158,88 @@ void HandleAssociationResponseFrame(const Network::WifiPacket& packet) { SendPacket(eapol_start); } -/* - * Returns an available index in the nodes array for the - * currently-hosted UDS network. - */ -static u16 GetNextAvailableNodeId() { - for (u16 index = 0; index < connection_status.max_nodes; ++index) { - if ((connection_status.node_bitmask & (1 << index)) == 0) - return index; - } +static void HandleEAPoLPacket(const Network::WifiPacket& packet) { + std::lock_guard lock(connection_status_mutex); - // Any connection attempts to an already full network should have been refused. - ASSERT_MSG(false, "No available connection slots in the network"); + if (GetEAPoLFrameType(packet.data) == EAPoLStartMagic) { + if (connection_status.status != static_cast(NetworkStatus::ConnectedAsHost)) { + LOG_DEBUG(Service_NWM, "Connection sequence aborted, because connection status is %u", + connection_status.status); + return; + } + + auto node = DeserializeNodeInfoFromFrame(packet.data); + + if (connection_status.max_nodes == connection_status.total_nodes) { + // Reject connection attempt + LOG_ERROR(Service_NWM, "Reached maximum nodes, but reject packet wasn't sent."); + // TODO(B3N30): Figure out what packet is sent here + return; + } + + // Get an unused network node id + u16 node_id = GetNextAvailableNodeId(); + node.network_node_id = node_id + 1; + + connection_status.node_bitmask |= 1 << node_id; + connection_status.changed_nodes |= 1 << node_id; + connection_status.nodes[node_id] = node.network_node_id; + connection_status.total_nodes++; + + u8 current_nodes = network_info.total_nodes; + node_info[current_nodes] = node; + + network_info.total_nodes++; + + // Send the EAPoL-Logoff packet. + using Network::WifiPacket; + WifiPacket eapol_logoff; + eapol_logoff.channel = network_channel; + eapol_logoff.data = + GenerateEAPoLLogoffFrame(packet.transmitter_address, node.network_node_id, node_info, + network_info.max_nodes, network_info.total_nodes); + // TODO(Subv): Encrypt the packet. + eapol_logoff.destination_address = packet.transmitter_address; + eapol_logoff.type = WifiPacket::PacketType::Data; + + SendPacket(eapol_logoff); + // TODO(B3N30): Broadcast updated node list + // The 3ds does this presumably to support spectators. + std::lock_guard lock(HLE::g_hle_lock); + connection_status_event->Signal(); + } else { + if (connection_status.status != static_cast(NetworkStatus::NotConnected)) { + LOG_DEBUG(Service_NWM, "Connection sequence aborted, because connection status is %u", + connection_status.status); + return; + } + auto logoff = ParseEAPoLLogoffFrame(packet.data); + + network_info.total_nodes = logoff.connected_nodes; + network_info.max_nodes = logoff.max_nodes; + + connection_status.network_node_id = logoff.assigned_node_id; + connection_status.total_nodes = logoff.connected_nodes; + connection_status.max_nodes = logoff.max_nodes; + + node_info.clear(); + node_info.reserve(network_info.max_nodes); + for (size_t index = 0; index < logoff.connected_nodes; ++index) { + connection_status.node_bitmask |= 1 << index; + connection_status.changed_nodes |= 1 << index; + connection_status.nodes[index] = logoff.nodes[index].network_node_id; + + node_info.emplace_back(DeserializeNodeInfo(logoff.nodes[index])); + } + + // We're now connected, signal the application + connection_status.status = static_cast(NetworkStatus::ConnectedAsClient); + // Some games require ConnectToNetwork to block, for now it doesn't + // If blocking is implemented this lock needs to be changed, + // otherwise it might cause deadlocks + std::lock_guard lock(HLE::g_hle_lock); + connection_status_event->Signal(); + } } /* @@ -238,6 +323,17 @@ void HandleAuthenticationFrame(const Network::WifiPacket& packet) { } } +static void HandleDataFrame(const Network::WifiPacket& packet) { + switch (GetFrameEtherType(packet.data)) { + case EtherType::EAPoL: + HandleEAPoLPacket(packet); + break; + case EtherType::SecureData: + // TODO(B3N30): Handle SecureData packets + break; + } +} + /// Callback to parse and handle a received wifi packet. void OnWifiPacketReceived(const Network::WifiPacket& packet) { switch (packet.type) { @@ -250,6 +346,9 @@ void OnWifiPacketReceived(const Network::WifiPacket& packet) { case Network::WifiPacket::PacketType::AssociationResponse: HandleAssociationResponseFrame(packet); break; + case Network::WifiPacket::PacketType::Data: + HandleDataFrame(packet); + break; } } diff --git a/src/core/hle/service/nwm/uds_data.cpp b/src/core/hle/service/nwm/uds_data.cpp index 3ef2a84b63..4b389710fa 100644 --- a/src/core/hle/service/nwm/uds_data.cpp +++ b/src/core/hle/service/nwm/uds_data.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include #include #include #include @@ -277,10 +278,10 @@ std::vector GenerateDataPayload(const std::vector& data, u8 channel, u16 std::vector GenerateEAPoLStartFrame(u16 association_id, const NodeInfo& node_info) { EAPoLStartPacket eapol_start{}; eapol_start.association_id = association_id; - eapol_start.friend_code_seed = node_info.friend_code_seed; + eapol_start.node.friend_code_seed = node_info.friend_code_seed; - for (int i = 0; i < node_info.username.size(); ++i) - eapol_start.username[i] = node_info.username[i]; + std::copy(node_info.username.begin(), node_info.username.end(), + eapol_start.node.username.begin()); // Note: The network_node_id and unknown bytes seem to be uninitialized in the NWM module. // TODO(B3N30): The last 8 bytes seem to have a fixed value of 07 88 15 00 04 e9 13 00 in @@ -295,5 +296,78 @@ std::vector GenerateEAPoLStartFrame(u16 association_id, const NodeInfo& node return buffer; } +EtherType GetFrameEtherType(const std::vector& frame) { + LLCHeader header; + std::memcpy(&header, frame.data(), sizeof(header)); + + u16 ethertype = header.protocol; + return static_cast(ethertype); +} + +u16 GetEAPoLFrameType(const std::vector& frame) { + // Ignore the LLC header + u16_be eapol_type; + std::memcpy(&eapol_type, frame.data() + sizeof(LLCHeader), sizeof(eapol_type)); + return eapol_type; +} + +NodeInfo DeserializeNodeInfoFromFrame(const std::vector& frame) { + EAPoLStartPacket eapol_start; + + // Skip the LLC header + std::memcpy(&eapol_start, frame.data() + sizeof(LLCHeader), sizeof(eapol_start)); + + NodeInfo node{}; + node.friend_code_seed = eapol_start.node.friend_code_seed; + + std::copy(eapol_start.node.username.begin(), eapol_start.node.username.end(), + node.username.begin()); + + return node; +} + +NodeInfo DeserializeNodeInfo(const EAPoLNodeInfo& node) { + NodeInfo node_info{}; + node_info.friend_code_seed = node.friend_code_seed; + node_info.network_node_id = node.network_node_id; + + std::copy(node.username.begin(), node.username.end(), node_info.username.begin()); + + return node_info; +} + +std::vector GenerateEAPoLLogoffFrame(const MacAddress& mac_address, u16 network_node_id, + const NodeList& nodes, u8 max_nodes, u8 total_nodes) { + EAPoLLogoffPacket eapol_logoff{}; + eapol_logoff.assigned_node_id = network_node_id; + eapol_logoff.connected_nodes = total_nodes; + eapol_logoff.max_nodes = max_nodes; + + for (size_t index = 0; index < total_nodes; ++index) { + const auto& node_info = nodes[index]; + auto& node = eapol_logoff.nodes[index]; + + node.friend_code_seed = node_info.friend_code_seed; + node.network_node_id = node_info.network_node_id; + + std::copy(node_info.username.begin(), node_info.username.end(), node.username.begin()); + } + + std::vector eapol_buffer(sizeof(EAPoLLogoffPacket)); + std::memcpy(eapol_buffer.data(), &eapol_logoff, sizeof(eapol_logoff)); + + std::vector buffer = GenerateLLCHeader(EtherType::EAPoL); + buffer.insert(buffer.end(), eapol_buffer.begin(), eapol_buffer.end()); + return buffer; +} + +EAPoLLogoffPacket ParseEAPoLLogoffFrame(const std::vector& frame) { + EAPoLLogoffPacket eapol_logoff; + + // Skip the LLC header + std::memcpy(&eapol_logoff, frame.data() + sizeof(LLCHeader), sizeof(eapol_logoff)); + return eapol_logoff; +} + } // namespace NWM } // namespace Service diff --git a/src/core/hle/service/nwm/uds_data.h b/src/core/hle/service/nwm/uds_data.h index 76e8f546b5..76bccb1bfa 100644 --- a/src/core/hle/service/nwm/uds_data.h +++ b/src/core/hle/service/nwm/uds_data.h @@ -8,6 +8,7 @@ #include #include "common/common_types.h" #include "common/swap.h" +#include "core/hle/service/nwm/uds_beacon.h" #include "core/hle/service/service.h" namespace Service { @@ -67,6 +68,16 @@ struct DataFrameCryptoCTR { static_assert(sizeof(DataFrameCryptoCTR) == 16, "DataFrameCryptoCTR has the wrong size"); +struct EAPoLNodeInfo { + u64_be friend_code_seed; + std::array username; + INSERT_PADDING_BYTES(4); + u16_be network_node_id; + INSERT_PADDING_BYTES(6); +}; + +static_assert(sizeof(EAPoLNodeInfo) == 0x28, "EAPoLNodeInfo has the wrong size"); + constexpr u16 EAPoLStartMagic = 0x201; /* @@ -78,16 +89,28 @@ struct EAPoLStartPacket { // This value is hardcoded to 1 in the NWM module. u16_be unknown = 1; INSERT_PADDING_BYTES(2); - - u64_be friend_code_seed; - std::array username; - INSERT_PADDING_BYTES(4); - u16_be network_node_id; - INSERT_PADDING_BYTES(6); + EAPoLNodeInfo node; }; static_assert(sizeof(EAPoLStartPacket) == 0x30, "EAPoLStartPacket has the wrong size"); +constexpr u16 EAPoLLogoffMagic = 0x202; + +struct EAPoLLogoffPacket { + u16_be magic = EAPoLLogoffMagic; + INSERT_PADDING_BYTES(2); + u16_be assigned_node_id; + MacAddress client_mac_address; + INSERT_PADDING_BYTES(6); + u8 connected_nodes; + u8 max_nodes; + INSERT_PADDING_BYTES(4); + + std::array nodes; +}; + +static_assert(sizeof(EAPoLLogoffPacket) == 0x298, "EAPoLLogoffPacket has the wrong size"); + /** * Generates an unencrypted 802.11 data payload. * @returns The generated frame payload. @@ -102,5 +125,40 @@ std::vector GenerateDataPayload(const std::vector& data, u8 channel, u16 */ std::vector GenerateEAPoLStartFrame(u16 association_id, const NodeInfo& node_info); +/* + * Returns the EtherType of the specified 802.11 frame. + */ +EtherType GetFrameEtherType(const std::vector& frame); + +/* + * Returns the EAPoL type (Start / Logoff) of the specified 802.11 frame. + * Note: The frame *must* be an EAPoL frame. + */ +u16 GetEAPoLFrameType(const std::vector& frame); + +/* + * Returns a deserialized NodeInfo structure from the information inside an EAPoL-Start packet + * encapsulated in an 802.11 data frame. + */ +NodeInfo DeserializeNodeInfoFromFrame(const std::vector& frame); + +/* + * Returns a NodeInfo constructed from the data in the specified EAPoLNodeInfo. + */ +NodeInfo DeserializeNodeInfo(const EAPoLNodeInfo& node); + +/* + * Generates an unencrypted 802.11 data frame body with the EAPoL-Logoff format for UDS + * communication. + * @returns The generated frame body. + */ +std::vector GenerateEAPoLLogoffFrame(const MacAddress& mac_address, u16 network_node_id, + const NodeList& nodes, u8 max_nodes, u8 total_nodes); + +/* + * Returns a EAPoLLogoffPacket representing the specified 802.11-encapsulated data frame. + */ +EAPoLLogoffPacket ParseEAPoLLogoffFrame(const std::vector& frame); + } // namespace NWM } // namespace Service