diff --git a/Source/Core/Core/HW/EXI/BBA/BuiltIn.cpp b/Source/Core/Core/HW/EXI/BBA/BuiltIn.cpp index 3af54b4f23..d0b169da71 100644 --- a/Source/Core/Core/HW/EXI/BBA/BuiltIn.cpp +++ b/Source/Core/Core/HW/EXI/BBA/BuiltIn.cpp @@ -92,6 +92,9 @@ bool CEXIETHERNET::BuiltInBBAInterface::Activate() ref.ip = 0; } + m_upnp_httpd.listen(Common::SSDP_PORT, sf::IpAddress(ip)); + m_upnp_httpd.setBlocking(false); + return RecvInit(); } @@ -116,6 +119,7 @@ void CEXIETHERNET::BuiltInBBAInterface::Deactivate() } m_arp_table.clear(); + m_upnp_httpd.close(); // Wait for read thread to exit. if (m_read_thread.joinable()) @@ -298,18 +302,30 @@ void CEXIETHERNET::BuiltInBBAInterface::HandleTCPFrame(const Common::TCPPacket& sf::IpAddress target; StackRef* ref = GetTCPSlot(tcp_header.source_port, tcp_header.destination_port, Common::BitCast(ip_header.destination_addr)); - const u16 properties = ntohs(tcp_header.properties); - if (properties & (TCP_FLAG_FIN | TCP_FLAG_RST)) + const u16 flags = ntohs(tcp_header.properties) & 0xfff; + if (flags & (TCP_FLAG_FIN | TCP_FLAG_RST)) { if (ref == nullptr) return; // not found - ref->ack_num++; + ref->ack_num += 1 + static_cast(data.size()); WriteToQueue(BuildFINFrame(ref)); ref->ip = 0; + if (!data.empty()) + ref->tcp_socket.send(data.data(), data.size()); ref->tcp_socket.disconnect(); } - else if (properties & TCP_FLAG_SIN) + else if (flags == (TCP_FLAG_SIN | TCP_FLAG_ACK)) + { + if (ref == nullptr) + return; // not found + + ref->seq_num++; + ref->ack_num = ntohl(tcp_header.sequence_number) + 1; + ref->ready = true; + WriteToQueue(BuildAckFrame(ref)); + } + else if (flags & TCP_FLAG_SIN) { // new connection if (ref != nullptr) @@ -322,7 +338,7 @@ void CEXIETHERNET::BuiltInBBAInterface::HandleTCPFrame(const Common::TCPPacket& ref->ack_num = ntohl(tcp_header.sequence_number) + 1; ref->ack_base = ref->ack_num; ref->seq_num = 0x1000000; - ref->window_size = ntohl(tcp_header.window_size); + ref->window_size = ntohs(tcp_header.window_size); ref->type = IPPROTO_TCP; for (auto& tcp_buf : ref->tcp_buffers) tcp_buf.used = false; @@ -339,7 +355,10 @@ void CEXIETHERNET::BuiltInBBAInterface::HandleTCPFrame(const Common::TCPPacket& Common::TCPPacket result(ref->bba_mac, ref->my_mac, ref->from, ref->to, ref->seq_num, ref->ack_num, TCP_FLAG_SIN | TCP_FLAG_ACK); - result.tcp_options = {0x02, 0x04, 0x05, 0xb4, 0x01, 0x01, 0x01, 0x01}; + result.tcp_options = { + 0x02, 0x04, 0x05, 0xb4, // Maximum segment size: 1460 bytes + 0x01, 0x01, 0x01, 0x01 // NOPs + }; ref->seq_num++; target = sf::IpAddress(ntohl(destination_ip)); @@ -488,6 +507,48 @@ void CEXIETHERNET::BuiltInBBAInterface::HandleUDPFrame(const Common::UDPPacket& ref->udp_socket.send(data.data(), data.size(), target, ntohs(udp_header.destination_port)); } +void CEXIETHERNET::BuiltInBBAInterface::HandleUPnPClient() +{ + StackRef* ref = GetAvailableSlot(0); + if (m_upnp_httpd.accept(ref->tcp_socket) != sf::Socket::Done) + return; + + if (ref->tcp_socket.GetPeerName(&ref->from) != sf::Socket::Status::Done || + ref->tcp_socket.GetSockName(&ref->to) != sf::Socket::Status::Done) + { + ERROR_LOG_FMT(SP1, "Failed to accept new UPnP client: {}", Common::StrNetworkError()); + return; + } + + ref->delay = GetTickCountStd(); + ref->ip = ref->from.sin_addr.s_addr; + ref->local = ref->to.sin_port; + ref->remote = ref->from.sin_port; + ref->ack_num = 0; + ref->ack_base = ref->ack_num; + ref->seq_num = 0x1000000; + ref->window_size = 8192; + ref->type = IPPROTO_TCP; + for (auto& tcp_buf : ref->tcp_buffers) + tcp_buf.used = false; + ref->bba_mac = m_current_mac; + ref->my_mac = ResolveAddress(ref->from.sin_addr.s_addr); + ref->tcp_socket.setBlocking(false); + ref->ready = false; + + Common::TCPPacket result(ref->bba_mac, ref->my_mac, ref->from, ref->to, ref->seq_num, + ref->ack_num, TCP_FLAG_SIN); + // Based on Nintendont packet capture of Mario Kart: Double Dash!! + result.tcp_options = { + 0x02, 0x04, 0x05, 0xb4, // Maximum segment size: 1460 bytes + 0x01, // NOP + 0x03, 0x03, 0x08, // Window scale: 8 (multiply by 256) + 0x01, 0x01, // NOPs + 0x04, 0x02 // SACK permitted + }; + WriteToQueue(result.Build()); +} + const Common::MACAddress& CEXIETHERNET::BuiltInBBAInterface::ResolveAddress(u32 inet_ip) { auto it = m_arp_table.lower_bound(inet_ip); @@ -669,6 +730,9 @@ void CEXIETHERNET::BuiltInBBAInterface::ReadThreadHandler(CEXIETHERNET::BuiltInB } } + // Check for new UPnP client + self->HandleUPnPClient(); + if (datasize > 0) { u8* buffer = reinterpret_cast(self->m_eth_ref->mRecvBuffer.get()); @@ -678,7 +742,12 @@ void CEXIETHERNET::BuiltInBBAInterface::ReadThreadHandler(CEXIETHERNET::BuiltInB { SetIPIdentification(buffer, datasize, ++self->m_ip_frame_id); } - self->m_eth_ref->mRecvBufferLength = datasize > 64 ? static_cast(datasize) : 64; + if (datasize < 64) + { + std::fill(buffer + datasize, buffer + 64, 0); + datasize = 64; + } + self->m_eth_ref->mRecvBufferLength = static_cast(datasize); self->m_eth_ref->RecvHandlePacket(); } } @@ -727,6 +796,28 @@ sf::Socket::Status BbaTcpSocket::Connect(const sf::IpAddress& dest, u16 port, u3 return this->connect(dest, port); } +sf::Socket::Status BbaTcpSocket::GetPeerName(sockaddr_in* addr) const +{ + socklen_t size = sizeof(*addr); + if (getpeername(getHandle(), reinterpret_cast(addr), &size) == -1) + { + ERROR_LOG_FMT(SP1, "getpeername failed: {}", Common::StrNetworkError()); + return sf::Socket::Status::Error; + } + return sf::Socket::Status::Done; +} + +sf::Socket::Status BbaTcpSocket::GetSockName(sockaddr_in* addr) const +{ + socklen_t size = sizeof(*addr); + if (getsockname(getHandle(), reinterpret_cast(addr), &size) == -1) + { + ERROR_LOG_FMT(SP1, "getsockname failed: {}", Common::StrNetworkError()); + return sf::Socket::Status::Error; + } + return sf::Socket::Status::Done; +} + BbaUdpSocket::BbaUdpSocket() = default; sf::Socket::Status BbaUdpSocket::Bind(u16 port, u32 net_ip) diff --git a/Source/Core/Core/HW/EXI/BBA/BuiltIn.h b/Source/Core/Core/HW/EXI/BBA/BuiltIn.h index 35e74eae43..4478f4de48 100644 --- a/Source/Core/Core/HW/EXI/BBA/BuiltIn.h +++ b/Source/Core/Core/HW/EXI/BBA/BuiltIn.h @@ -5,6 +5,7 @@ #ifdef _WIN32 #include +using socklen_t = int; #else #include #endif @@ -45,6 +46,8 @@ public: BbaTcpSocket(); sf::Socket::Status Connect(const sf::IpAddress& dest, u16 port, u32 net_ip); + sf::Socket::Status GetPeerName(sockaddr_in* addr) const; + sf::Socket::Status GetSockName(sockaddr_in* addr) const; }; class BbaUdpSocket : public sf::UdpSocket diff --git a/Source/Core/Core/HW/EXI/EXI_DeviceEthernet.h b/Source/Core/Core/HW/EXI/EXI_DeviceEthernet.h index 4a6c4019f5..48697f704e 100644 --- a/Source/Core/Core/HW/EXI/EXI_DeviceEthernet.h +++ b/Source/Core/Core/HW/EXI/EXI_DeviceEthernet.h @@ -449,6 +449,7 @@ private: u32 m_router_ip = 0; Common::MACAddress m_router_mac{}; std::map m_arp_table; + sf::TcpListener m_upnp_httpd; #if defined(WIN32) || defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__) || \ defined(__OpenBSD__) || defined(__NetBSD__) || defined(__HAIKU__) std::array network_ref{}; // max 10 at same time, i think most gc game had a @@ -468,6 +469,7 @@ private: void HandleTCPFrame(const Common::TCPPacket& packet); void InitUDPPort(u16 port); void HandleUDPFrame(const Common::UDPPacket& packet); + void HandleUPnPClient(); const Common::MACAddress& ResolveAddress(u32 inet_ip); };