wraitii
25490bfec3
- Check for pending exceptions after function calls and script executions. - Call LOGERROR instead of JS_ReportError when there is a conversion error in FromJSVal, since that can only be called from C++ (where JS errors don't really make sense). Instead, C++ callers of FromJSVal should handle the failure and, themselves, either report an error or simply do something else. - Wrap JS_ReportError since that makes updating it later easier. This isn't a systematical fix since ToJSVal also ought return a boolean for failures, and we probably should trigger errors instead of warnings on 'implicit' conversions, rather a preparation diff. Part of the SM52 migration, stage: SM45 compatible (actually SM52 incompatible, too). Based on a patch by: Itms Comments by: Vladislavbelov, Stan` Refs #742, #4893 Differential Revision: https://code.wildfiregames.com/D3093 This was SVN commit r24187.
434 lines
11 KiB
C++
434 lines
11 KiB
C++
/* Copyright (C) 2020 Wildfire Games.
|
|
* Copyright (C) 2013-2016 SuperTuxKart-Team.
|
|
* This file is part of 0 A.D.
|
|
*
|
|
* 0 A.D. is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* 0 A.D. is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "precompiled.h"
|
|
|
|
#include "StunClient.h"
|
|
|
|
#include <chrono>
|
|
#include <cstdio>
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include <sys/types.h>
|
|
#ifdef WIN32
|
|
# include <winsock2.h>
|
|
# include <ws2tcpip.h>
|
|
#else
|
|
# include <sys/socket.h>
|
|
# include <netdb.h>
|
|
#endif
|
|
|
|
#include <vector>
|
|
|
|
#include "lib/external_libraries/enet.h"
|
|
|
|
#if OS_WIN
|
|
#include "lib/sysdep/os/win/wposix/wtime.h"
|
|
#endif
|
|
|
|
#include "scriptinterface/ScriptInterface.h"
|
|
#include "ps/CLogger.h"
|
|
#include "ps/ConfigDB.h"
|
|
|
|
unsigned int m_StunServerIP;
|
|
int m_StunServerPort;
|
|
|
|
/**
|
|
* These constants are defined in Section 6 of RFC 5389.
|
|
*/
|
|
const u32 m_MagicCookie = 0x2112A442;
|
|
const u32 m_MethodTypeBinding = 0x0001;
|
|
const u32 m_BindingSuccessResponse = 0x0101;
|
|
|
|
/**
|
|
* Bit determining whether comprehension of an attribute is optional.
|
|
* Described in Section 15 of RFC 5389.
|
|
*/
|
|
const u16 m_ComprehensionOptional = 0x1 << 15;
|
|
|
|
/**
|
|
* Bit determining whether the bit was assigned by IETF Review.
|
|
* Described in section 18.1. of RFC 5389.
|
|
*/
|
|
const u16 m_IETFReview = 0x1 << 14;
|
|
|
|
/**
|
|
* These constants are defined in Section 15.1 of RFC 5389.
|
|
*/
|
|
const u8 m_IPAddressFamilyIPv4 = 0x01;
|
|
|
|
/**
|
|
* These constants are defined in Section 18.2 of RFC 5389.
|
|
*/
|
|
const u16 m_AttrTypeMappedAddress = 0x001;
|
|
const u16 m_AttrTypeXORMappedAddress = 0x0020;
|
|
|
|
/**
|
|
* Described in section 3 of RFC 5389.
|
|
*/
|
|
u8 m_TransactionID[12];
|
|
|
|
/**
|
|
* Discovered STUN endpoint
|
|
*/
|
|
u32 m_IP;
|
|
u16 m_Port;
|
|
|
|
void AddUInt16(std::vector<u8>& buffer, const u16 value)
|
|
{
|
|
buffer.push_back((value >> 8) & 0xff);
|
|
buffer.push_back(value & 0xff);
|
|
}
|
|
|
|
void AddUInt32(std::vector<u8>& buffer, const u32 value)
|
|
{
|
|
buffer.push_back((value >> 24) & 0xff);
|
|
buffer.push_back((value >> 16) & 0xff);
|
|
buffer.push_back((value >> 8) & 0xff);
|
|
buffer.push_back( value & 0xff);
|
|
}
|
|
|
|
template<typename T, size_t n>
|
|
bool GetFromBuffer(const std::vector<u8>& buffer, u32& offset, T& result)
|
|
{
|
|
if (offset + n > buffer.size())
|
|
return false;
|
|
|
|
int a = n;
|
|
offset += n;
|
|
while (a--)
|
|
{
|
|
// Prevent shift count overflow if the type is u8
|
|
if (n > 1)
|
|
result <<= 8;
|
|
|
|
result += buffer[offset - 1 - a];
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Creates a STUN request and sends it to a STUN server.
|
|
* The request is sent through transactionHost, from which the answer
|
|
* will be retrieved by ReceiveStunResponse and interpreted by ParseStunResponse.
|
|
*/
|
|
bool CreateStunRequest(ENetHost& transactionHost)
|
|
{
|
|
CStr server_name;
|
|
CFG_GET_VAL("lobby.stun.server", server_name);
|
|
CFG_GET_VAL("lobby.stun.port", m_StunServerPort);
|
|
|
|
debug_printf("GetPublicAddress: Using STUN server %s:%d\n", server_name.c_str(), m_StunServerPort);
|
|
|
|
addrinfo hints;
|
|
addrinfo* res;
|
|
|
|
memset(&hints, 0, sizeof(hints));
|
|
hints.ai_family = AF_UNSPEC; // AF_INET or AF_INET6 to force version
|
|
hints.ai_socktype = SOCK_STREAM;
|
|
|
|
// Resolve the stun server name so we can send it a STUN request
|
|
int status = getaddrinfo(server_name.c_str(), nullptr, &hints, &res);
|
|
if (status != 0)
|
|
{
|
|
#ifdef UNICODE
|
|
LOGERROR("GetPublicAddress: Error in getaddrinfo: %s", utf8_from_wstring(gai_strerror(status)));
|
|
#else
|
|
LOGERROR("GetPublicAddress: Error in getaddrinfo: %s", gai_strerror(status));
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
ENSURE(res);
|
|
|
|
// Documentation says it points to "one or more addrinfo structures"
|
|
sockaddr_in* current_interface = reinterpret_cast<sockaddr_in*>(res->ai_addr);
|
|
m_StunServerIP = ntohl(current_interface->sin_addr.s_addr);
|
|
|
|
StunClient::SendStunRequest(transactionHost, m_StunServerIP, m_StunServerPort);
|
|
|
|
freeaddrinfo(res);
|
|
return true;
|
|
}
|
|
|
|
void StunClient::SendStunRequest(ENetHost& transactionHost, u32 targetIp, u16 targetPort)
|
|
{
|
|
std::vector<u8> buffer;
|
|
AddUInt16(buffer, m_MethodTypeBinding);
|
|
AddUInt16(buffer, 0); // length
|
|
AddUInt32(buffer, m_MagicCookie);
|
|
|
|
for (std::size_t i = 0; i < sizeof(m_TransactionID); ++i)
|
|
{
|
|
u8 random_byte = rand() % 256;
|
|
buffer.push_back(random_byte);
|
|
m_TransactionID[i] = random_byte;
|
|
}
|
|
|
|
sockaddr_in to;
|
|
int to_len = sizeof(to);
|
|
memset(&to, 0, to_len);
|
|
|
|
to.sin_family = AF_INET;
|
|
to.sin_port = htons(targetPort);
|
|
to.sin_addr.s_addr = htonl(targetIp);
|
|
|
|
sendto(
|
|
transactionHost.socket,
|
|
reinterpret_cast<char*>(buffer.data()),
|
|
static_cast<int>(buffer.size()),
|
|
0,
|
|
reinterpret_cast<sockaddr*>(&to),
|
|
to_len);
|
|
}
|
|
|
|
/**
|
|
* Gets the response from the STUN server and checks it for its validity.
|
|
*/
|
|
bool ReceiveStunResponse(ENetHost& transactionHost, std::vector<u8>& buffer)
|
|
{
|
|
// TransportAddress sender;
|
|
const int LEN = 2048;
|
|
char input_buffer[LEN];
|
|
|
|
memset(input_buffer, 0, LEN);
|
|
|
|
sockaddr_in addr;
|
|
socklen_t from_len = sizeof(addr);
|
|
|
|
int len = recvfrom(transactionHost.socket, input_buffer, LEN, 0, reinterpret_cast<sockaddr*>(&addr), &from_len);
|
|
|
|
int delay = 200;
|
|
CFG_GET_VAL("lobby.stun.delay", delay);
|
|
|
|
// Wait to receive the message because enet sockets are non-blocking
|
|
const int max_tries = 5;
|
|
for (int count = 0; len < 0 && (count < max_tries || max_tries == -1); ++count)
|
|
{
|
|
usleep(delay * 1000);
|
|
len = recvfrom(transactionHost.socket, input_buffer, LEN, 0, reinterpret_cast<sockaddr*>(&addr), &from_len);
|
|
}
|
|
|
|
if (len < 0)
|
|
{
|
|
LOGERROR("GetPublicAddress: recvfrom error (%d): %s", errno, strerror(errno));
|
|
return false;
|
|
}
|
|
|
|
u32 sender_ip = ntohl(static_cast<u32>(addr.sin_addr.s_addr));
|
|
u16 sender_port = ntohs(addr.sin_port);
|
|
|
|
if (sender_ip != m_StunServerIP)
|
|
LOGERROR("GetPublicAddress: Received stun response from different address: %d:%d (%d.%d.%d.%d:%d) %s",
|
|
addr.sin_addr.s_addr,
|
|
addr.sin_port,
|
|
(sender_ip >> 24) & 0xff,
|
|
(sender_ip >> 16) & 0xff,
|
|
(sender_ip >> 8) & 0xff,
|
|
(sender_ip >> 0) & 0xff,
|
|
sender_port,
|
|
input_buffer);
|
|
|
|
// Convert to network string.
|
|
buffer.resize(len);
|
|
memcpy(buffer.data(), reinterpret_cast<u8*>(input_buffer), len);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ParseStunResponse(const std::vector<u8>& buffer)
|
|
{
|
|
u32 offset = 0;
|
|
|
|
u16 responseType = 0;
|
|
if (!GetFromBuffer<u16, 2>(buffer, offset, responseType) || responseType != m_BindingSuccessResponse)
|
|
{
|
|
LOGERROR("STUN response isn't a binding success response");
|
|
return false;
|
|
}
|
|
|
|
// Ignore message size
|
|
offset += 2;
|
|
|
|
u32 cookie = 0;
|
|
if (!GetFromBuffer<u32, 4>(buffer, offset, cookie) || cookie != m_MagicCookie)
|
|
{
|
|
LOGERROR("STUN response doesn't contain the magic cookie");
|
|
return false;
|
|
}
|
|
|
|
for (std::size_t i = 0; i < sizeof(m_TransactionID); ++i)
|
|
{
|
|
u8 transactionChar = 0;
|
|
if (!GetFromBuffer<u8, 1>(buffer, offset, transactionChar) || transactionChar != m_TransactionID[i])
|
|
{
|
|
LOGERROR("STUN response doesn't contain the transaction ID");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
while (offset < buffer.size())
|
|
{
|
|
u16 type = 0;
|
|
u16 size = 0;
|
|
if (!GetFromBuffer<u16, 2>(buffer, offset, type) ||
|
|
!GetFromBuffer<u16, 2>(buffer, offset, size))
|
|
{
|
|
LOGERROR("STUN response contains invalid attribute");
|
|
return false;
|
|
}
|
|
|
|
// The first two bits are irrelevant to the type
|
|
type &= ~(m_ComprehensionOptional | m_IETFReview);
|
|
|
|
switch (type)
|
|
{
|
|
case m_AttrTypeMappedAddress:
|
|
case m_AttrTypeXORMappedAddress:
|
|
{
|
|
if (size != 8)
|
|
{
|
|
LOGERROR("Invalid STUN Mapped Address length");
|
|
return false;
|
|
}
|
|
|
|
// Ignore the first byte as mentioned in Section 15.1 of RFC 5389.
|
|
++offset;
|
|
|
|
u8 ipFamily = 0;
|
|
if (!GetFromBuffer<u8, 1>(buffer, offset, ipFamily) || ipFamily != m_IPAddressFamilyIPv4)
|
|
{
|
|
LOGERROR("Unsupported address family, IPv4 is expected");
|
|
return false;
|
|
}
|
|
|
|
u16 port = 0;
|
|
u32 ip = 0;
|
|
if (!GetFromBuffer<u16, 2>(buffer, offset, port) ||
|
|
!GetFromBuffer<u32, 4>(buffer, offset, ip))
|
|
{
|
|
LOGERROR("Mapped address doesn't contain IP and port");
|
|
return false;
|
|
}
|
|
|
|
// Obfuscation is described in Section 15.2 of RFC 5389.
|
|
if (type == m_AttrTypeXORMappedAddress)
|
|
{
|
|
port ^= m_MagicCookie >> 16;
|
|
ip ^= m_MagicCookie;
|
|
}
|
|
|
|
m_Port = port;
|
|
m_IP = ip;
|
|
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
// We don't care about other attributes at all
|
|
|
|
// Skip attribute
|
|
offset += size;
|
|
|
|
// Skip padding
|
|
int padding = size % 4;
|
|
if (padding)
|
|
offset += 4 - padding;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool STUNRequestAndResponse(ENetHost& transactionHost)
|
|
{
|
|
if (!CreateStunRequest(transactionHost))
|
|
return false;
|
|
|
|
std::vector<u8> buffer;
|
|
return ReceiveStunResponse(transactionHost, buffer) &&
|
|
ParseStunResponse(buffer);
|
|
}
|
|
|
|
JS::Value StunClient::FindStunEndpointHost(const ScriptInterface& scriptInterface, int port)
|
|
{
|
|
ENetAddress hostAddr{ENET_HOST_ANY, static_cast<u16>(port)};
|
|
ENetHost* transactionHost = enet_host_create(&hostAddr, 1, 1, 0, 0);
|
|
if (!transactionHost)
|
|
{
|
|
LOGERROR("FindStunEndpointHost: Failed to create enet host");
|
|
return JS::UndefinedValue();
|
|
}
|
|
|
|
bool success = STUNRequestAndResponse(*transactionHost);
|
|
enet_host_destroy(transactionHost);
|
|
if (!success)
|
|
return JS::UndefinedValue();
|
|
|
|
// Convert m_IP to string
|
|
char ipStr[256] = "(error)";
|
|
ENetAddress addr;
|
|
addr.host = ntohl(m_IP);
|
|
enet_address_get_host_ip(&addr, ipStr, ARRAY_SIZE(ipStr));
|
|
|
|
ScriptRequest rq(scriptInterface);
|
|
|
|
JS::RootedValue stunEndpoint(rq.cx);
|
|
ScriptInterface::CreateObject(rq, &stunEndpoint, "ip", ipStr, "port", m_Port);
|
|
return stunEndpoint;
|
|
}
|
|
|
|
bool StunClient::FindStunEndpointJoin(ENetHost& transactionHost, StunClient::StunEndpoint& stunEndpoint)
|
|
{
|
|
if (!STUNRequestAndResponse(transactionHost))
|
|
return false;
|
|
|
|
// Convert m_IP to string
|
|
char ipStr[256] = "(error)";
|
|
ENetAddress addr;
|
|
addr.host = ntohl(m_IP);
|
|
enet_address_get_host_ip(&addr, ipStr, ARRAY_SIZE(ipStr));
|
|
|
|
stunEndpoint.ip = m_IP;
|
|
stunEndpoint.port = m_Port;
|
|
|
|
return true;
|
|
}
|
|
|
|
void StunClient::SendHolePunchingMessages(ENetHost& enetClient, const std::string& serverAddress, u16 serverPort)
|
|
{
|
|
// Convert ip string to int64
|
|
ENetAddress addr;
|
|
addr.port = serverPort;
|
|
enet_address_set_host(&addr, serverAddress.c_str());
|
|
|
|
int delay = 200;
|
|
CFG_GET_VAL("lobby.stun.delay", delay);
|
|
|
|
// Send an UDP message from enet host to ip:port
|
|
for (int i = 0; i < 3; ++i)
|
|
{
|
|
StunClient::SendStunRequest(enetClient, htonl(addr.host), serverPort);
|
|
usleep(delay * 1000);
|
|
}
|
|
}
|