0ad/source/network/StunClient.cpp
elexis a84e2e57df Use ScriptInterface::CreateObject for ToJSVal<CColor>, and for ToJSVal<Grid<u8> >, ToJSVal<Grid<u16> > used by the AIManager obtaining the pathfinder grid.
Make that function static, so that it can be used for these functions
without slowly having to obtain the ScriptInterface instance using
GetScriptInterfaceAndCBData just to get the JSContext again.
Remove few redundant conversions for CreateObject arguments.

Differential Revision: https://code.wildfiregames.com/D2128
Tested on: gcc 9.1.0, clang 8.0.1, Jenkins
Tedious performance testing in: D2128, D2127

This was SVN commit r22894.
2019-09-13 00:56:51 +00:00

435 lines
11 KiB
C++

/* Copyright (C) 2019 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));
JSContext* cx = scriptInterface.GetContext();
JSAutoRequest rq(cx);
JS::RootedValue stunEndpoint(cx);
ScriptInterface::CreateObject(cx, &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);
}
}