/* Copyright (C) 2019 Mr Goldberg
This file is part of the Goldberg Emulator
The Goldberg Emulator is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 3 of the License, or (at your option) any later version.
The Goldberg Emulator 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with the Goldberg Emulator; if not, see
. */
#include "dll/network.h"
#include "dll/dll.h"
#define MAX_BROADCASTS 16
static int number_broadcasts = -1;
static IP_PORT broadcasts[MAX_BROADCASTS];
static uint32_t lower_range_ips[MAX_BROADCASTS];
static uint32_t upper_range_ips[MAX_BROADCASTS];
#define BROADCAST_INTERVAL 5.0
#define HEARTBEAT_TIMEOUT 20.0
#define USER_TIMEOUT 20.0
#define MAX_UDP_SIZE 16384
#if defined(STEAM_WIN32)
//windows xp support
static int
inet_pton4(const char *src, uint32_t *dst)
{
static const char digits[] = "0123456789";
int saw_digit, octets, ch;
u_char tmp[sizeof(uint32_t)], *tp;
saw_digit = 0;
octets = 0;
*(tp = tmp) = 0;
while ((ch = *src++) != '\0') {
const char *pch;
if ((pch = strchr(digits, ch)) != NULL) {
size_t nx = *tp * 10 + (pch - digits);
if (nx > 255)
return (0);
*tp = (u_char) nx;
if (! saw_digit) {
if (++octets > 4)
return (0);
saw_digit = 1;
}
} else if (ch == '.' && saw_digit) {
if (octets == 4)
return (0);
*++tp = 0;
saw_digit = 0;
} else
return (0);
}
if (octets < 4)
return (0);
memcpy(dst, tmp, sizeof(uint32_t));
return (1);
}
static void get_broadcast_info(uint16 port)
{
number_broadcasts = 0;
IP_ADAPTER_INFO *pAdapterInfo = (IP_ADAPTER_INFO *)malloc(sizeof(IP_ADAPTER_INFO));
unsigned long ulOutBufLen = sizeof(IP_ADAPTER_INFO);
if (pAdapterInfo == NULL) {
return;
}
if (GetAdaptersInfo(pAdapterInfo, &ulOutBufLen) == ERROR_BUFFER_OVERFLOW) {
free(pAdapterInfo);
pAdapterInfo = (IP_ADAPTER_INFO *)malloc(ulOutBufLen);
if (pAdapterInfo == NULL) {
return;
}
}
int ret;
if ((ret = GetAdaptersInfo(pAdapterInfo, &ulOutBufLen)) == NO_ERROR) {
IP_ADAPTER_INFO *pAdapter = pAdapterInfo;
while (pAdapter) {
uint32_t iface_ip = 0, subnet_mask = 0;
if (inet_pton4(pAdapter->IpAddressList.IpMask.String, &subnet_mask) == 1
&& inet_pton4(pAdapter->IpAddressList.IpAddress.String, &iface_ip) == 1) {
IP_PORT *ip_port = &broadcasts[number_broadcasts];
uint32 broadcast_ip = iface_ip | ~subnet_mask;
ip_port->ip = broadcast_ip;
ip_port->port = port;
lower_range_ips[number_broadcasts] = iface_ip & subnet_mask;
upper_range_ips[number_broadcasts] = broadcast_ip;
number_broadcasts++;
if (number_broadcasts >= MAX_BROADCASTS) {
return;
}
}
pAdapter = pAdapter->Next;
}
}
if (pAdapterInfo) {
free(pAdapterInfo);
}
}
#elif defined(__linux__)
static void get_broadcast_info(uint16 port)
{
/* Not sure how many platforms this will run on,
* so it's wrapped in __linux for now.
* Definitely won't work like this on Windows...
*/
number_broadcasts = 0;
sock_t sock = 0;
if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0)
return;
/* Configure ifconf for the ioctl call. */
struct ifreq i_faces[MAX_BROADCASTS];
memset(i_faces, 0, sizeof(struct ifreq) * MAX_BROADCASTS);
struct ifconf ifconf;
ifconf.ifc_buf = (char *)i_faces;
ifconf.ifc_len = sizeof(i_faces);
if (ioctl(sock, SIOCGIFCONF, &ifconf) < 0) {
close(sock);
return;
}
/* ifconf.ifc_len is set by the ioctl() to the actual length used;
* on usage of the complete array the call should be repeated with
* a larger array, not done (640kB and 16 interfaces shall be
* enough, for everybody!)
*/
int i, count = ifconf.ifc_len / sizeof(struct ifreq);
for (i = 0; i < count; i++) {
/* there are interfaces with are incapable of broadcast */
if (ioctl(sock, SIOCGIFBRDADDR, &i_faces[i]) < 0)
continue;
/* moot check: only AF_INET returned (backwards compat.) */
if (i_faces[i].ifr_broadaddr.sa_family != AF_INET)
continue;
struct sockaddr_in *sock4 = (struct sockaddr_in *)&i_faces[i].ifr_broadaddr;
if (number_broadcasts >= MAX_BROADCASTS) {
close(sock);
return;
}
IP_PORT *ip_port = &broadcasts[number_broadcasts];
ip_port->ip = sock4->sin_addr.s_addr;
if (ip_port->ip == 0) {
continue;
}
ip_port->port = port;
number_broadcasts++;
}
close(sock);
}
#endif
static bool is_socket_valid(sock_t sock)
{
#if defined(STEAM_WIN32)
if (sock == (sock_t)INVALID_SOCKET || sock == (sock_t)~0) {
#else
if (sock < 0) {
#endif
return false;
}
return true;
}
static bool is_tcp_socket_valid(struct TCP_Socket &socket)
{
return is_socket_valid(socket.sock);
}
static int set_socket_nonblocking(sock_t sock)
{
#if defined(STEAM_WIN32)
u_long mode = 1;
return (ioctlsocket(sock, FIONBIO, &mode) == 0);
#else
return (fcntl(sock, F_SETFL, O_NONBLOCK, 1) == 0);
#endif
}
static bool disable_nagle(sock_t sock)
{
int set = 1;
return (setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (char *)&set, sizeof(set)) == 0);
}
static void kill_socket(sock_t sock)
{
#if defined(STEAM_WIN32)
closesocket(sock);
#else
close(sock);
#endif
}
static void kill_tcp_socket(struct TCP_Socket &socket)
{
if (is_socket_valid(socket.sock)) {
kill_socket(socket.sock);
}
socket = TCP_Socket();
}
static bool initialed;
static void run_at_startup()
{
if (initialed) {
return;
}
#if defined(STEAM_WIN32)
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != NO_ERROR) {
PRINT_DEBUG("Networking WSAStartup error\n");
return;
}
for (int i = 0; i < 10; ++i) {
//hack: the game Full Mojo Rampage calls WSACleanup on startup so we call WSAStartup a few times so it doesn't get deallocated.
WSAStartup(MAKEWORD(2, 2), &wsaData);
}
#else
#endif
initialed = true;
}
static int get_last_error()
{
#if defined(STEAM_WIN32)
return WSAGetLastError();
#else
return 0;
#endif
}
//Reset the wsa error code so that games don't get confused.
static void reset_last_error()
{
#if defined(STEAM_WIN32)
WSASetLastError(0);
#else
return;
#endif
}
static int send_packet_to(sock_t sock, IP_PORT ip_port, char *data, unsigned long length)
{
PRINT_DEBUG("send: %lu %hhu.%hhu.%hhu.%hhu:%hu\n\n", length, ((unsigned char *)&ip_port.ip)[0], ((unsigned char *)&ip_port.ip)[1], ((unsigned char *)&ip_port.ip)[2], ((unsigned char *)&ip_port.ip)[3], htons(ip_port.port));
struct sockaddr_storage addr;
size_t addrsize = 0;
struct sockaddr_in *addr4 = (struct sockaddr_in *)&addr;
addrsize = sizeof(struct sockaddr_in);
addr4->sin_family = AF_INET;
addr4->sin_addr.s_addr = ip_port.ip;
addr4->sin_port = ip_port.port;
return sendto(sock, data, length, 0, (struct sockaddr *)&addr, addrsize);
}
static int receive_packet(sock_t sock, IP_PORT *ip_port, char *data, unsigned long max_length)
{
struct sockaddr_storage addr{};
#if defined(STEAM_WIN32)
int addrlen = sizeof(addr);
#else
socklen_t addrlen = sizeof(addr);
#endif
int ret = recvfrom(sock, (char *) data, max_length, 0, (struct sockaddr *)&addr, &addrlen);
if (ret >= 0) {
struct sockaddr_in *addr_in = (struct sockaddr_in *)&addr;
ip_port->ip = addr_in->sin_addr.s_addr;
ip_port->port = addr_in->sin_port;
return ret;
}
return -1;
}
static bool send_broadcasts(sock_t sock, uint16 port, char *data, unsigned long length, std::vector *custom_broadcasts)
{
static std::chrono::high_resolution_clock::time_point last_get_broadcast_info;
if (number_broadcasts < 0 || check_timedout(last_get_broadcast_info, 60.0)) {
PRINT_DEBUG("get_broadcast_info\n");
get_broadcast_info(port);
std::vector lower_range(lower_range_ips, lower_range_ips + number_broadcasts), upper_range(upper_range_ips, upper_range_ips + number_broadcasts);
for(auto &addr : *custom_broadcasts) {
lower_range.push_back(addr.ip);
upper_range.push_back(addr.ip);
}
set_whitelist_ips(lower_range.data(), upper_range.data(), lower_range.size());
last_get_broadcast_info = std::chrono::high_resolution_clock::now();
}
IP_PORT main_broadcast;
main_broadcast.ip = INADDR_BROADCAST;
main_broadcast.port = port;
int ret = send_packet_to(sock, main_broadcast, data, length);
if (!number_broadcasts)
return false;
for (int i = 0; i < number_broadcasts; i++) {
ret = send_packet_to(sock, broadcasts[i], data, length);
IP_PORT ip_port = broadcasts[i];
}
/**
* Custom targeted clients server broadcaster
*
* Sends to custom IPs the broadcast packet
* This is useful in cases of undetected network interfaces
*/
PRINT_DEBUG("start custom broadcasts\n");
for(auto &addr : *custom_broadcasts) {
send_packet_to(sock, addr, data, length);
}
PRINT_DEBUG("end custom broadcasts\n");
return true;
}
static void buffers_set(sock_t sock)
{
int n = 1024 * 1024;
setsockopt(sock, SOL_SOCKET, SO_RCVBUF, (char *)&n, sizeof(n));
setsockopt(sock, SOL_SOCKET, SO_SNDBUF, (char *)&n, sizeof(n));
}
static bool bind_socket(sock_t sock, uint16 port)
{
struct sockaddr_storage addr = {};
size_t addrsize;
struct sockaddr_in *addr4 = (struct sockaddr_in *)&addr;
addrsize = sizeof(struct sockaddr_in);
addr4->sin_family = AF_INET;
addr4->sin_port = htons(port);
addr4->sin_addr.s_addr = 0;
return !bind(sock, (struct sockaddr *)&addr, addrsize);
}
static bool socket_reuseaddr(sock_t sock)
{
int set = 1;
return (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *)&set, sizeof(set)) == 0);
}
static void connect_socket(sock_t sock, IP_PORT ip_port)
{
struct sockaddr_storage addr;
size_t addrsize = 0;
struct sockaddr_in *addr4 = (struct sockaddr_in *)&addr;
addrsize = sizeof(struct sockaddr_in);
addr4->sin_family = AF_INET;
addr4->sin_addr.s_addr = ip_port.ip;
addr4->sin_port = ip_port.port;
connect(sock, (struct sockaddr *)&addr, addrsize);
}
unsigned int receive_buffer_amount(sock_t sock)
{
#if defined(STEAM_WIN32)
unsigned long count = 0;
ioctlsocket(sock, FIONREAD, &count);
#else
int count = 0;
ioctl(sock, FIONREAD, &count);
#endif
return count;
}
static void send_tcp_pending(struct TCP_Socket &socket)
{
size_t buf_size = socket.send_buffer.size();
if (buf_size == 0) return;
int len = send(socket.sock, &(socket.send_buffer[0]), buf_size, MSG_NOSIGNAL);
if (len <= 0) return;
socket.send_buffer.erase(socket.send_buffer.begin(), socket.send_buffer.begin() + len);
}
static void send_buffer_tcp(struct TCP_Socket &socket, Common_Message *msg)
{
uint32 size = msg->ByteSizeLong(), old_size = socket.send_buffer.size();
socket.send_buffer.resize(old_size + sizeof(uint32) + size);
memcpy(&(socket.send_buffer[old_size]), &size, sizeof(size));
msg->SerializeToArray(&(socket.send_buffer[old_size + sizeof(uint32)]), size);
send_tcp_pending(socket);
}
static unsigned long peek_buffer_tcp(struct TCP_Socket &socket)
{
uint32 length;
if (socket.recv_buffer.size() < sizeof(length)) return 0;
memcpy(&length, &(socket.recv_buffer[0]), sizeof(length));
if (sizeof(length) + length > socket.recv_buffer.size()) return 0;
return length;
}
static bool unbuffer_tcp(struct TCP_Socket &socket, Common_Message *msg)
{
uint32 l = peek_buffer_tcp(socket);
if (!l) {
return false;
}
if (msg->ParseFromArray(&(socket.recv_buffer[sizeof(uint32)]), l)) {
socket.recv_buffer.erase(socket.recv_buffer.begin(), socket.recv_buffer.begin() + sizeof(l) + l);
return true;
} else {
PRINT_DEBUG("BAD TCP DATA %u %zu %zu %hhu\n", l, socket.recv_buffer.size(), sizeof(uint32), *((char *)&(socket.recv_buffer[sizeof(uint32)])));
kill_tcp_socket(socket);
}
return false;
}
static bool recv_tcp(struct TCP_Socket &socket)
{
if (is_socket_valid(socket.sock)) {
unsigned int size = receive_buffer_amount(socket.sock), old_size = socket.recv_buffer.size();
int len;
socket.recv_buffer.resize(old_size + size);
if (size > 0) {
len = recv(socket.sock, &(socket.recv_buffer[old_size]), size, MSG_NOSIGNAL);
socket.received_data = true;
return true;
}
}
return false;
}
static void socket_timeouts(struct TCP_Socket &socket, double extra_time)
{
if (check_timedout(socket.last_heartbeat_sent, HEARTBEAT_TIMEOUT / 2.0)) {
Common_Message msg;
msg.set_allocated_low_level(new Low_Level());
msg.mutable_low_level()->set_type(Low_Level::HEARTBEAT);
send_buffer_tcp(socket, &msg);
socket.last_heartbeat_sent = std::chrono::high_resolution_clock::now();
}
if (check_timedout(socket.last_heartbeat_received, HEARTBEAT_TIMEOUT + extra_time)) {
kill_tcp_socket(socket);
PRINT_DEBUG("TCP SOCKET HEARTBEAT TIMEOUT\n");
}
}
std::set Networking::resolve_ip(std::string dns)
{
run_at_startup();
std::set ips;
struct addrinfo* result = NULL;
uint16 port = 0;
auto port_sindex = dns.find(":", 0);
if (port_sindex != std::string::npos) {
port = (uint16)atoi(dns.substr(port_sindex + 1).c_str());
dns = dns.substr(0, port_sindex);
}
if (getaddrinfo(dns.c_str(), NULL, NULL, &result) == 0) {
for (struct addrinfo *res = result; res != NULL; res = res->ai_next) {
// cast to size_t because on Linux 'ai_addrlen' is defined as unsigned int and clang complains
PRINT_DEBUG("%zu %u\n", (size_t)res->ai_addrlen, res->ai_family);
if (res->ai_family == AF_INET) {
struct sockaddr_in *ipv4 = (struct sockaddr_in *)res->ai_addr;
uint32 ip;
memcpy(&ip, &ipv4->sin_addr, sizeof(ip));
IP_PORT addr;
addr.ip = ntohl(ip);
addr.port = port;
ips.insert(addr);
}
}
}
return ips;
}
void Networking::do_callbacks_message(Common_Message *msg)
{
if (msg->has_network() || msg->has_network_old()) {
PRINT_DEBUG("Networking has_network\n");
run_callbacks(CALLBACK_ID_NETWORKING, msg);
}
if (msg->has_lobby()) {
PRINT_DEBUG("Networking has_lobby\n");
run_callbacks(CALLBACK_ID_LOBBY, msg);
}
if (msg->has_lobby_messages()) {
PRINT_DEBUG("Networking has_lobby_messages\n");
run_callbacks(CALLBACK_ID_LOBBY, msg);
}
if (msg->has_gameserver()) {
PRINT_DEBUG("Networking has_gameserver\n");
run_callbacks(CALLBACK_ID_GAMESERVER, msg);
}
if (msg->has_friend_()) {
PRINT_DEBUG("Networking has_friend_\n");
run_callbacks(CALLBACK_ID_FRIEND, msg);
}
if (msg->has_auth_ticket()) {
PRINT_DEBUG("Networking has_auth_ticket\n");
run_callbacks(CALLBACK_ID_AUTH_TICKET, msg);
}
if (msg->has_friend_messages()) {
PRINT_DEBUG("Networking has_friend_messages\n");
run_callbacks(CALLBACK_ID_FRIEND_MESSAGES, msg);
}
if (msg->has_networking_sockets()) {
PRINT_DEBUG("Networking has_networking_sockets\n");
run_callbacks(CALLBACK_ID_NETWORKING_SOCKETS, msg);
}
if (msg->has_steam_messages()) {
PRINT_DEBUG("Networking has_steam_messages\n");
run_callbacks(CALLBACK_ID_STEAM_MESSAGES, msg);
}
if (msg->has_networking_messages()) {
PRINT_DEBUG("Networking has_networking_messages\n");
run_callbacks(CALLBACK_ID_NETWORKING_MESSAGES, msg);
}
if (msg->has_gameserver_stats_messages()) {
PRINT_DEBUG("Networking has_gameserver_stats\n");
run_callbacks(CALLBACK_ID_GAMESERVER_STATS, msg);
}
if (msg->has_leaderboards_messages()) {
PRINT_DEBUG("Networking has_leaderboards_messages\n");
run_callbacks(CALLBACK_ID_LEADERBOARDS_STATS, msg);
}
}
bool Networking::handle_tcp(Common_Message *msg, struct TCP_Socket &socket)
{
socket.last_heartbeat_received = std::chrono::high_resolution_clock::now();
if (msg->has_low_level()) {
switch (msg->low_level().type()) {
case Low_Level::DISCONNECT:
break;
case Low_Level::HEARTBEAT:
//socket.last_heartbeat_received = std::chrono::high_resolution_clock::now();
break;
}
}
do_callbacks_message(msg);
return true;
}
struct Connection *Networking::find_connection(CSteamID search_id, uint32 appid)
{
auto conn = std::find_if(connections.begin(), connections.end(), [&search_id, appid](struct Connection const& conn) {
if (appid && (conn.appid != appid)) return false;
for (const auto &id: conn.ids) {
if (search_id == id) return true;
}
return false;
});
if (connections.end() != conn)
return &(*conn);
return nullptr;
}
bool Networking::add_id_connection(struct Connection *connection, CSteamID steam_id)
{
if (!connection) return false;
auto id = std::find(connection->ids.begin(), connection->ids.end(), steam_id);
if (id != connection->ids.end())
return false;
PRINT_DEBUG("Networking::add_id_connection ADDED ID %llu\n", (uint64)steam_id.ConvertToUint64());
connection->ids.push_back(steam_id);
if (connection->connected) {
run_callback_user(steam_id, true, connection->appid);
}
return true;
}
struct Connection *Networking::new_connection(CSteamID search_id, uint32 appid)
{
Connection *conn = find_connection(search_id, appid);
if (conn && conn->appid == appid) return NULL;
struct Connection connection;
connection.ids.push_back(search_id);
connection.appid = appid;
connection.last_received = std::chrono::high_resolution_clock::now();
PRINT_DEBUG("Networking::new_connection ADDED ID %llu\n", (uint64)search_id.ConvertToUint64());
connections.push_back(connection);
return &(connections[connections.size() - 1]);
}
bool Networking::handle_announce(Common_Message *msg, IP_PORT ip_port)
{
Connection *conn = find_connection((uint64)msg->source_id(), msg->announce().appid());
if (!conn || conn->appid != msg->announce().appid()) {
conn = new_connection((uint64)msg->source_id(), msg->announce().appid());
if (!conn) return false;
PRINT_DEBUG(
"Networking::handle_announce new connection created: user %llu, appid %u\n",
(uint64)msg->source_id(), msg->announce().appid()
);
}
PRINT_DEBUG("Handle Announce: %u, " "%" PRIu64 ", %u, %u\n", conn->appid, msg->source_id(), msg->announce().appid(), msg->announce().type());
conn->tcp_ip_port = ip_port;
conn->tcp_ip_port.port = htons(msg->announce().tcp_port());
conn->appid = msg->announce().appid();
for (int i = 0; i < msg->announce().ids_size(); ++i) {
add_id_connection(conn, (uint64) msg->announce().ids(i));
}
for (int i = 0; i < msg->announce().peers_size(); ++i) {
CSteamID search_id((uint64)msg->announce().peers(i).id());
auto id_temp = std::find(ids.begin(), ids.end(), search_id);
if (id_temp != ids.end()) {
own_ip = ntohl(msg->announce().peers(i).ip());
}
Connection *conn = find_connection((uint64)msg->announce().peers(i).id(), msg->announce().peers(i).appid());
PRINT_DEBUG("%p %u %u " "%" PRIu64 "\n", conn, conn ? conn->appid : (uint32)0, msg->announce().peers(i).appid(), msg->announce().peers(i).id());
if (!conn || conn->appid != msg->announce().peers(i).appid()) {
Common_Message msg_ = create_announce(true);
size_t size = msg_.ByteSizeLong();
char *buffer = new char[size];
msg_.SerializeToArray(buffer, size);
IP_PORT ipp;
ipp.ip = msg->announce().peers(i).ip();
ipp.port = htons(msg->announce().peers(i).udp_port());
send_packet_to(udp_socket, ipp, buffer, size);
delete[] buffer;
}
}
conn->last_received = std::chrono::high_resolution_clock::now();
if (msg->announce().type() == Announce::PING) {
Common_Message msg = create_announce(false);
size_t size = msg.ByteSizeLong();
char *buffer = new char[size];
msg.SerializeToArray(buffer, size);
send_packet_to(udp_socket, ip_port, buffer, size);
delete[] buffer;
//send ping packet if not pinged
if (!conn->udp_pinged) {
Common_Message msg = create_announce(true);
size_t size = msg.ByteSizeLong();
char *buffer = new char[size];
msg.SerializeToArray(buffer, size);
send_packet_to(udp_socket, ip_port, buffer, size);
delete[] buffer;
}
} else if (msg->announce().type() == Announce::PONG) {
conn->udp_ip_port = ip_port;
conn->udp_pinged = true;
}
return true;
}
bool Networking::handle_low_level_udp(Common_Message *msg, IP_PORT ip_port)
{
//TODO: connection appid
struct Connection *connection = find_connection((uint64)msg->source_id());
if (!connection)
return false;
switch (msg->low_level().type()) {
case Low_Level::DISCONNECT:
break;
case Low_Level::HEARTBEAT:
break;
}
return false;
}
#define NUM_TCP_WAITING 128
Networking::Networking(CSteamID id, uint32 appid, uint16 port, std::set *custom_broadcasts, bool disable_sockets)
{
tcp_port = udp_port = port;
own_ip = 0x7F000001;
last_run = std::chrono::high_resolution_clock::now();
this->appid = appid;
if (disable_sockets) {
enabled = false;
udp_socket = -1;
tcp_socket = -1;
return;
}
if (custom_broadcasts) {
std::transform(custom_broadcasts->begin(), custom_broadcasts->end(), std::back_inserter(this->custom_broadcasts), [](IP_PORT addr) {addr.ip = htonl(addr.ip); addr.port = htons(addr.port); return addr; });
for (auto& addr : this->custom_broadcasts) {
if (addr.port == htons(0))
addr.port = htons(port);
}
}
run_at_startup();
sock_t sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
PRINT_DEBUG("UDP socket: %u\n", sock);
if (is_socket_valid(sock) && set_socket_nonblocking(sock)) {
int broadcast = 1;
setsockopt(sock, SOL_SOCKET, SO_BROADCAST, (char *)&broadcast, sizeof(broadcast));
//socket_reuseaddr(sock);
buffers_set(sock);
for (unsigned i = 0; i < 1000; ++i) {
udp_port = port + i;
if (bind_socket(sock, udp_port)) {
PRINT_DEBUG("UDP successful\n");
udp_socket = sock;
break;
} else {
//clear the error
int error = 0;
socklen_t len = sizeof(error);
getsockopt(sock, SOL_SOCKET, SO_ERROR, (char *)&error, &len);
}
}
if (!is_socket_valid(udp_socket)) {
PRINT_DEBUG("UDP: could not bind socket\n");
}
} else {
PRINT_DEBUG("UDP: could not initialize %i\n", get_last_error());
}
sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
PRINT_DEBUG("TCP socket: %u\n", sock);
if (is_socket_valid(sock) && set_socket_nonblocking(sock)) {
buffers_set(sock);
//socket_reuseaddr(sock);
for (unsigned i = 0; i < 1000; ++i) {
tcp_port = port + i;
if (bind_socket(sock, tcp_port)) {
if ((listen(sock, NUM_TCP_WAITING) == 0)) {
PRINT_DEBUG("TCP successful\n");
tcp_socket = sock;
break;
} else {
int error = 0;
socklen_t len = sizeof(error);
getsockopt(sock, SOL_SOCKET, SO_ERROR, (char *)&error, &len);
PRINT_DEBUG("TCP listen error %i\n", error);
}
} else {
int error = 0;
socklen_t len = sizeof(error);
getsockopt(sock, SOL_SOCKET, SO_ERROR, (char *)&error, &len);
}
}
if (!is_socket_valid(udp_socket)) {
PRINT_DEBUG("TCP: could not bind or listen\n");
}
} else {
PRINT_DEBUG("TCP: could not initialize %i\n", get_last_error());
}
if (curl_global_init(CURL_GLOBAL_ALL) == 0) {
PRINT_DEBUG("CURL successful\n");
} else {
PRINT_DEBUG("CURL: could not initialize\n");
}
if (is_socket_valid(udp_socket) && is_socket_valid(tcp_socket)) {
PRINT_DEBUG("Networking initialized successfully on udp: %u tcp: %u \n", udp_port, tcp_port);
enabled = true;
}
PRINT_DEBUG("Networking::Networking ADDED ID %llu\n", (uint64)id.ConvertToUint64());
ids.push_back(id);
reset_last_error();
}
Networking::~Networking()
{
for (auto &c : connections) {
kill_tcp_socket(c.tcp_socket_incoming);
kill_tcp_socket(c.tcp_socket_outgoing);
}
for (auto &c : accepted) {
kill_tcp_socket(c);
}
kill_socket(udp_socket);
kill_socket(tcp_socket);
curl_global_cleanup();
}
Common_Message Networking::create_announce(bool request)
{
Announce *announce = new Announce();
PRINT_DEBUG("Networking:: ids length %zu\n", ids.size());
if (request) {
announce->set_type(Announce::PING);
} else {
announce->set_type(Announce::PONG);
for (auto &conn: connections) {
PRINT_DEBUG("Connection %u %llu %u\n", conn.udp_pinged, conn.ids[0].ConvertToUint64(), conn.appid);
if (conn.udp_pinged) {
Announce_Other_Peers *peer = announce->add_peers();
peer->set_id(conn.ids[0].ConvertToUint64());
peer->set_ip(conn.udp_ip_port.ip);
peer->set_udp_port(ntohs(conn.udp_ip_port.port));
peer->set_appid(conn.appid);
}
}
}
announce->set_tcp_port(tcp_port);
announce->set_appid(this->appid);
for (auto &id : ids) announce->add_ids(id.ConvertToUint64());
Common_Message msg;
msg.set_allocated_announce(announce);
msg.set_source_id(ids[0].ConvertToUint64());
return msg;
}
void Networking::send_announce_broadcasts()
{
Common_Message msg = create_announce(true);
size_t size = msg.ByteSizeLong();
char *buffer = new char[size];
msg.SerializeToArray(buffer, size);
send_broadcasts(udp_socket, htons(DEFAULT_PORT), buffer, size, &this->custom_broadcasts);
if (udp_port != DEFAULT_PORT) {
send_broadcasts(udp_socket, htons(udp_port), buffer, size, &this->custom_broadcasts);
}
delete[] buffer;
last_broadcast = std::chrono::high_resolution_clock::now();
PRINT_DEBUG("Networking:: sent broadcasts\n");
}
void Networking::Run()
{
std::chrono::high_resolution_clock::time_point now = std::chrono::high_resolution_clock::now();
double time_extra = std::chrono::duration_cast>(now - last_run).count();
last_run = now;
if (!enabled || ids.size() == 0) {
return;
}
//PRINT_DEBUG("Networking::Run() %lf\n", time_extra);
// PRINT_DEBUG("Networking::Run()\n");
if (check_timedout(last_broadcast, BROADCAST_INTERVAL)) {
send_announce_broadcasts();
}
IP_PORT ip_port;
char data[MAX_UDP_SIZE];
int len;
if (query_alive && is_socket_valid(query_socket)) {
PRINT_DEBUG("Networking::Run RECV Source Query\n");
Steam_Client* client = get_steam_client();
sockaddr_in addr;
addr.sin_family = AF_INET;
while ((len = receive_packet(query_socket, &ip_port, data, sizeof(data))) >= 0) {
PRINT_DEBUG("Networking::Run requesting Source Query server info from Steam_GameServer\n");
client->steam_gameserver->HandleIncomingPacket(data, len, htonl(ip_port.ip), htons(ip_port.port));
len = client->steam_gameserver->GetNextOutgoingPacket(data, sizeof(data), &ip_port.ip, &ip_port.port);
PRINT_DEBUG("Networking::Run sending Source Query server info\n");
addr.sin_addr.s_addr = htonl(ip_port.ip);
addr.sin_port = htons(ip_port.port);
sendto(query_socket, data, len, 0, (sockaddr*)&addr, sizeof(addr));
}
}
PRINT_DEBUG("Networking::Run RECV UDP\n");
while((len = receive_packet(udp_socket, &ip_port, data, sizeof(data))) >= 0) {
PRINT_DEBUG("recv %i %hhu.%hhu.%hhu.%hhu:%hu\n", len, ((unsigned char *)&ip_port.ip)[0], ((unsigned char *)&ip_port.ip)[1], ((unsigned char *)&ip_port.ip)[2], ((unsigned char *)&ip_port.ip)[3], htons(ip_port.port));
Common_Message msg;
if (msg.ParseFromArray(data, len)) {
if (msg.source_id()) {
if (msg.has_announce()) {
handle_announce(&msg, ip_port);
} else
if (msg.has_low_level()) {
handle_low_level_udp(&msg, ip_port);
} else
{
msg.set_source_ip(ntohl(ip_port.ip));
msg.set_source_port(ntohs(ip_port.port));
do_callbacks_message(&msg);
}
}
}
}
PRINT_DEBUG("Networking::Run RECV LOCAL\n");
std::vector local_send_copy = local_send;
local_send.clear();
for (auto & m: local_send_copy) {
m.set_source_ip(ntohl(own_ip));
m.set_source_port(ntohs(udp_port));
do_callbacks_message(&m);
}
struct sockaddr_storage addr;
#if defined(STEAM_WIN32)
int addrlen = sizeof(addr);
#else
socklen_t addrlen = sizeof(addr);
#endif
sock_t sock;
PRINT_DEBUG("ACCEPTING\n");
while (is_socket_valid(sock = accept(tcp_socket, (struct sockaddr *)&addr, &addrlen))) {
PRINT_DEBUG("ACCEPT SOCKET %u\n", sock);
struct sockaddr_storage addr;
#if defined(STEAM_WIN32)
int addrlen = sizeof(addr);
#else
socklen_t addrlen = sizeof(addr);
#endif
struct sockaddr_in *addr_in = (struct sockaddr_in *)&addr;
ip_port.ip = addr_in->sin_addr.s_addr;
ip_port.port = addr_in->sin_port;
struct TCP_Socket socket;
if (set_socket_nonblocking(sock)) {
PRINT_DEBUG("SET NONBLOCK\n");
disable_nagle(sock);
socket.sock = sock;
socket.received_data = true;
socket.last_heartbeat_received = std::chrono::high_resolution_clock::now();
accepted.push_back(socket);
PRINT_DEBUG("TCP ACCEPTED %u\n", sock);
}
}
PRINT_DEBUG("ACCEPTED %zu\n", accepted.size());
auto conn = std::begin(accepted);
while (conn != std::end(accepted)) {
bool deleted = false;
recv_tcp(*conn);
Common_Message msg;
if (unbuffer_tcp(*conn, &msg)) {
if (msg.source_id()) {
Connection *connection = find_connection((uint64)msg.source_id());
if (connection) {
kill_tcp_socket(connection->tcp_socket_incoming);
connection->tcp_socket_incoming = *conn;
conn = accepted.erase(conn);
deleted = true;
PRINT_DEBUG("TCP REPLACED\n");
//TODO: add other ids?
} else {
//Don't allow connection from unknown
//Connection *conn = Networking::new_connection(msg.source_id());
kill_tcp_socket(*conn);
conn = accepted.erase(conn);
deleted = true;
PRINT_DEBUG("TCP UNKNOWN\n");
}
}
}
if (!deleted && check_timedout(conn->last_heartbeat_received, HEARTBEAT_TIMEOUT + time_extra)) {
kill_tcp_socket(*conn);
conn = accepted.erase(conn);
deleted = true;
PRINT_DEBUG("TCP TIMEOUT\n");
}
if (!deleted){
++conn;
}
}
PRINT_DEBUG("CONNECTIONS %zu\n", connections.size());
for (auto &conn: connections) {
if (!is_tcp_socket_valid(conn.tcp_socket_outgoing)) {
sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (is_socket_valid(sock) && set_socket_nonblocking(sock)) {
PRINT_DEBUG("NEW SOCKET %u %u\n", sock, conn.tcp_socket_outgoing.sock);
disable_nagle(sock);
connect_socket(sock, conn.tcp_ip_port);
conn.tcp_socket_outgoing.sock = sock;
conn.tcp_socket_outgoing.last_heartbeat_received = std::chrono::high_resolution_clock::now();
Common_Message msg;
msg.set_source_id(ids[0].ConvertToUint64());
send_buffer_tcp(conn.tcp_socket_outgoing, &msg);
}
}
PRINT_DEBUG("RUN SOCKET1 %u %u\n", conn.tcp_socket_outgoing.sock, conn.tcp_socket_incoming.sock);
recv_tcp(conn.tcp_socket_outgoing);
recv_tcp(conn.tcp_socket_incoming);
if (conn.tcp_socket_incoming.received_data || conn.tcp_socket_outgoing.received_data) {
if (!conn.connected) {
//reconnect the connection if it has the right appid
if (conn.appid == this->appid || conn.appid == LOBBY_CONNECT_APPID) {
for (auto &c: connections) {
if (&c == &conn) continue;
if (c.appid != this->appid) continue;
for (auto &steam_id : conn.ids) {
auto i = std::find(c.ids.begin(), c.ids.end(), steam_id);
if (i != c.ids.end()) {
c.ids.erase(i);
run_callback_user(steam_id, false, c.appid);
PRINT_DEBUG("REMOVE OLD CONNECTION ID\n");
}
}
}
for (auto &steam_id : conn.ids) run_callback_user(steam_id, true, conn.appid);
}
conn.connected = true;
}
}
PRINT_DEBUG("RUN SOCKET2 %u %u\n", conn.tcp_socket_outgoing.sock, conn.tcp_socket_incoming.sock);
send_tcp_pending(conn.tcp_socket_outgoing);
send_tcp_pending(conn.tcp_socket_incoming);
PRINT_DEBUG("RUN SOCKET3 %u %u\n", conn.tcp_socket_outgoing.sock, conn.tcp_socket_incoming.sock);
Common_Message msg;
while (unbuffer_tcp(conn.tcp_socket_outgoing, &msg)) {
PRINT_DEBUG("UNBUFFER SOCKET\n");
msg.set_source_ip(ntohl(conn.tcp_ip_port.ip)); //TODO: get from tcp socket
handle_tcp(&msg, conn.tcp_socket_outgoing);
conn.last_received = std::chrono::high_resolution_clock::now();
}
while (unbuffer_tcp(conn.tcp_socket_incoming, &msg)) {
PRINT_DEBUG("UNBUFFER SOCKET\n");
msg.set_source_ip(ntohl(conn.tcp_ip_port.ip)); //TODO: get from tcp socket
handle_tcp(&msg, conn.tcp_socket_incoming);
conn.last_received = std::chrono::high_resolution_clock::now();
}
PRINT_DEBUG("RUN SOCKET4 %u %u\n", conn.tcp_socket_outgoing.sock, conn.tcp_socket_incoming.sock);
socket_timeouts(conn.tcp_socket_outgoing, time_extra);
socket_timeouts(conn.tcp_socket_incoming, time_extra);
}
{
auto conn = std::begin(connections);
while (conn != std::end(connections)) {
if (check_timedout(conn->last_received, USER_TIMEOUT + time_extra)) {
if (conn->connected) for (auto &steam_id : conn->ids) run_callback_user(steam_id, false, conn->appid);
kill_tcp_socket(conn->tcp_socket_outgoing);
kill_tcp_socket(conn->tcp_socket_incoming);
conn = connections.erase(conn);
PRINT_DEBUG("USER TIMEOUT\n");
} else {
++conn;
}
}
}
for (auto &conn: connections) {
if (!(conn.tcp_socket_incoming.received_data || conn.tcp_socket_outgoing.received_data)) {
if (conn.connected) for (auto &steam_id : conn.ids) run_callback_user(steam_id, false, conn.appid);
conn.connected = false;
}
}
reset_last_error();
}
void Networking::addListenId(CSteamID id)
{
if (!enabled) return;
auto i = std::find(ids.begin(), ids.end(), id);
if (i != ids.end()) {
return;
}
PRINT_DEBUG("Networking::addListenId ADDED ID %llu\n", (uint64)id.ConvertToUint64());
ids.push_back(id);
send_announce_broadcasts();
return;
}
void Networking::setAppID(uint32 appid)
{
this->appid = appid;
}
bool Networking::sendToIPPort(Common_Message *msg, uint32 ip, uint16 port, bool reliable)
{
bool is_local_ip = ((ip >> 24) == 0x7F);
uint32_t local_ip = getIP(ids.front());
PRINT_DEBUG("Networking::sendToIPPort %X %u %X\n", ip, is_local_ip, local_ip);
//TODO: actually send to ip/port
for (auto &conn: connections) {
if (ntohl(conn.tcp_ip_port.ip) == ip || (is_local_ip && ntohl(conn.tcp_ip_port.ip) == local_ip)) {
for (auto &steam_id : conn.ids) {
msg->set_dest_id(steam_id.ConvertToUint64());
sendTo(msg, reliable, &conn);
}
}
}
return true;
}
uint32 Networking::getIP(CSteamID id)
{
Connection *conn = find_connection(id, this->appid);
if (conn) {
return ntohl(conn->tcp_ip_port.ip);
}
return 0;
}
bool Networking::sendTo(Common_Message *msg, bool reliable, Connection *conn)
{
if (!enabled) return false;
size_t size = msg->ByteSizeLong();
if (size >= MAX_UDP_SIZE) reliable = true; //too big for UDP
bool ret = false;
CSteamID dest_id((uint64)msg->dest_id());
if (std::find(ids.begin(), ids.end(), dest_id) != ids.end()) {
PRINT_DEBUG("Networking sending to self\n");
if (!conn) {
PRINT_DEBUG("Networking local send\n");
local_send.push_back(*msg);
ret = true;
}
}
if (!conn) {
conn = find_connection(dest_id, this->appid);
}
if (!ret && conn) {
if (reliable || !conn->udp_pinged) {
if (conn->tcp_socket_incoming.received_data) {
send_buffer_tcp(conn->tcp_socket_incoming, msg);
ret = true;
} else if (conn->tcp_socket_outgoing.received_data) {
send_buffer_tcp(conn->tcp_socket_outgoing, msg);
ret = true;
}
} else {
std::vector buffer(size, 0);
msg->SerializeToArray(&buffer[0], size);
send_packet_to(udp_socket, conn->udp_ip_port, &buffer[0], size);
ret = true;
}
}
reset_last_error();
return ret;
}
bool Networking::sendToAllIndividuals(Common_Message *msg, bool reliable)
{
for (auto &conn: connections) {
for (auto &steam_id : conn.ids) {
if (steam_id.BIndividualAccount()) {
msg->set_dest_id(steam_id.ConvertToUint64());
sendTo(msg, reliable, &conn);
}
}
}
return true;
}
bool Networking::sendToAll(Common_Message *msg, bool reliable)
{
for (auto &conn: connections) {
for (auto &steam_id : conn.ids) {
msg->set_dest_id(steam_id.ConvertToUint64());
sendTo(msg, reliable, &conn);
}
}
return true;
}
void Networking::run_callbacks(Callback_Ids id, Common_Message *msg)
{
for (auto &cb : callbacks[id].callbacks) {
uint64 callback_allowed_steamid = cb.steam_id.ConvertToUint64();
uint64 message_destination_steamid = msg->dest_id();
if (callback_allowed_steamid == 0 || // callback wants to receive all messages (callback for broadcast)
message_destination_steamid == 0 || // message was broadcasted to all (broadcast message)
callback_allowed_steamid == message_destination_steamid) { // callback destination is the same as the message destination
cb.message_callback(cb.object, msg);
}
}
}
void Networking::run_callback_user(CSteamID steam_id, bool online, uint32 appid)
{
//only give callbacks for right game accounts
if (steam_id.BIndividualAccount() && appid != this->appid && appid != LOBBY_CONNECT_APPID) return;
Common_Message msg{};
msg.set_source_id(steam_id.ConvertToUint64());
msg.set_allocated_low_level(new Low_Level());
if (online) {
msg.mutable_low_level()->set_type(Low_Level::CONNECT);
} else {
msg.mutable_low_level()->set_type(Low_Level::DISCONNECT);
}
run_callbacks(CALLBACK_ID_USER_STATUS, &msg);
}
bool Networking::setCallback(Callback_Ids id, CSteamID steam_id, void (*message_callback)(void *object, Common_Message *msg), void *object)
{
if (id >= CALLBACK_IDS_MAX) return false;
struct Network_Callback nc{};
nc.message_callback = message_callback;
nc.object = object;
nc.steam_id = steam_id;
callbacks[id].callbacks.push_back(nc);
return true;
}
void Networking::rmCallback(Callback_Ids id, CSteamID steam_id, void (*message_callback)(void *object, Common_Message *msg), void *object)
{
if (id >= CALLBACK_IDS_MAX) return;
auto &target_cb = callbacks[id].callbacks;
auto itrm = std::remove_if(
target_cb.begin(),
target_cb.end(),
[=, &steam_id](const struct Network_Callback &item) {
return item.message_callback == message_callback &&
item.object == object &&
item.steam_id == steam_id;
}
);
target_cb.erase(itrm, target_cb.end());
}
uint32 Networking::getOwnIP()
{
return own_ip;
}
void Networking::startQuery(IP_PORT ip_port)
{
if (ip_port.port <= 1024)
return;
if (!query_alive)
{
if (ip_port.port == MASTERSERVERUPDATERPORT_USEGAMESOCKETSHARE)
{
PRINT_DEBUG("Source Query in Shared Mode\n");
return;
}
int retry = 0;
constexpr auto max_retry = 10;
while (retry++ < max_retry)
{
query_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (is_socket_valid(query_socket))
break;
if (retry > max_retry)
{
reset_last_error();
return;
}
}
retry = 0;
sockaddr_in addr{};
addr.sin_addr.s_addr = htonl(ip_port.ip);
addr.sin_port = htons(ip_port.port);
addr.sin_family = AF_INET;
while (retry++ < max_retry)
{
int res = bind(query_socket, (sockaddr*)&addr, sizeof(sockaddr_in));
if (res == 0)
{
set_socket_nonblocking(query_socket);
break;
}
if (retry >= max_retry)
{
kill_socket(query_socket);
query_socket = -1;
reset_last_error();
return;
}
}
char str_ip[16]{};
inet_ntop(AF_INET, &(addr.sin_addr), str_ip, 16);
PRINT_DEBUG("Started query server on %s:%d\n", str_ip, htons(addr.sin_port));
}
query_alive = true;
}
void Networking::shutDownQuery()
{
query_alive = false;
kill_socket(query_socket);
}
bool Networking::isQueryAlive()
{
return query_alive;
}