Changeset View
Changeset View
Standalone View
Standalone View
ps/trunk/source/network/StunClient.cpp
Show All 14 Lines | |||||
* You should have received a copy of the GNU General Public License | * 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/>. | * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>. | ||||
*/ | */ | ||||
#include "precompiled.h" | #include "precompiled.h" | ||||
#include "StunClient.h" | #include "StunClient.h" | ||||
#include "lib/sysdep/os.h" | #include "lib/byte_order.h" | ||||
#include <chrono> | |||||
#include <cstdio> | |||||
#include <stdlib.h> | |||||
#include <string.h> | |||||
#include <sys/types.h> | |||||
#if OS_WIN | |||||
# 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 "ps/CLogger.h" | #include "ps/CLogger.h" | ||||
#include "ps/ConfigDB.h" | #include "ps/ConfigDB.h" | ||||
#include "ps/CStr.h" | #include "ps/CStr.h" | ||||
unsigned int m_StunServerIP; | #include "lib/external_libraries/enet.h" | ||||
int m_StunServerPort; | |||||
#include <chrono> | |||||
#include <vector> | |||||
#include <thread> | |||||
namespace StunClient | |||||
{ | |||||
/** | /** | ||||
* These constants are defined in Section 6 of RFC 5389. | * These constants are defined in Section 6 of RFC 5389. | ||||
*/ | */ | ||||
const u32 m_MagicCookie = 0x2112A442; | const u32 m_MagicCookie = 0x2112A442; | ||||
const u32 m_MethodTypeBinding = 0x0001; | const u16 m_MethodTypeBinding = 0x01; | ||||
const u32 m_BindingSuccessResponse = 0x0101; | const u32 m_BindingSuccessResponse = 0x0101; | ||||
/** | /** | ||||
* Bit determining whether comprehension of an attribute is optional. | * Bit determining whether comprehension of an attribute is optional. | ||||
* Described in Section 15 of RFC 5389. | * Described in Section 15 of RFC 5389. | ||||
*/ | */ | ||||
const u16 m_ComprehensionOptional = 0x1 << 15; | const u16 m_ComprehensionOptional = 0x1 << 15; | ||||
Show All 14 Lines | |||||
const u16 m_AttrTypeMappedAddress = 0x001; | const u16 m_AttrTypeMappedAddress = 0x001; | ||||
const u16 m_AttrTypeXORMappedAddress = 0x0020; | const u16 m_AttrTypeXORMappedAddress = 0x0020; | ||||
/** | /** | ||||
* Described in section 3 of RFC 5389. | * Described in section 3 of RFC 5389. | ||||
*/ | */ | ||||
u8 m_TransactionID[12]; | u8 m_TransactionID[12]; | ||||
ENetAddress m_StunServer; | |||||
/** | /** | ||||
* Discovered STUN endpoint | * Public IP + port discovered via the STUN transaction. | ||||
*/ | */ | ||||
u32 m_IP; | ENetAddress m_PublicAddress; | ||||
u16 m_Port; | |||||
void AddUInt16(std::vector<u8>& buffer, const u16 value) | /** | ||||
{ | * Push POD data to a network-byte-order buffer. | ||||
buffer.push_back((value >> 8) & 0xff); | * TODO: this should be optimised & moved to byte_order.h | ||||
buffer.push_back(value & 0xff); | */ | ||||
} | template<typename T, size_t n = sizeof(T)> | ||||
void AddToBuffer(std::vector<u8>& buffer, const T value) | |||||
void AddUInt32(std::vector<u8>& buffer, const u32 value) | |||||
{ | { | ||||
buffer.push_back((value >> 24) & 0xff); | static_assert(std::is_pod_v<T>, "T must be POD"); | ||||
buffer.push_back((value >> 16) & 0xff); | buffer.reserve(buffer.size() + n); | ||||
buffer.push_back((value >> 8) & 0xff); | // std::byte* can alias anything so this is legal. | ||||
buffer.push_back( value & 0xff); | const std::byte* ptr = reinterpret_cast<const std::byte*>(&value); | ||||
for (size_t a = 0; a < n; ++a) | |||||
#if BYTE_ORDER == LITTLE_ENDIAN | |||||
buffer.push_back(static_cast<u8>(*(ptr + n - 1 - a))); | |||||
#else | |||||
buffer.push_back(static_cast<u8>(*(ptr + a))); | |||||
#endif | |||||
} | } | ||||
template<typename T, size_t n> | /** | ||||
* Read POD data from a network-byte-order buffer. | |||||
* TODO: this should be optimised & moved to byte_order.h | |||||
*/ | |||||
template<typename T, size_t n = sizeof(T)> | |||||
bool GetFromBuffer(const std::vector<u8>& buffer, u32& offset, T& result) | bool GetFromBuffer(const std::vector<u8>& buffer, u32& offset, T& result) | ||||
{ | { | ||||
static_assert(std::is_pod_v<T>, "T must be POD"); | |||||
if (offset + n > buffer.size()) | if (offset + n > buffer.size()) | ||||
return false; | return false; | ||||
int a = n; | // std::byte* can alias anything so this is legal. | ||||
std::byte* ptr = reinterpret_cast<std::byte*>(&result); | |||||
for (size_t a = 0; a < n; ++a) | |||||
#if BYTE_ORDER == LITTLE_ENDIAN | |||||
*ptr++ = static_cast<std::byte>(buffer[offset + n - 1 - a]); | |||||
#else | |||||
*ptr++ = static_cast<std::byte>(buffer[offset + a]); | |||||
#endif | |||||
offset += n; | offset += n; | ||||
while (a--) | return true; | ||||
} | |||||
void SendStunRequest(ENetHost& transactionHost, ENetAddress addr) | |||||
{ | { | ||||
// Prevent shift count overflow if the type is u8 | std::vector<u8> buffer; | ||||
if (n > 1) | AddToBuffer<u16>(buffer, m_MethodTypeBinding); | ||||
result <<= 8; | AddToBuffer<u16>(buffer, 0); // length | ||||
AddToBuffer<u32>(buffer, m_MagicCookie); | |||||
result += buffer[offset - 1 - a]; | 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; | |||||
} | } | ||||
return true; | |||||
ENetBuffer enetBuffer; | |||||
enetBuffer.data = buffer.data(); | |||||
enetBuffer.dataLength = buffer.size(); | |||||
enet_socket_send(transactionHost.socket, &addr, &enetBuffer, 1); | |||||
} | } | ||||
/** | /** | ||||
* Creates a STUN request and sends it to a STUN server. | * Creates a STUN request and sends it to a STUN server. | ||||
* The request is sent through transactionHost, from which the answer | * The request is sent through transactionHost, from which the answer | ||||
* will be retrieved by ReceiveStunResponse and interpreted by ParseStunResponse. | * will be retrieved by ReceiveStunResponse and interpreted by ParseStunResponse. | ||||
*/ | */ | ||||
bool CreateStunRequest(ENetHost& transactionHost) | bool CreateStunRequest(ENetHost& transactionHost) | ||||
{ | { | ||||
CStr server_name; | CStr server_name; | ||||
int port; | |||||
CFG_GET_VAL("lobby.stun.server", server_name); | CFG_GET_VAL("lobby.stun.server", server_name); | ||||
CFG_GET_VAL("lobby.stun.port", m_StunServerPort); | CFG_GET_VAL("lobby.stun.port", port); | ||||
debug_printf("GetPublicAddress: Using STUN server %s:%d\n", server_name.c_str(), m_StunServerPort); | |||||
addrinfo hints; | |||||
addrinfo* res; | |||||
memset(&hints, 0, sizeof(hints)); | LOGMESSAGE("StunClient: Using STUN server %s:%d\n", server_name.c_str(), port); | ||||
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 | ENetAddress addr; | ||||
int status = getaddrinfo(server_name.c_str(), nullptr, &hints, &res); | addr.port = port; | ||||
if (status != 0) | if (enet_address_set_host(&addr, server_name.c_str()) == -1) | ||||
{ | |||||
#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; | return false; | ||||
} | |||||
ENSURE(res); | m_StunServer = addr; | ||||
// Documentation says it points to "one or more addrinfo structures" | StunClient::SendStunRequest(transactionHost, addr); | ||||
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; | 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. | * Gets the response from the STUN server and checks it for its validity. | ||||
*/ | */ | ||||
bool ReceiveStunResponse(ENetHost& transactionHost, std::vector<u8>& buffer) | bool ReceiveStunResponse(ENetHost& transactionHost, std::vector<u8>& buffer) | ||||
{ | { | ||||
// TransportAddress sender; | // TransportAddress sender; | ||||
const int LEN = 2048; | const int LEN = 2048; | ||||
char input_buffer[LEN]; | char input_buffer[LEN]; | ||||
memset(input_buffer, 0, LEN); | memset(input_buffer, 0, LEN); | ||||
sockaddr_in addr; | ENetBuffer enetBuffer; | ||||
socklen_t from_len = sizeof(addr); | enetBuffer.data = input_buffer; | ||||
enetBuffer.dataLength = LEN; | |||||
int len = recvfrom(transactionHost.socket, input_buffer, LEN, 0, reinterpret_cast<sockaddr*>(&addr), &from_len); | ENetAddress sender = m_StunServer; | ||||
int len = enet_socket_receive(transactionHost.socket, &sender, &enetBuffer, 1); | |||||
int delay = 200; | int delay = 200; | ||||
CFG_GET_VAL("lobby.stun.delay", delay); | CFG_GET_VAL("lobby.stun.delay", delay); | ||||
// Wait to receive the message because enet sockets are non-blocking | // Wait to receive the message because enet sockets are non-blocking | ||||
const int max_tries = 5; | const int max_tries = 5; | ||||
for (int count = 0; len < 0 && (count < max_tries || max_tries == -1); ++count) | for (int count = 0; len <= 0 && (count < max_tries || max_tries == -1); ++count) | ||||
{ | { | ||||
usleep(delay * 1000); | std::this_thread::sleep_for(std::chrono::milliseconds(delay)); | ||||
len = recvfrom(transactionHost.socket, input_buffer, LEN, 0, reinterpret_cast<sockaddr*>(&addr), &from_len); | len = enet_socket_receive(transactionHost.socket, &sender, &enetBuffer, 1); | ||||
} | } | ||||
if (len < 0) | if (len <= 0) | ||||
{ | { | ||||
LOGERROR("GetPublicAddress: recvfrom error (%d): %s", errno, strerror(errno)); | LOGERROR("ReceiveStunResponse: recvfrom error (%d): %s", errno, strerror(errno)); | ||||
return false; | return false; | ||||
} | } | ||||
u32 sender_ip = ntohl(static_cast<u32>(addr.sin_addr.s_addr)); | if (memcmp(&sender, &m_StunServer, sizeof(m_StunServer)) != 0) | ||||
u16 sender_port = ntohs(addr.sin_port); | LOGERROR("ReceiveStunResponse: Received stun response from different address: %d.%d.%d.%d:%d %s", | ||||
(sender.host >> 24) & 0xff, | |||||
if (sender_ip != m_StunServerIP) | (sender.host >> 16) & 0xff, | ||||
LOGERROR("GetPublicAddress: Received stun response from different address: %d:%d (%d.%d.%d.%d:%d) %s", | (sender.host >> 8) & 0xff, | ||||
addr.sin_addr.s_addr, | (sender.host >> 0) & 0xff, | ||||
addr.sin_port, | sender.port, | ||||
(sender_ip >> 24) & 0xff, | |||||
(sender_ip >> 16) & 0xff, | |||||
(sender_ip >> 8) & 0xff, | |||||
(sender_ip >> 0) & 0xff, | |||||
sender_port, | |||||
input_buffer); | input_buffer); | ||||
// Convert to network string. | // Convert to network string. | ||||
buffer.resize(len); | buffer.resize(len); | ||||
memcpy(buffer.data(), reinterpret_cast<u8*>(input_buffer), len); | memcpy(buffer.data(), reinterpret_cast<u8*>(input_buffer), len); | ||||
return true; | return true; | ||||
} | } | ||||
bool ParseStunResponse(const std::vector<u8>& buffer) | bool ParseStunResponse(const std::vector<u8>& buffer) | ||||
{ | { | ||||
u32 offset = 0; | u32 offset = 0; | ||||
u16 responseType = 0; | u16 responseType = 0; | ||||
if (!GetFromBuffer<u16, 2>(buffer, offset, responseType) || responseType != m_BindingSuccessResponse) | if (!GetFromBuffer(buffer, offset, responseType) || responseType != m_BindingSuccessResponse) | ||||
{ | { | ||||
LOGERROR("STUN response isn't a binding success response"); | LOGERROR("STUN response isn't a binding success response"); | ||||
return false; | return false; | ||||
} | } | ||||
// Ignore message size | // Ignore message size | ||||
offset += 2; | offset += 2; | ||||
u32 cookie = 0; | u32 cookie = 0; | ||||
if (!GetFromBuffer<u32, 4>(buffer, offset, cookie) || cookie != m_MagicCookie) | if (!GetFromBuffer(buffer, offset, cookie) || cookie != m_MagicCookie) | ||||
{ | { | ||||
LOGERROR("STUN response doesn't contain the magic cookie"); | LOGERROR("STUN response doesn't contain the magic cookie"); | ||||
return false; | return false; | ||||
} | } | ||||
for (std::size_t i = 0; i < sizeof(m_TransactionID); ++i) | for (std::size_t i = 0; i < sizeof(m_TransactionID); ++i) | ||||
{ | { | ||||
u8 transactionChar = 0; | u8 transactionChar = 0; | ||||
if (!GetFromBuffer<u8, 1>(buffer, offset, transactionChar) || transactionChar != m_TransactionID[i]) | if (!GetFromBuffer(buffer, offset, transactionChar) || transactionChar != m_TransactionID[i]) | ||||
{ | { | ||||
LOGERROR("STUN response doesn't contain the transaction ID"); | LOGERROR("STUN response doesn't contain the transaction ID"); | ||||
return false; | return false; | ||||
} | } | ||||
} | } | ||||
while (offset < buffer.size()) | while (offset < buffer.size()) | ||||
{ | { | ||||
u16 type = 0; | u16 type = 0; | ||||
u16 size = 0; | u16 size = 0; | ||||
if (!GetFromBuffer<u16, 2>(buffer, offset, type) || | if (!GetFromBuffer(buffer, offset, type) || | ||||
!GetFromBuffer<u16, 2>(buffer, offset, size)) | !GetFromBuffer(buffer, offset, size)) | ||||
{ | { | ||||
LOGERROR("STUN response contains invalid attribute"); | LOGERROR("STUN response contains invalid attribute"); | ||||
return false; | return false; | ||||
} | } | ||||
// The first two bits are irrelevant to the type | // The first two bits are irrelevant to the type | ||||
type &= ~(m_ComprehensionOptional | m_IETFReview); | type &= ~(m_ComprehensionOptional | m_IETFReview); | ||||
switch (type) | switch (type) | ||||
{ | { | ||||
case m_AttrTypeMappedAddress: | case m_AttrTypeMappedAddress: | ||||
case m_AttrTypeXORMappedAddress: | case m_AttrTypeXORMappedAddress: | ||||
{ | { | ||||
if (size != 8) | if (size != 8) | ||||
{ | { | ||||
LOGERROR("Invalid STUN Mapped Address length"); | LOGERROR("Invalid STUN Mapped Address length"); | ||||
return false; | return false; | ||||
} | } | ||||
// Ignore the first byte as mentioned in Section 15.1 of RFC 5389. | // Ignore the first byte as mentioned in Section 15.1 of RFC 5389. | ||||
++offset; | ++offset; | ||||
u8 ipFamily = 0; | u8 ipFamily = 0; | ||||
if (!GetFromBuffer<u8, 1>(buffer, offset, ipFamily) || ipFamily != m_IPAddressFamilyIPv4) | if (!GetFromBuffer(buffer, offset, ipFamily) || ipFamily != m_IPAddressFamilyIPv4) | ||||
{ | { | ||||
LOGERROR("Unsupported address family, IPv4 is expected"); | LOGERROR("Unsupported address family, IPv4 is expected"); | ||||
return false; | return false; | ||||
} | } | ||||
u16 port = 0; | u16 port = 0; | ||||
u32 ip = 0; | u32 ip = 0; | ||||
if (!GetFromBuffer<u16, 2>(buffer, offset, port) || | if (!GetFromBuffer(buffer, offset, port) || | ||||
!GetFromBuffer<u32, 4>(buffer, offset, ip)) | !GetFromBuffer(buffer, offset, ip)) | ||||
{ | { | ||||
LOGERROR("Mapped address doesn't contain IP and port"); | LOGERROR("Mapped address doesn't contain IP and port"); | ||||
return false; | return false; | ||||
} | } | ||||
// Obfuscation is described in Section 15.2 of RFC 5389. | // Obfuscation is described in Section 15.2 of RFC 5389. | ||||
if (type == m_AttrTypeXORMappedAddress) | if (type == m_AttrTypeXORMappedAddress) | ||||
{ | { | ||||
port ^= m_MagicCookie >> 16; | port ^= m_MagicCookie >> 16; | ||||
ip ^= m_MagicCookie; | ip ^= m_MagicCookie; | ||||
} | } | ||||
m_Port = port; | // ENetAddress takes a host byte-order port and network byte-order IP. | ||||
m_IP = ip; | // Network byte order is big endian, so convert appropriately. | ||||
m_PublicAddress.host = to_be32(ip); | |||||
m_PublicAddress.port = port; | |||||
break; | break; | ||||
} | } | ||||
default: | default: | ||||
{ | { | ||||
// We don't care about other attributes at all | // We don't care about other attributes at all | ||||
// Skip attribute | // Skip attribute | ||||
Show All 16 Lines | bool STUNRequestAndResponse(ENetHost& transactionHost) | ||||
if (!CreateStunRequest(transactionHost)) | if (!CreateStunRequest(transactionHost)) | ||||
return false; | return false; | ||||
std::vector<u8> buffer; | std::vector<u8> buffer; | ||||
return ReceiveStunResponse(transactionHost, buffer) && | return ReceiveStunResponse(transactionHost, buffer) && | ||||
ParseStunResponse(buffer); | ParseStunResponse(buffer); | ||||
} | } | ||||
bool StunClient::FindStunEndpointHost(CStr& ip, u16& port) | bool FindPublicIP(ENetHost& transactionHost, CStr& ip, u16& port) | ||||
{ | |||||
ENetAddress hostAddr{ENET_HOST_ANY, static_cast<u16>(port)}; | |||||
ENetHost* transactionHost = enet_host_create(&hostAddr, 1, 1, 0, 0); | |||||
if (!transactionHost) | |||||
return false; | |||||
bool success = STUNRequestAndResponse(*transactionHost); | |||||
enet_host_destroy(transactionHost); | |||||
if (!success) | |||||
return false; | |||||
// Convert m_IP to string | |||||
char ipStr[256] = "(error)"; | |||||
ENetAddress addr; | |||||
addr.host = ntohl(m_IP); | |||||
int result = enet_address_get_host_ip(&addr, ipStr, ARRAY_SIZE(ipStr)); | |||||
ip = ipStr; | |||||
port = m_Port; | |||||
return result == 0; | |||||
} | |||||
bool StunClient::FindStunEndpointJoin(ENetHost& transactionHost, StunClient::StunEndpoint& stunEndpoint, CStr& ip) | |||||
{ | { | ||||
if (!STUNRequestAndResponse(transactionHost)) | if (!STUNRequestAndResponse(transactionHost)) | ||||
return false; | return false; | ||||
// Convert m_IP to string | // Convert m_IP to string | ||||
char ipStr[256] = "(error)"; | char ipStr[256] = "(error)"; | ||||
ENetAddress addr; | enet_address_get_host_ip(&m_PublicAddress, ipStr, ARRAY_SIZE(ipStr)); | ||||
addr.host = ntohl(m_IP); | |||||
enet_address_get_host_ip(&addr, ipStr, ARRAY_SIZE(ipStr)); | |||||
ip = ipStr; | ip = ipStr; | ||||
stunEndpoint.ip = m_IP; | port = m_PublicAddress.port; | ||||
stunEndpoint.port = m_Port; | |||||
LOGMESSAGE("StunClient: external IP address is %s:%i", ip.c_str(), port); | |||||
return true; | return true; | ||||
} | } | ||||
void StunClient::SendHolePunchingMessages(ENetHost& enetClient, const std::string& serverAddress, u16 serverPort) | void SendHolePunchingMessages(ENetHost& enetClient, const std::string& serverAddress, u16 serverPort) | ||||
{ | { | ||||
// Convert ip string to int64 | // Convert ip string to int64 | ||||
ENetAddress addr; | ENetAddress addr; | ||||
addr.port = serverPort; | addr.port = serverPort; | ||||
enet_address_set_host(&addr, serverAddress.c_str()); | enet_address_set_host(&addr, serverAddress.c_str()); | ||||
int delay = 200; | int delay = 200; | ||||
CFG_GET_VAL("lobby.stun.delay", delay); | CFG_GET_VAL("lobby.stun.delay", delay); | ||||
// Send an UDP message from enet host to ip:port | // Send an UDP message from enet host to ip:port | ||||
for (int i = 0; i < 3; ++i) | for (int i = 0; i < 3; ++i) | ||||
{ | { | ||||
StunClient::SendStunRequest(enetClient, htonl(addr.host), serverPort); | SendStunRequest(enetClient, addr); | ||||
usleep(delay * 1000); | std::this_thread::sleep_for(std::chrono::milliseconds(delay)); | ||||
} | } | ||||
} | } | ||||
bool StunClient::FindLocalIP(CStr& ip) | bool FindLocalIP(CStr& ip) | ||||
{ | { | ||||
// Open an UDP socket. | // Open an UDP socket. | ||||
ENetSocket socket = enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM); | ENetSocket socket = enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM); | ||||
ENetAddress addr; | ENetAddress addr; | ||||
addr.port = 9; // Use the debug port (which we pick does not matter). | addr.port = 9; // Use the debug port (which we pick does not matter). | ||||
// Connect to a random address. It does not need to be valid, only to not be the loopback address. | // Connect to a random address. It does not need to be valid, only to not be the loopback address. | ||||
if (enet_address_set_host(&addr, "100.0.100.0") == -1) | if (enet_address_set_host(&addr, "100.0.100.0") == -1) | ||||
return false; | return false; | ||||
// Connect the socket. Being UDP, there is no actual outgoing traffic, this just binds it | // Connect the socket. Being UDP, there is no actual outgoing traffic, this just binds it | ||||
// to a valid port locally, allowing us to get the local IP of the machine. | // to a valid port locally, allowing us to get the local IP of the machine. | ||||
if (enet_socket_connect(socket, &addr) == -1) | if (enet_socket_connect(socket, &addr) == -1) | ||||
return false; | return false; | ||||
// Fetch the local port & IP. | // Fetch the local port & IP. | ||||
if (enet_socket_get_address(socket, &addr) == -1) | if (enet_socket_get_address(socket, &addr) == -1) | ||||
return false; | return false; | ||||
enet_socket_destroy(socket); | |||||
// Convert to a human readable string. | // Convert to a human readable string. | ||||
char buf[INET_ADDRSTRLEN]; | char buf[50]; | ||||
if (enet_address_get_host_ip(&addr, buf, INET_ADDRSTRLEN) == -1) | if (enet_address_get_host_ip(&addr, buf, ARRAY_SIZE(buf)) == -1) | ||||
return false; | return false; | ||||
ip = buf; | ip = buf; | ||||
return true; | return true; | ||||
} | } | ||||
} |
Wildfire Games · Phabricator