0ad/source/network/StunClient.cpp
wraitii ee0d204bf6 Wrap JSAutoRequest and replace usage of JSContext* with the wrapper.
JSAutoRequest is required before calling into most JSAPI methods, for GC
reasons.
Calling it is required and fragile as one must not forget.
Further, SM52 and later make manipulating JSContext* dangerous as that
can cross Compartment(Realm in SM68) barriers (and ScriptInterface now
matches a Compartment).

The solution to both problems is to avoid using JSContext* in 0 A.D.
itself. To achieve this, a Request class is introduced, and must be used
to access a JSContext* from a scriptInterface. Further, Request is
passed to other ScriptInterface functions isntead of JSContext*, making
it obvious that the caller has already called it, reducing errors and
redundant JSAutoRequest calls.
Only JSNative functions now get a naked JSContext* without protection,
but the likelihood of forgetting a request is lower since many
ScriptInterface functions now expect it.

JSContext* is directly passed to JSAPI functions only.

Part of the SM52 migration, stage: SM45 compatible

Based on a patch by: Itms
Tested By: Freagarach
Refs #4893

Differential Revision: https://code.wildfiregames.com/D3088
This was SVN commit r24176.
2020-11-13 13:18:22 +00:00

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));
ScriptInterface::Request 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);
}
}