mirror of
https://github.com/scp-fs2open/fs2open.github.com.git
synced 2026-11-08 07:28:22 -10:00
2280 lines
59 KiB
C++
2280 lines
59 KiB
C++
/*
|
|
* Copyright (C) Volition, Inc. 1999. All rights reserved.
|
|
*
|
|
* All source code herein is the property of Volition, Inc. You may not sell
|
|
* or otherwise commercially exploit the source or things you created based on the
|
|
* source.
|
|
*
|
|
*/
|
|
|
|
|
|
|
|
|
|
#ifdef _WIN32
|
|
#include <winsock2.h>
|
|
#else
|
|
#include <sys/time.h>
|
|
#include <sys/types.h>
|
|
#include <sys/socket.h>
|
|
#include <netinet/in.h>
|
|
#include <sys/select.h>
|
|
#include <cerrno>
|
|
#include <arpa/inet.h>
|
|
#include <netdb.h>
|
|
#endif
|
|
|
|
#include <cstdio>
|
|
#include <climits>
|
|
#include <algorithm>
|
|
|
|
#include "globalincs/pstypes.h"
|
|
#include "network/psnet2.h"
|
|
#include "network/multi.h"
|
|
#include "network/multiutil.h"
|
|
#include "network/multilag.h"
|
|
#include "osapi/osregistry.h"
|
|
#include "io/timer.h"
|
|
#include "network/multi_log.h"
|
|
#include "network/multi_rate.h"
|
|
#include "cmdline/cmdline.h"
|
|
|
|
// -------------------------------------------------------------------------------------------------------
|
|
// PSNET 2 DEFINES/VARS
|
|
//
|
|
|
|
net_addr Psnet_my_addr;
|
|
static SCP_vector<in6_addr> Psnet_my_ip;
|
|
|
|
static int Psnet_ip_mode = PSNET_IP_MODE_UNKNOWN;
|
|
|
|
static bool Psnet_active = false;
|
|
|
|
static int Network_status;
|
|
int Psnet_failure_code = 0;
|
|
int Psnet_connection;
|
|
|
|
uint16_t Psnet_default_port;
|
|
|
|
// defines and variables to indicate network connection status
|
|
#define NETWORK_STATUS_NOT_INITIALIZED 1
|
|
#define NETWORK_STATUS_NO_WINSOCK 2 // winsock failed to initialize
|
|
#define NETWORK_STATUS_NO_PROTOCOL 3 // TCP/IP doesn't appear to be loaded
|
|
#define NETWORK_STATUS_NO_RELIABLE 4
|
|
#define NETWORK_STATUS_RUNNING 5 // everything should be running
|
|
|
|
// defintion of structures that actually leave this machine. psnet_send give us only
|
|
// the data that we want to send. We will add a header onto this data (packet sequence
|
|
// number, possibly a checksum). We must include a 2 byte flags variable into both structure
|
|
// since the receiving end of this packet must know whether or not to checksum the packet.
|
|
|
|
// use the pack pragma to pack these structures to 2 byte aligment. Really only needed for
|
|
// the naked packet.
|
|
#define MAX_PACKET_BUFFERS 75
|
|
|
|
#pragma pack(push, 2)
|
|
|
|
/**
|
|
* Structure definition for our packet buffers
|
|
*/
|
|
typedef struct network_packet_buffer
|
|
{
|
|
int sequence_number;
|
|
SSIZE_T len;
|
|
SOCKADDR_IN6 from_addr;
|
|
ubyte data[MAX_TOP_LAYER_PACKET_SIZE];
|
|
} network_packet_buffer;
|
|
|
|
/**
|
|
* Structure for a bunch of network packet buffers
|
|
*/
|
|
typedef struct network_packet_buffer_list {
|
|
network_packet_buffer psnet_buffers[MAX_PACKET_BUFFERS];
|
|
int psnet_seq_number;
|
|
int psnet_lowest_id;
|
|
int psnet_highest_id;
|
|
} network_packet_buffer_list;
|
|
|
|
#pragma pack(pop)
|
|
|
|
|
|
#define MAX_RECEIVE_BUFSIZE 4096 // 32 K, eh?
|
|
#define MAX_SEND_RETRIES 20 // number of retries when sending would block
|
|
#define MAX_LINGER_TIME 0 // in seconds -- when lingering to close a socket
|
|
|
|
//Reliable UDP stuff
|
|
//*******************************
|
|
#define MAXNETBUFFERS 150 // Maximum network buffers (For between network and upper level functions, which is
|
|
// required in case of out of order packets
|
|
#define NETRETRYTIME 0.75f // Time after sending before we resend
|
|
#define MIN_NET_RETRYTIME 0.2f
|
|
#define NETTIMEOUT 30 // Time after receiving the last packet before we drop that user
|
|
#define NETHEARTBEATTIME 3 // How often to send a heartbeat
|
|
#define MAXRELIABLESOCKETS 40 // Max reliable sockets to open at once...
|
|
|
|
#define RELIABLE_CONNECT_TIME 7 // how long we'll wait for a response when doing a reliable connect
|
|
|
|
static int Nettimeout = NETTIMEOUT;
|
|
|
|
// Reliable packet stuff
|
|
#define RNT_ACK 1 // ACK Packet
|
|
#define RNT_DATA 2 // Data Packet
|
|
#define RNT_DATA_COMP 3 // Compressed Data Packet
|
|
#define RNT_REQ_CONN 4 // Requesting a connection
|
|
#define RNT_DISCONNECT 5 // Disconnecting a connection
|
|
#define RNT_HEARTBEAT 6 // Heartbeat -- send every NETHEARTBEATTIME
|
|
#define RNT_I_AM_HERE 7
|
|
|
|
#pragma pack(push, 1)
|
|
typedef struct {
|
|
ubyte type; // packet type
|
|
ubyte compressed; //
|
|
ushort seq; // sequence packet 0-65535 used for ACKing also
|
|
ushort data_len; // length of data
|
|
float send_time; // Time the packet was sent, if an ACK the time the packet being ACK'd was sent.
|
|
ubyte data[MAX_PACKET_SIZE]; // Packet data
|
|
} reliable_header;
|
|
#pragma pack(pop)
|
|
|
|
// Psnet adds 1 byte for type ident, so make sure we've got a little headroom
|
|
static_assert(sizeof(reliable_header) < MAX_TOP_LAYER_PACKET_SIZE, "reliable_header is larger than max packet size!");
|
|
|
|
#define RELIABLE_PACKET_HEADER_ONLY_SIZE (sizeof(reliable_header)-MAX_PACKET_SIZE)
|
|
#define MAX_PING_HISTORY 10
|
|
|
|
typedef struct {
|
|
ubyte buffer[MAX_PACKET_SIZE];
|
|
} reliable_net_buffer;
|
|
|
|
#pragma pack(push, 1)
|
|
typedef struct {
|
|
reliable_net_buffer *sbuffers[MAXNETBUFFERS]; // This is an array of pointers for quick sorting
|
|
reliable_net_buffer *rbuffers[MAXNETBUFFERS];
|
|
int send_len[MAXNETBUFFERS];
|
|
int recv_len[MAXNETBUFFERS];
|
|
unsigned short ssequence[MAXNETBUFFERS];
|
|
unsigned short rsequence[MAXNETBUFFERS]; // This is the sequence number of the given packet
|
|
float timesent[MAXNETBUFFERS];
|
|
float last_packet_received; // For a given connection, this is the last packet we received
|
|
float last_packet_sent;
|
|
SOCKADDR_IN6 addr; // SOCKADDR of our peer
|
|
ushort status; // Status of this connection
|
|
unsigned short oursequence; // This is the next sequence number the application is expecting
|
|
unsigned short theirsequence; // This is the next sequence number the peer is expecting
|
|
unsigned short ping_pos;
|
|
float pings[MAX_PING_HISTORY];
|
|
unsigned int num_ping_samples;
|
|
float mean_ping;
|
|
} reliable_socket;
|
|
#pragma pack(pop)
|
|
|
|
static reliable_socket Reliable_sockets[MAXRELIABLESOCKETS];
|
|
|
|
// the sockets that the game will use when selecting network type
|
|
SOCKET Psnet_socket = INVALID_SOCKET;
|
|
|
|
static float First_sent_iamhere = 0;
|
|
static float Last_sent_iamhere = 0;
|
|
|
|
#define CONNECTSEQ 0x142 // Magic number for starting a connection, just so it isn't 0
|
|
|
|
unsigned int Serverconn = 0xffffffff;
|
|
|
|
//*******************************
|
|
|
|
// top layer buffers
|
|
static network_packet_buffer_list Psnet_top_buffers[PSNET_NUM_TYPES];
|
|
|
|
// -------------------------------------------------------------------------------------------------------
|
|
// PSNET 2 FORWARD DECLARATIONS
|
|
//
|
|
|
|
// set some options on a socket
|
|
void psnet_set_socket_options();
|
|
|
|
// initialize socket
|
|
bool psnet_init_socket();
|
|
|
|
// initilize my addr
|
|
bool psnet_init_my_addr();
|
|
|
|
// get time in seconds
|
|
float psnet_get_time();
|
|
|
|
// initialize reliable sockets
|
|
void psnet_init_rel_tcp();
|
|
|
|
// shutdown reliable sockets
|
|
void psnet_rel_close();
|
|
|
|
// initialize the buffering system
|
|
void psnet_buffer_init(network_packet_buffer_list *l);
|
|
|
|
// buffer a packet (maintain order!)
|
|
void psnet_buffer_packet(network_packet_buffer_list *l, ubyte *data, SSIZE_T length, SOCKADDR_IN6 *from);
|
|
|
|
// get the index of the next packet in order!
|
|
int psnet_buffer_get_next(network_packet_buffer_list *l, ubyte *data, SSIZE_T *length, SOCKADDR_IN6 *from);
|
|
|
|
// ip string parsing helpers
|
|
static bool psnet_is_ip_notation(int af, const char *ip_string);
|
|
static bool psnet_explode_ip_string(const char *ip_string, SCP_string &host, SCP_string &port);
|
|
|
|
// conversions
|
|
static void psnet_sockaddr_to_addr(const SOCKADDR_IN6 *sockaddr, net_addr *addr);
|
|
static void psnet_addr_to_sockaddr(const net_addr *addr, SOCKADDR_IN6 *sockaddr);
|
|
|
|
// -------------------------------------------------------------------------------------------------------
|
|
// PSNET 2 TOP LAYER FUNCTIONS - these functions simply buffer and store packets based upon type (see PSNET_TYPE_* defines)
|
|
//
|
|
|
|
/**
|
|
* Wrappers around select() and recvfrom() for lagging/losing data
|
|
*/
|
|
int RECVFROM(SOCKET /*s*/, char *buf, int /*len*/, int /*flags*/, SOCKADDR *from, int *fromlen, int psnet_type)
|
|
{
|
|
network_packet_buffer_list *l;
|
|
SOCKADDR_IN6 addr;
|
|
int ret;
|
|
SSIZE_T ret_len;
|
|
|
|
// bad type
|
|
Assert( (psnet_type >= 0) && (psnet_type < PSNET_NUM_TYPES) );
|
|
|
|
if ( (psnet_type < 0) || (psnet_type >= PSNET_NUM_TYPES) ) {
|
|
return -1;
|
|
}
|
|
|
|
// make sure we have enough storage size for return addr
|
|
if ( *fromlen < static_cast<int>(sizeof(addr)) ) {
|
|
Int3();
|
|
return -1;
|
|
}
|
|
|
|
l = &Psnet_top_buffers[psnet_type];
|
|
|
|
// if we have no buffer! The user should have made sure this wasn't the case by calling SELECT()
|
|
ret = psnet_buffer_get_next(l, reinterpret_cast<ubyte *>(buf), &ret_len, &addr);
|
|
|
|
if ( !ret ) {
|
|
Int3();
|
|
return -1;
|
|
}
|
|
|
|
// otherwise, stuff the outgoing data
|
|
memcpy(from, &addr, sizeof(addr));
|
|
*fromlen = sizeof(addr);
|
|
|
|
// return bytes read
|
|
return static_cast<int>(ret_len);
|
|
}
|
|
|
|
/**
|
|
* Wrappers around select() and recvfrom() for lagging/losing data
|
|
*/
|
|
int SELECT(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout, int psnet_type)
|
|
{
|
|
network_packet_buffer_list *l;
|
|
|
|
// if this is a check for writability, just return the select
|
|
if (writefds != nullptr) {
|
|
return select(nfds, readfds, writefds, exceptfds, timeout);
|
|
}
|
|
|
|
// bad type
|
|
Assert( (psnet_type >= 0) && (psnet_type < PSNET_NUM_TYPES) );
|
|
|
|
if ( (psnet_type < 0) || (psnet_type >= PSNET_NUM_TYPES) ) {
|
|
return -1;
|
|
}
|
|
|
|
l = &Psnet_top_buffers[psnet_type];
|
|
|
|
// do we have any buffers in here?
|
|
if ( (l->psnet_lowest_id == -1) || (l->psnet_lowest_id > l->psnet_highest_id) ) {
|
|
if (readfds) {
|
|
FD_ZERO(readfds);
|
|
}
|
|
|
|
if (exceptfds) {
|
|
FD_ZERO(exceptfds);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
// yo
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* Wrappers around sendto to sorting through different packet types
|
|
*/
|
|
int SENDTO(SOCKET s, char * buf, int len, int flags, SOCKADDR *to, int tolen, int psnet_type)
|
|
{
|
|
char outbuf[MAX_TOP_LAYER_PACKET_SIZE];
|
|
|
|
Assert(len < MAX_TOP_LAYER_PACKET_SIZE);
|
|
|
|
// stuff type
|
|
outbuf[0] = static_cast<char>(psnet_type);
|
|
memcpy(&outbuf[1], buf, static_cast<size_t>(len));
|
|
|
|
// send it
|
|
return static_cast<int>( sendto(s, outbuf, len + 1, flags, reinterpret_cast<LPSOCKADDR>(to), tolen) );
|
|
}
|
|
|
|
/**
|
|
* Call this once per frame to read everything off of our socket
|
|
*/
|
|
void PSNET_TOP_LAYER_PROCESS()
|
|
{
|
|
// read socket stuff
|
|
fd_set rfds;
|
|
timeval timeout;
|
|
SSIZE_T read_len;
|
|
socklen_t from_len;
|
|
SOCKADDR_IN6 from_addr;
|
|
uint8_t packet_data[MAX_TOP_LAYER_PACKET_SIZE];
|
|
|
|
if ( !Psnet_active ) {
|
|
return;
|
|
}
|
|
|
|
// clear the addresses to remove compiler warnings
|
|
memset(&from_addr, 0, sizeof(from_addr));
|
|
|
|
while (true) {
|
|
// check if there is any data on the socket to be read. The amount of data that can be
|
|
// atomically read is stored in len.
|
|
|
|
FD_ZERO(&rfds);
|
|
FD_SET(Psnet_socket, &rfds);
|
|
timeout.tv_sec = 0;
|
|
timeout.tv_usec = 0;
|
|
|
|
if ( select(static_cast<int>(Psnet_socket + 1), &rfds, nullptr, nullptr, &timeout) == SOCKET_ERROR ) {
|
|
ml_printf("Error %d doing a socket select on read", WSAGetLastError());
|
|
break;
|
|
}
|
|
|
|
// if the read file descriptor is not set, then bail!
|
|
if ( !FD_ISSET(Psnet_socket, &rfds) ) {
|
|
return;
|
|
}
|
|
|
|
// get data off the socket and process
|
|
from_len = sizeof(from_addr);
|
|
read_len = recvfrom(Psnet_socket, reinterpret_cast<char *>(packet_data), sizeof(packet_data),
|
|
0, reinterpret_cast<LPSOCKADDR>(&from_addr), &from_len);
|
|
|
|
if (read_len <= 0) {
|
|
if (read_len == -1) {
|
|
ml_string("Socket error on socket_get_data()");
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
// determine the packet type
|
|
int packet_type = packet_data[0];
|
|
Assertion(( (packet_type >= 0) && (packet_type < PSNET_NUM_TYPES) ), "Invalid packet_type found. Packet type %d does not exist", packet_type);
|
|
|
|
if ( (packet_type >= 0) && (packet_type < PSNET_NUM_TYPES) ) {
|
|
// buffer the packet
|
|
psnet_buffer_packet(&Psnet_top_buffers[packet_type], packet_data + 1, read_len - 1, &from_addr);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// -------------------------------------------------------------------------------------------------------
|
|
// PSNET 2 FUNCTIONS
|
|
//
|
|
|
|
/**
|
|
* Initialize psnet to use the specified port
|
|
*/
|
|
void psnet_init(uint16_t port_num)
|
|
{
|
|
int idx;
|
|
|
|
if (Psnet_active) {
|
|
return;
|
|
}
|
|
|
|
Psnet_connection = NETWORK_CONNECTION_LAN;
|
|
|
|
Network_status = NETWORK_STATUS_NO_PROTOCOL;
|
|
|
|
#ifdef _WIN32
|
|
WSADATA wsa_data;
|
|
WORD ws_ver;
|
|
|
|
ws_ver = MAKEWORD(2, 2);
|
|
|
|
if ( WSAStartup(ws_ver, &wsa_data) ) {
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
// get the port for running this game on. Be careful that it cannot be out of bounds
|
|
Psnet_default_port = DEFAULT_GAME_PORT;
|
|
|
|
if (port_num > 1023) {
|
|
Psnet_default_port = port_num;
|
|
}
|
|
|
|
// initialize all packet type buffers
|
|
for (idx = 0; idx < PSNET_NUM_TYPES; idx++) {
|
|
psnet_buffer_init(&Psnet_top_buffers[idx]);
|
|
}
|
|
|
|
// do this before socket init
|
|
psnet_init_my_addr();
|
|
|
|
// initialize UDP now
|
|
if ( !psnet_init_socket() ) {
|
|
ml_printf("Error on UDP startup %d", Psnet_failure_code);
|
|
return;
|
|
}
|
|
|
|
psnet_init_rel_tcp();
|
|
|
|
Psnet_active = true;
|
|
|
|
// specified network timeout
|
|
Nettimeout = NETTIMEOUT;
|
|
|
|
if (Cmdline_timeout > 0) {
|
|
Nettimeout = Cmdline_timeout;
|
|
}
|
|
|
|
Network_status = NETWORK_STATUS_RUNNING;
|
|
}
|
|
|
|
/**
|
|
* Shutdown psnet
|
|
*/
|
|
void psnet_close()
|
|
{
|
|
if ( !Psnet_active ) {
|
|
return;
|
|
}
|
|
|
|
// close down all reliable sockets - this forces them to
|
|
// send a disconnect to any remote machines
|
|
psnet_rel_close();
|
|
|
|
if (Psnet_socket != INVALID_SOCKET) {
|
|
shutdown(Psnet_socket, 1);
|
|
closesocket(Psnet_socket);
|
|
}
|
|
|
|
Psnet_active = false;
|
|
Network_status = NETWORK_STATUS_NOT_INITIALIZED;
|
|
|
|
#ifdef _WIN32
|
|
WSACleanup();
|
|
#endif
|
|
}
|
|
|
|
/**
|
|
* Test whether psnet is ready or not
|
|
*/
|
|
bool psnet_is_active()
|
|
{
|
|
return Psnet_active;
|
|
}
|
|
|
|
/**
|
|
* Initialize my addr, ip, etc.
|
|
*/
|
|
bool psnet_init_my_addr()
|
|
{
|
|
SOCKADDR_STORAGE remote_addr;
|
|
SOCKADDR_IN6 local_addr_ipv4, local_addr_ipv6;
|
|
char ip_string[INET6_ADDRSTRLEN];
|
|
socklen_t addrlen;
|
|
SOCKET tsock;
|
|
int rval;
|
|
const char *local_ip;
|
|
|
|
// zero out my address
|
|
Psnet_my_ip.clear();
|
|
memset(&Psnet_my_addr, 0, sizeof(Psnet_my_addr));
|
|
Psnet_my_addr.port = Psnet_default_port; // set this just in case we bail
|
|
|
|
local_addr_ipv4.sin6_family = AF_UNSPEC;
|
|
local_addr_ipv4.sin6_addr = IN6ADDR_ANY_INIT;
|
|
local_addr_ipv6.sin6_family = AF_UNSPEC;
|
|
local_addr_ipv6.sin6_addr = IN6ADDR_ANY_INIT;
|
|
|
|
//
|
|
// run through some public DNS servers to try and populate the routing table
|
|
// which should load the socket with our local interface IP address
|
|
//
|
|
// IPv6 and IPv4 checks are separate so we can get both addresses
|
|
// it's safe for either one to fail
|
|
//
|
|
|
|
// IPv6 & IPv4 -> Cloudflare, Google Public DNS, Quad9
|
|
const SCP_vector<SCP_string> remote_hosts_ipv6 = { "2606:4700:4700::1111", "2001:4860:4860::8888", "2620:fe::fe" };
|
|
const SCP_vector<SCP_string> remote_hosts_ipv4 = { "1.1.1.1", "8.8.8.8", "9.9.9.9" };
|
|
|
|
// IPv6 check (should be done first!!)
|
|
tsock = socket(AF_INET6, SOCK_DGRAM, 0);
|
|
|
|
if (tsock == INVALID_SOCKET) {
|
|
ml_string("Error creating IPv6 socket, unable to determine local IP");
|
|
} else {
|
|
for (const auto &host : remote_hosts_ipv6) {
|
|
if ( !psnet_get_addr(host.c_str(), 53, &remote_addr) ) {
|
|
continue;
|
|
}
|
|
|
|
rval = connect(tsock, reinterpret_cast<LPSOCKADDR>(&remote_addr), sizeof(remote_addr));
|
|
|
|
if (rval) {
|
|
continue;
|
|
}
|
|
|
|
// we've connected, so the socket should have a routable IP now
|
|
addrlen = sizeof(local_addr_ipv6);
|
|
rval = getsockname(tsock, reinterpret_cast<LPSOCKADDR>(&local_addr_ipv6), &addrlen);
|
|
|
|
if ( !rval ) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( !IN6_IS_ADDR_UNSPECIFIED(&local_addr_ipv6.sin6_addr) ) {
|
|
// add to ip list
|
|
Psnet_my_ip.push_back(local_addr_ipv6.sin6_addr);
|
|
}
|
|
|
|
shutdown(tsock, 1);
|
|
closesocket(tsock);
|
|
}
|
|
|
|
// IPv4 check
|
|
tsock = socket(AF_INET6, SOCK_DGRAM, 0);
|
|
|
|
if (tsock == INVALID_SOCKET) {
|
|
ml_string("Error creating IPv4 socket, unable to determine local IP");
|
|
} else {
|
|
// make sure we are in dual-stack mode (not the default on Windows)
|
|
int i_opt = 0;
|
|
setsockopt(tsock, IPPROTO_IPV6, IPV6_V6ONLY, reinterpret_cast<const char *>(&i_opt), sizeof(i_opt));
|
|
|
|
for (const auto &host : remote_hosts_ipv4) {
|
|
if ( !psnet_get_addr(host.c_str(), 53, &remote_addr) ) {
|
|
continue;
|
|
}
|
|
|
|
rval = connect(tsock, reinterpret_cast<LPSOCKADDR>(&remote_addr), sizeof(remote_addr));
|
|
|
|
if (rval) {
|
|
continue;
|
|
}
|
|
|
|
// we've connected, so the socket should have a routable IP now
|
|
addrlen = sizeof(local_addr_ipv4);
|
|
rval = getsockname(tsock, reinterpret_cast<LPSOCKADDR>(&local_addr_ipv4), &addrlen);
|
|
|
|
if ( !rval ) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( !IN6_IS_ADDR_UNSPECIFIED(&local_addr_ipv4.sin6_addr) ) {
|
|
// add to ip list
|
|
Psnet_my_ip.push_back(local_addr_ipv4.sin6_addr);
|
|
}
|
|
|
|
shutdown(tsock, 1);
|
|
closesocket(tsock);
|
|
}
|
|
|
|
//
|
|
// rest of this just normalizes and populates the internal structures
|
|
//
|
|
|
|
// ?? oops
|
|
if ( Psnet_my_ip.empty() ) {
|
|
ml_string("Local interface IP address => <undetermined>");
|
|
return false;
|
|
}
|
|
|
|
if (Psnet_my_ip.size() > 1) {
|
|
Psnet_ip_mode = PSNET_IP_MODE_DUAL;
|
|
} else if ( IN6_IS_ADDR_V4MAPPED(&Psnet_my_ip[0]) ) {
|
|
Psnet_ip_mode = PSNET_IP_MODE_V4;
|
|
} else {
|
|
Psnet_ip_mode = PSNET_IP_MODE_V6;
|
|
}
|
|
|
|
// prefer IPv4 address if avaiable (last entry), for maximum compatibility
|
|
// TODO: something more robust probably needs to happen with this
|
|
memcpy(&Psnet_my_addr.addr, &Psnet_my_ip.back(), sizeof(in6_addr));
|
|
Psnet_my_addr.port = Psnet_default_port;
|
|
|
|
// log results to multi.log
|
|
for (auto &in6 : Psnet_my_ip) {
|
|
local_ip = inet_ntop(AF_INET6, psnet_mask_addr(&in6), ip_string, sizeof(ip_string));
|
|
|
|
if (local_ip) {
|
|
ml_printf("Local interface address => %s", local_ip);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Get the status of the network
|
|
*/
|
|
int psnet_get_network_status()
|
|
{
|
|
// first case is when "none" is selected
|
|
if (Psnet_connection == NETWORK_CONNECTION_NONE) {
|
|
return NETWORK_ERROR_NO_TYPE;
|
|
}
|
|
|
|
// first, check the connection status of the network
|
|
if (Network_status == NETWORK_STATUS_NO_WINSOCK) {
|
|
return NETWORK_ERROR_NO_WINSOCK;
|
|
}
|
|
|
|
if (Network_status == NETWORK_STATUS_NO_PROTOCOL) {
|
|
return NETWORK_ERROR_NO_PROTOCOL;
|
|
}
|
|
|
|
return NETWORK_ERROR_NONE;
|
|
}
|
|
|
|
/**
|
|
* Convert a ::net_addr to a string
|
|
*/
|
|
const char *psnet_addr_to_string(const net_addr *address, char *text, size_t max_len)
|
|
{
|
|
in6_addr addr6;
|
|
|
|
memcpy(&addr6, &address->addr, sizeof(addr6));
|
|
|
|
if ( inet_ntop(AF_INET6, &addr6, text, static_cast<socklen_t>(max_len)) == nullptr ) {
|
|
strncpy(text, "", max_len);
|
|
}
|
|
|
|
return text;
|
|
}
|
|
|
|
/**
|
|
* Convert a string to a net addr
|
|
*/
|
|
bool psnet_string_to_addr(const char *text, net_addr *address)
|
|
{
|
|
SCP_string host, port;
|
|
SOCKADDR_STORAGE sockaddr;
|
|
auto *addr6 = reinterpret_cast<SOCKADDR_IN6 *>(&sockaddr);
|
|
|
|
memset(address, 0, sizeof(*address));
|
|
|
|
if ( !psnet_explode_ip_string(text, host, port) ) {
|
|
return false;
|
|
}
|
|
|
|
if ( !psnet_get_addr(host.c_str(), DEFAULT_GAME_PORT, &sockaddr) ) {
|
|
return false;
|
|
}
|
|
|
|
memcpy(&address->addr, &addr6->sin6_addr, sizeof(address->addr));
|
|
|
|
if ( !port.empty() ) {
|
|
try {
|
|
int port_num = std::stoi(port);
|
|
|
|
if ( (port_num >= 0) && (port_num <= USHRT_MAX) ) {
|
|
address->port = static_cast<uint16_t>(port_num);
|
|
}
|
|
} catch (...) {
|
|
mprintf(("Invalid port number in psnet_string_to_addr()"));
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Compare 2 addresses
|
|
*/
|
|
int psnet_same(SOCKADDR_IN6 *a1, SOCKADDR_IN6 *a2)
|
|
{
|
|
return (a1->sin6_port == a2->sin6_port) && IN6_ARE_ADDR_EQUAL(&a1->sin6_addr, &a2->sin6_addr);
|
|
}
|
|
|
|
int psnet_same(net_addr *a1, net_addr *a2)
|
|
{
|
|
return (a1->port == a2->port) && !memcmp(&a1->addr, &a2->addr, sizeof(a1->addr));
|
|
}
|
|
|
|
/**
|
|
* Compare against possible address of this machine
|
|
*/
|
|
|
|
bool psnet_is_local_addr(const net_addr *addr)
|
|
{
|
|
// if port is different then skip the rest of the tests
|
|
if (addr->port != Psnet_default_port) {
|
|
return false;
|
|
}
|
|
|
|
auto *sin6_addr = reinterpret_cast<const in6_addr *>(&addr->addr);
|
|
|
|
// IPv6 loopback
|
|
if (IN6_IS_ADDR_LOOPBACK(sin6_addr)) {
|
|
return true;
|
|
}
|
|
|
|
// IPv4 loopback
|
|
if (IN6_IS_ADDR_V4MAPPED(sin6_addr)) {
|
|
if (reinterpret_cast<const uint32_t *>(sin6_addr)[3] == INADDR_LOOPBACK) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// identified local interfaces
|
|
for (auto &in6 : Psnet_my_ip) {
|
|
if (IN6_ARE_ADDR_EQUAL(sin6_addr, &in6)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Get IP mode (IPv4, IPv6, dual stack...)
|
|
*/
|
|
int psnet_get_ip_mode()
|
|
{
|
|
return Psnet_ip_mode;
|
|
}
|
|
|
|
/**
|
|
* Send data unreliably
|
|
*/
|
|
int psnet_send(net_addr *who_to_addr, void *data, int len, int np_index) // NOLINT(misc-unused-parameters)
|
|
{
|
|
// send data unreliably
|
|
int ret;
|
|
fd_set wfds;
|
|
struct timeval timeout;
|
|
SOCKADDR_IN6 who_to;
|
|
|
|
if ( !Psnet_active ) {
|
|
ml_string("Network ==> Socket not inited in psnet_send");
|
|
return 0;
|
|
}
|
|
|
|
psnet_addr_to_sockaddr(who_to_addr, &who_to);
|
|
|
|
if ( IN6_IS_ADDR_UNSPECIFIED(&who_to.sin6_addr) ) {
|
|
ml_string("Network ==> send to address is 0 in psnet_send");
|
|
return 0;
|
|
}
|
|
|
|
if (who_to.sin6_port == 0) {
|
|
ml_printf("Network ==> destination port %d invalid in psnet_send", ntohs(who_to.sin6_port));
|
|
return 0;
|
|
}
|
|
|
|
if ( psnet_is_local_addr(who_to_addr) ) {
|
|
return 0;
|
|
}
|
|
|
|
FD_ZERO(&wfds);
|
|
FD_SET(Psnet_socket, &wfds);
|
|
|
|
timeout.tv_sec = 0;
|
|
timeout.tv_usec = 0;
|
|
|
|
if ( SELECT(static_cast<int>(Psnet_socket+1), nullptr, &wfds, nullptr, &timeout, PSNET_TYPE_UNRELIABLE) == SOCKET_ERROR ) {
|
|
ml_printf("Error on blocking select for write %d", WSAGetLastError());
|
|
return 0;
|
|
}
|
|
|
|
// if the write file descriptor is not set, then bail!
|
|
if ( !FD_ISSET(Psnet_socket, &wfds) ) {
|
|
return 0;
|
|
}
|
|
|
|
multi_rate_add(np_index, "udp(h)", len + UDP_HEADER_SIZE);
|
|
multi_rate_add(np_index, "udp", len);
|
|
|
|
ret = SENDTO(Psnet_socket, reinterpret_cast<char *>(data), len, 0,
|
|
reinterpret_cast<LPSOCKADDR>(&who_to), sizeof(who_to),
|
|
PSNET_TYPE_UNRELIABLE);
|
|
|
|
if (ret != SOCKET_ERROR) {
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Get data from the unreliable socket
|
|
*/
|
|
int psnet_get(void *data, net_addr *addr)
|
|
{
|
|
SSIZE_T buffer_size;
|
|
SOCKADDR_IN6 from_addr;
|
|
|
|
if ( !Psnet_active ) {
|
|
return 0;
|
|
}
|
|
|
|
// try and get a free buffer and return its size
|
|
if ( psnet_buffer_get_next(&Psnet_top_buffers[PSNET_TYPE_UNRELIABLE], reinterpret_cast<ubyte *>(data), &buffer_size, &from_addr) ) {
|
|
psnet_sockaddr_to_addr(&from_addr, addr);
|
|
return static_cast<int>(buffer_size);
|
|
}
|
|
|
|
// return nothing
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Flush all sockets
|
|
*/
|
|
void psnet_flush()
|
|
{
|
|
ubyte data[MAX_TOP_LAYER_PACKET_SIZE];
|
|
net_addr from_addr;
|
|
|
|
if ( !Psnet_active ) {
|
|
return;
|
|
}
|
|
|
|
while ( psnet_get(data, &from_addr) > 0 );
|
|
}
|
|
|
|
/**
|
|
* Convert from sockaddr_in6 to net_addr struct
|
|
*/
|
|
static void psnet_sockaddr_to_addr(const SOCKADDR_IN6 *sockaddr, net_addr *addr)
|
|
{
|
|
memcpy(&addr->addr, &sockaddr->sin6_addr, sizeof(addr->addr));
|
|
addr->port = ntohs(sockaddr->sin6_port);
|
|
}
|
|
|
|
/**
|
|
* Convert from net_addr struct to sockaddr_in6
|
|
*/
|
|
static void psnet_addr_to_sockaddr(const net_addr *addr, SOCKADDR_IN6 *sockaddr)
|
|
{
|
|
memset(sockaddr, 0, sizeof(*sockaddr));
|
|
|
|
memcpy(&sockaddr->sin6_addr, &addr->addr, sizeof(sockaddr->sin6_addr));
|
|
|
|
sockaddr->sin6_family = AF_INET6;
|
|
sockaddr->sin6_port = htons(addr->port);
|
|
}
|
|
|
|
/**
|
|
* Convert from sockaddr_storage struct to sockaddr_in6
|
|
*/
|
|
static void psnet_sockaddr_storage_to_in6(const SOCKADDR_STORAGE *addr, SOCKADDR_IN6 *addr_in6)
|
|
{
|
|
if (addr->ss_family == AF_INET6) {
|
|
memcpy(addr_in6, addr, sizeof(SOCKADDR_IN6));
|
|
} else {
|
|
Assertion(addr->ss_family == AF_INET, "Invalid sockaddr type (%d)!", addr->ss_family);
|
|
auto *in4 = reinterpret_cast<const SOCKADDR_IN *>(addr);
|
|
|
|
memset(addr_in6, 0, sizeof(*addr_in6));
|
|
|
|
addr_in6->sin6_family = AF_INET6;
|
|
addr_in6->sin6_port = in4->sin_port;
|
|
psnet_map4to6(&in4->sin_addr, &addr_in6->sin6_addr);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Helper to map IPv4 to IPv6
|
|
*/
|
|
void psnet_map4to6(const in_addr *in4, in6_addr *in6)
|
|
{
|
|
if (in4->s_addr == INADDR_ANY) {
|
|
memcpy(in6, &in6addr_any, sizeof(in6_addr));
|
|
} else {
|
|
reinterpret_cast<uint32_t *>(in6)[0] = 0;
|
|
reinterpret_cast<uint32_t *>(in6)[1] = 0;
|
|
reinterpret_cast<uint32_t *>(in6)[2] = htonl(0xffff);
|
|
reinterpret_cast<uint32_t *>(in6)[3] = in4->s_addr;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Helper to map IPv6 to IPv4
|
|
*/
|
|
bool psnet_map6to4(const in6_addr *in6, in_addr *in4)
|
|
{
|
|
if ( !IN6_IS_ADDR_V4MAPPED(in6) ) {
|
|
return false;
|
|
}
|
|
|
|
in4->s_addr = reinterpret_cast<const uint32_t *>(in6)[3];
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Helper to anonymize IP address for logging purposes
|
|
*/
|
|
in6_addr *psnet_mask_addr(const in6_addr *inaddr)
|
|
{
|
|
static in6_addr addr;
|
|
size_t nbytes = 0;
|
|
|
|
memcpy(&addr, inaddr, sizeof(in6_addr));
|
|
|
|
if ( IN6_IS_ADDR_V4MAPPED(&addr) ) {
|
|
// zero last octet of IPv4 address
|
|
// (last byte)
|
|
nbytes = 1;
|
|
} else {
|
|
// zero last 80 bits of IPv6 address
|
|
// (last 10 bytes)
|
|
nbytes = 10;
|
|
}
|
|
|
|
memset(addr.s6_addr+(sizeof(addr)-nbytes), 0, nbytes);
|
|
|
|
return &addr;
|
|
}
|
|
|
|
/**
|
|
* Helper to quickly test if ip string is in standard notation
|
|
*/
|
|
static bool psnet_is_ip_notation(int af, const char *ip_string)
|
|
{
|
|
uint8_t addr[sizeof(in6_addr)];
|
|
|
|
if (ip_string == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
switch (af) {
|
|
case AF_INET6:
|
|
case AF_INET: {
|
|
if ( inet_pton(af, ip_string, addr) == 1 ) {
|
|
return true;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
default: {
|
|
if ( inet_pton(AF_INET6, ip_string, addr) == 1 ) {
|
|
return true;
|
|
}
|
|
|
|
if ( inet_pton(AF_INET, ip_string, addr) == 1 ) {
|
|
return true;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Helper to extract the ip address and port number from a string
|
|
*/
|
|
static bool psnet_explode_ip_string(const char *ip_string, SCP_string &host, SCP_string &port)
|
|
{
|
|
SCP_string ip;
|
|
|
|
host.clear();
|
|
port.clear();
|
|
|
|
if (ip_string == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
ip = ip_string;
|
|
|
|
auto obracket = ip.find_first_of('[');
|
|
auto colon = ip.find_first_of(':');
|
|
auto dot = ip.find_first_of('.');
|
|
|
|
//
|
|
// look for IPv6 formatted string first
|
|
//
|
|
|
|
// check for brackets and colon (ip with port)
|
|
if ( (obracket != SCP_string::npos) && (colon != SCP_string::npos) ) {
|
|
auto cbracket = ip.find_last_of(']');
|
|
|
|
// no closing bracket... fail
|
|
if (cbracket == SCP_string::npos) {
|
|
return false;
|
|
}
|
|
|
|
// closing bracket before opening bracket... fail
|
|
if (cbracket < obracket) {
|
|
return false;
|
|
}
|
|
|
|
// colon not between brackets... fail
|
|
if ( (colon < obracket) || (colon > cbracket) ) {
|
|
return false;
|
|
}
|
|
|
|
// ok, assume ipv6 and grab inside of brackets as address
|
|
host = ip.substr(obracket + 1, cbracket - obracket - 1);
|
|
|
|
// now check for port number after closing bracket
|
|
auto lcolon = ip.find_last_of(':');
|
|
|
|
if ( (lcolon != SCP_string::npos) && (cbracket < lcolon) ) {
|
|
port = ip.substr(lcolon + 1);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// if it has no dots then assume it's just IPv6
|
|
if (dot == SCP_string::npos) {
|
|
host = ip;
|
|
return true;
|
|
}
|
|
|
|
// if colon before dots then it's likely mapped IPv4
|
|
if ( (colon != SCP_string::npos) && (dot != SCP_string::npos) && (colon < dot) ) {
|
|
host = ip;
|
|
return true;
|
|
}
|
|
|
|
// otherwise it should be IPv4
|
|
// if it has a port then split it off
|
|
if (colon != SCP_string::npos) {
|
|
host = ip.substr(0, colon);
|
|
port = ip.substr(colon + 1);
|
|
} else {
|
|
host = ip;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* If the passed string is a valid IP string
|
|
*/
|
|
bool psnet_is_valid_ip_string(const char *ip_string)
|
|
{
|
|
SCP_string ip;
|
|
SCP_string host, port;
|
|
|
|
if (ip_string == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
// split string into host & port
|
|
if ( !psnet_explode_ip_string(ip_string, host, port) ) {
|
|
return false;
|
|
}
|
|
|
|
// if host is standard notation then we're done
|
|
if ( psnet_is_ip_notation(AF_UNSPEC, ip_string) ) {
|
|
return true;
|
|
}
|
|
|
|
// final check, may have to do DNS lookup on it
|
|
return psnet_get_addr(host.c_str(), nullptr, nullptr);
|
|
}
|
|
|
|
/**
|
|
* Get valid sockaddr structure
|
|
*/
|
|
bool psnet_get_addr(const char *host, const char *port, SOCKADDR_STORAGE *addr, int flags)
|
|
{
|
|
struct addrinfo hints, *srvinfo = nullptr;
|
|
bool success = false;
|
|
SOCKADDR_IN6 si4to6;
|
|
int rval;
|
|
|
|
memset(&si4to6, 0, sizeof(si4to6));
|
|
|
|
memset(&hints, 0, sizeof(hints));
|
|
|
|
hints.ai_family = AF_UNSPEC;
|
|
hints.ai_socktype = 0;
|
|
hints.ai_flags = AI_V4MAPPED;
|
|
|
|
if (flags & ADDR_FLAG_NUMERIC_SERVICE) {
|
|
hints.ai_flags |= AI_NUMERICSERV;
|
|
}
|
|
|
|
if (host == nullptr) {
|
|
hints.ai_flags |= AI_PASSIVE;
|
|
} else {
|
|
// avoid dns lookups if we can
|
|
if ( psnet_is_ip_notation(AF_UNSPEC, host) ) {
|
|
hints.ai_flags |= AI_NUMERICHOST;
|
|
}
|
|
// skip AAAA lookups if we can't use them
|
|
else if ( !(Psnet_ip_mode & PSNET_IP_MODE_V6) ) {
|
|
hints.ai_family = AF_INET;
|
|
}
|
|
// skip A lookups if we can't use them
|
|
else if ( !(Psnet_ip_mode & PSNET_IP_MODE_V4) ) {
|
|
hints.ai_family = AF_INET6;
|
|
}
|
|
}
|
|
|
|
if ( (rval = getaddrinfo(host, port, &hints, &srvinfo)) != 0 ) {
|
|
ml_printf("getaddrinfo() => %s", gai_strerror(rval));
|
|
|
|
if (srvinfo) {
|
|
freeaddrinfo(srvinfo);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
const bool prefer_v4 = ((flags & ADDR_FLAG_PREFER_IPV4) && (Psnet_ip_mode == PSNET_IP_MODE_DUAL));
|
|
|
|
for (auto *srv = srvinfo; srv != nullptr; srv = srv->ai_next) {
|
|
if ( (srv->ai_family == AF_INET) || (srv->ai_family == AF_INET6) ) {
|
|
if (addr) {
|
|
// map ipv4 to ipv6
|
|
if (srv->ai_family == AF_INET) {
|
|
auto *si4 = reinterpret_cast<SOCKADDR_IN *>(srv->ai_addr);
|
|
|
|
si4to6.sin6_family = AF_INET6;
|
|
si4to6.sin6_port = si4->sin_port;
|
|
|
|
psnet_map4to6(&si4->sin_addr, &si4to6.sin6_addr);
|
|
|
|
memcpy(addr, &si4to6, sizeof(si4to6));
|
|
} else if ( !success ) {
|
|
memcpy(addr, srv->ai_addr, srv->ai_addrlen);
|
|
}
|
|
}
|
|
|
|
success = true;
|
|
|
|
// if we would prefer an IPv4 address then maybe keep looking
|
|
if ( prefer_v4 && addr && (srv->ai_family == AF_INET6) ) {
|
|
continue;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
freeaddrinfo(srvinfo);
|
|
|
|
return success;
|
|
}
|
|
|
|
bool psnet_get_addr(const char *host, const uint16_t port, SOCKADDR_STORAGE *addr, int flags)
|
|
{
|
|
SCP_string port_str = std::to_string(port);
|
|
|
|
return psnet_get_addr(host, port_str.c_str(), addr, flags | ADDR_FLAG_NUMERIC_SERVICE);
|
|
}
|
|
|
|
const in6_addr *psnet_get_local_ip(int af_type)
|
|
{
|
|
switch (af_type) {
|
|
case AF_INET: {
|
|
if (Psnet_ip_mode == PSNET_IP_MODE_DUAL) {
|
|
return &Psnet_my_ip[1];
|
|
} else if (Psnet_ip_mode & PSNET_IP_MODE_V4) {
|
|
return &Psnet_my_ip[0];
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case AF_INET6: {
|
|
if (Psnet_ip_mode & PSNET_IP_MODE_V6) {
|
|
return &Psnet_my_ip[0];
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
// -------------------------------------------------------------------------------------------------------
|
|
// PSNET 2 RELIABLE SOCKET FUNCTIONS
|
|
//
|
|
|
|
void psnet_rel_send_ack(SOCKADDR_IN6 *raddr, ushort sig, float time_sent)
|
|
{
|
|
int ret;
|
|
ushort sig_tmp;
|
|
reliable_header ack_header;
|
|
|
|
ack_header.type = RNT_ACK;
|
|
ack_header.data_len = sizeof(ushort);
|
|
ack_header.send_time = INTEL_FLOAT(&time_sent);
|
|
|
|
sig_tmp = INTEL_SHORT(sig);
|
|
memcpy(&ack_header.data, &sig_tmp, sizeof(ushort));
|
|
|
|
ret = SENDTO(Psnet_socket, reinterpret_cast<char *>(&ack_header),
|
|
RELIABLE_PACKET_HEADER_ONLY_SIZE + ack_header.data_len, 0,
|
|
reinterpret_cast<LPSOCKADDR>(raddr), sizeof(*raddr), PSNET_TYPE_RELIABLE);
|
|
|
|
if (ret == -1) {
|
|
ml_string("TCP SENDTO failed in rel_send_ack()");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Function to shutdown and close the given socket.
|
|
*
|
|
* It takes a couple of things into consideration when closing, such as possibly
|
|
* reiniting reliable sockets if they are closed here.
|
|
*/
|
|
void psnet_rel_close_socket(PSNET_SOCKET_RELIABLE socketid)
|
|
{
|
|
reliable_header diss_conn_header;
|
|
reliable_socket *rsocket = nullptr;
|
|
|
|
// if the socket is out of range
|
|
if (socketid >= MAXRELIABLESOCKETS) {
|
|
if (socketid != PSNET_INVALID_SOCKET) {
|
|
ml_printf("Invalid socket id passed to psnet_rel_close_socket() -- %d", socketid);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
ml_printf("Closing socket %d", socketid);
|
|
|
|
rsocket = &Reliable_sockets[socketid];
|
|
|
|
// go through every buffer and "free it up(tm)"
|
|
for (auto i = 0; i < MAXNETBUFFERS; i++) {
|
|
if (rsocket->rbuffers[i]) {
|
|
vm_free(rsocket->rbuffers[i]);
|
|
|
|
rsocket->rbuffers[i] = nullptr;
|
|
rsocket->rsequence[i] = 0;
|
|
}
|
|
|
|
if (rsocket->sbuffers[i]) {
|
|
vm_free(rsocket->sbuffers[i]);
|
|
|
|
rsocket->sbuffers[i] = nullptr;
|
|
rsocket->ssequence[i] = 0;
|
|
}
|
|
}
|
|
|
|
// send a disconnect packet to the socket on the other end
|
|
diss_conn_header.type = RNT_DISCONNECT;
|
|
diss_conn_header.seq = CONNECTSEQ;
|
|
diss_conn_header.data_len = 0;
|
|
|
|
if (socketid == Serverconn) {
|
|
Serverconn = 0xffffffff;
|
|
}
|
|
|
|
SENDTO(Psnet_socket, reinterpret_cast<char *>(&diss_conn_header), RELIABLE_PACKET_HEADER_ONLY_SIZE,
|
|
0, reinterpret_cast<LPSOCKADDR>(&rsocket->addr), sizeof(rsocket->addr), PSNET_TYPE_RELIABLE);
|
|
|
|
memset(rsocket, 0, sizeof(reliable_socket));
|
|
rsocket->status = RNF_UNUSED;
|
|
}
|
|
|
|
/**
|
|
* Send data reliably
|
|
*/
|
|
int psnet_rel_send(PSNET_SOCKET_RELIABLE socketid, ubyte *data, int length, int np_index) // NOLINT(misc-unused-parameters)
|
|
{
|
|
int i;
|
|
int bytesout = 0;
|
|
reliable_socket *rsocket;
|
|
|
|
if (socketid >= MAXRELIABLESOCKETS) {
|
|
ml_printf("Invalid socket id passed to psnet_rel_send() -- %d", socketid);
|
|
return -1;
|
|
}
|
|
|
|
Assert(length <= MAX_PACKET_SIZE);
|
|
|
|
psnet_rel_work();
|
|
|
|
rsocket = &Reliable_sockets[socketid];
|
|
|
|
if (rsocket->status != RNF_CONNECTED) {
|
|
// We can't send because this isn't a connected reliable socket.
|
|
ml_printf("Can't send packet because of status %d in psnet_rel_send(). socket = %d", rsocket->status, socketid);
|
|
return -1;
|
|
}
|
|
|
|
// Add the new packet to the sending list and send it.
|
|
for (i = 0; i < MAXNETBUFFERS; i++) {
|
|
if (rsocket->sbuffers[i] == nullptr) {
|
|
reliable_header send_header;
|
|
int send_this_packet = 1;
|
|
|
|
rsocket->send_len[i] = length;
|
|
rsocket->sbuffers[i] = reinterpret_cast<reliable_net_buffer *>(vm_malloc(sizeof(reliable_net_buffer)));
|
|
|
|
memcpy(rsocket->sbuffers[i]->buffer, data, static_cast<size_t>(length));
|
|
|
|
send_header.seq = INTEL_SHORT(rsocket->theirsequence);
|
|
rsocket->ssequence[i] = rsocket->theirsequence;
|
|
|
|
memcpy(send_header.data, data, static_cast<size_t>(length));
|
|
|
|
send_header.data_len = INTEL_SHORT( static_cast<ushort>(length) );
|
|
send_header.type = RNT_DATA;
|
|
send_header.send_time = psnet_get_time();
|
|
send_header.send_time = INTEL_FLOAT( &send_header.send_time ) ;
|
|
|
|
if (send_this_packet) {
|
|
multi_rate_add(np_index, "tcp(h)", RELIABLE_PACKET_HEADER_ONLY_SIZE+rsocket->send_len[i]);
|
|
|
|
bytesout = SENDTO(Psnet_socket, reinterpret_cast<char *>(&send_header),
|
|
static_cast<int>(RELIABLE_PACKET_HEADER_ONLY_SIZE) + rsocket->send_len[i], 0,
|
|
reinterpret_cast<LPSOCKADDR>(&rsocket->addr), sizeof(rsocket->addr),
|
|
PSNET_TYPE_RELIABLE);
|
|
}
|
|
|
|
if ( (bytesout == SOCKET_ERROR) && (WSAGetLastError() == WSAEWOULDBLOCK) ) {
|
|
// This will cause it to try to send again next frame. (or sooner)
|
|
rsocket->timesent[i] = psnet_get_time() - (NETRETRYTIME*4);
|
|
} else {
|
|
rsocket->timesent[i] = psnet_get_time();
|
|
}
|
|
|
|
rsocket->theirsequence++;
|
|
|
|
return bytesout;
|
|
}
|
|
}
|
|
|
|
ml_printf("PSNET RELIABLE SEND BUFFER OVERRUN. socket = %d", socketid);
|
|
|
|
return 0;
|
|
}
|
|
|
|
// Return codes:
|
|
// -1 socket not connected
|
|
// 0 No packet ready to receive
|
|
// >0 Buffer filled with the number of bytes recieved
|
|
int psnet_rel_get(PSNET_SOCKET socketid, ubyte *buffer, int max_length)
|
|
{
|
|
reliable_socket *rsocket = nullptr;
|
|
|
|
psnet_rel_work();
|
|
|
|
if (socketid >= MAXRELIABLESOCKETS) {
|
|
ml_printf("Invalid socket id passed to psnet_rel_get() -- %d", socketid);
|
|
return -1;
|
|
}
|
|
|
|
rsocket = &Reliable_sockets[socketid];
|
|
|
|
if ( (rsocket->status != RNF_CONNECTED) && (rsocket->status != RNF_LIMBO) ) {
|
|
ml_printf("Can't receive packet because it isn't connected in psnet_rel_get(). socket = %d", socketid);
|
|
return 0;
|
|
}
|
|
|
|
// If the buffer position is the position we are waiting for, fill in
|
|
// the buffer we received in the call to this function and return true
|
|
|
|
for (auto i = 0; i < MAXNETBUFFERS; i++) {
|
|
if ( (rsocket->rsequence[i] == rsocket->oursequence) && rsocket->rbuffers[i] ) {
|
|
Assert(max_length >= rsocket->recv_len[i]);
|
|
|
|
if (max_length < rsocket->recv_len[i]) {
|
|
ml_printf("Data too large for buffer - size %d, max %d. socket = %d",
|
|
rsocket->recv_len[i], max_length, socketid);
|
|
|
|
return 0;
|
|
}
|
|
|
|
memcpy(buffer, rsocket->rbuffers[i]->buffer, static_cast<size_t>(rsocket->recv_len[i]));
|
|
|
|
vm_free(rsocket->rbuffers[i]);
|
|
rsocket->rbuffers[i] = nullptr;
|
|
|
|
rsocket->rsequence[i] = 0;
|
|
rsocket->oursequence++;
|
|
|
|
return rsocket->recv_len[i];
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Process all active reliable sockets
|
|
*/
|
|
void psnet_rel_work()
|
|
{
|
|
int i, j;
|
|
int rcode = -1;
|
|
fd_set read_fds;
|
|
struct timeval timeout;
|
|
reliable_header rcv_buff;
|
|
SOCKADDR_STORAGE rcv_addr;
|
|
auto *addr6 = reinterpret_cast<SOCKADDR_IN6 *>(&rcv_addr);
|
|
char addr_string[INET6_ADDRSTRLEN];
|
|
|
|
if ( !Psnet_active ) {
|
|
return;
|
|
}
|
|
|
|
PSNET_TOP_LAYER_PROCESS();
|
|
|
|
// negotitate initial connection with the server
|
|
reliable_socket *rsocket = nullptr;
|
|
|
|
if (Serverconn != 0xffffffff) {
|
|
// Check to see if we need to send a packet out.
|
|
if ( (Reliable_sockets[Serverconn].status == RNF_LIMBO) &&
|
|
((Serverconn != 0xffffffff) && fl_abs((psnet_get_time() - Last_sent_iamhere)) > NETRETRYTIME) )
|
|
{
|
|
rsocket = &Reliable_sockets[Serverconn];
|
|
|
|
reliable_header conn_header;
|
|
|
|
// Now send I_AM_HERE packet
|
|
conn_header.type = RNT_I_AM_HERE;
|
|
conn_header.seq = static_cast<ushort>(~CONNECTSEQ);
|
|
conn_header.data_len = 0;
|
|
|
|
Last_sent_iamhere = psnet_get_time();
|
|
|
|
int ret = SENDTO(Psnet_socket, reinterpret_cast<char *>(&conn_header), RELIABLE_PACKET_HEADER_ONLY_SIZE, 0,
|
|
reinterpret_cast<LPSOCKADDR>(&rsocket->addr), sizeof(rsocket->addr), PSNET_TYPE_RELIABLE);
|
|
|
|
|
|
if ( (ret == SOCKET_ERROR) && (WSAGetLastError() == WSAEWOULDBLOCK) ) {
|
|
rsocket->last_packet_sent = psnet_get_time() - NETRETRYTIME;
|
|
} else {
|
|
rsocket->last_packet_sent = psnet_get_time();
|
|
}
|
|
}
|
|
}
|
|
|
|
do {
|
|
rsocket = nullptr;
|
|
|
|
// Check UDP
|
|
|
|
timeout.tv_sec = 0;
|
|
timeout.tv_usec = 0;
|
|
|
|
FD_ZERO(&read_fds);
|
|
FD_SET(Psnet_socket, &read_fds);
|
|
|
|
if ( SELECT(static_cast<int>(Psnet_socket+1), &read_fds, nullptr, nullptr, &timeout, PSNET_TYPE_RELIABLE) == SOCKET_ERROR ) {
|
|
break;
|
|
}
|
|
|
|
// if the file descriptor is not set, then bail!
|
|
if ( !FD_ISSET(Psnet_socket, &read_fds) ) {
|
|
break;
|
|
}
|
|
|
|
memset(&rcv_addr, 0, sizeof(rcv_addr));
|
|
int addrlen = sizeof(rcv_addr);
|
|
|
|
int bytesin = RECVFROM(Psnet_socket, reinterpret_cast<char *>(&rcv_buff), sizeof(reliable_header), 0,
|
|
reinterpret_cast<LPSOCKADDR>(&rcv_addr), &addrlen, PSNET_TYPE_RELIABLE);
|
|
|
|
if (bytesin == 0) {
|
|
break;
|
|
}
|
|
|
|
if (bytesin == -1) {
|
|
ml_printf("recvfrom returned an error! -- %d",WSAGetLastError());
|
|
|
|
break;
|
|
}
|
|
|
|
rcv_buff.seq = INTEL_SHORT( rcv_buff.seq ); //-V570
|
|
rcv_buff.data_len = INTEL_SHORT( rcv_buff.data_len ); //-V570
|
|
rcv_buff.send_time = INTEL_FLOAT( &rcv_buff.send_time );
|
|
|
|
// Someone wants to connect, so find a slot
|
|
if (rcv_buff.type == RNT_REQ_CONN) {
|
|
for (i = 1; i < MAXRELIABLESOCKETS; i++) {
|
|
if ( (Reliable_sockets[i].status == RNF_CONNECTED) || (Reliable_sockets[i].status == RNF_LIMBO) ) {
|
|
if ( psnet_same(addr6, &Reliable_sockets[i].addr) ) {
|
|
// We already have a reliable link to this user, so we will ignore it...
|
|
ml_printf("Received duplicate connection request. %d",i);
|
|
|
|
psnet_rel_send_ack(&Reliable_sockets[i].addr, rcv_buff.seq, rcv_buff.send_time);
|
|
|
|
// We will change this as a hack to prevent later code from hooking us up
|
|
rcv_buff.type = 0xff;
|
|
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (i = 1; i < MAXRELIABLESOCKETS; i++) {
|
|
if (Reliable_sockets[i].status == RNF_UNUSED) {
|
|
// Add the new connection here.
|
|
rsocket = &Reliable_sockets[i];
|
|
|
|
psnet_sockaddr_storage_to_in6(&rcv_addr, &Reliable_sockets[i].addr);
|
|
|
|
rsocket->ping_pos = 0;
|
|
rsocket->num_ping_samples = 0;
|
|
rsocket->status = RNF_LIMBO;
|
|
rsocket->last_packet_received = psnet_get_time();
|
|
|
|
const char *ip_string = inet_ntop(AF_INET6, psnet_mask_addr(&addr6->sin6_addr), addr_string, sizeof(addr_string));
|
|
ml_printf("Connect from [%s]:%d", ip_string ? ip_string : "unknown", ntohs(addr6->sin6_port));
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (i == MAXRELIABLESOCKETS) {
|
|
// No more connections!
|
|
ml_string("Out of incoming reliable connection sockets");
|
|
|
|
continue;
|
|
}
|
|
|
|
psnet_rel_send_ack(&rsocket->addr, rcv_buff.seq, rcv_buff.send_time);
|
|
}
|
|
|
|
// Find out if this is a packet from someone we were expecting a packet.
|
|
for (i = 1; i < MAXRELIABLESOCKETS; i++) {
|
|
if ( psnet_same(addr6, &Reliable_sockets[i].addr) ) {
|
|
rsocket = &Reliable_sockets[i];
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (rsocket == nullptr) {
|
|
const char *ip_string = inet_ntop(AF_INET6, psnet_mask_addr(&addr6->sin6_addr), addr_string, sizeof(addr_string));
|
|
ml_string("Received reliable data from unconnected client.");
|
|
ml_printf("Received from [%s]:%d\n", ip_string ? ip_string : "unknown", ntohs(addr6->sin6_port));
|
|
|
|
continue;
|
|
}
|
|
|
|
rsocket->last_packet_received = psnet_get_time();
|
|
|
|
if (rsocket->status != RNF_CONNECTED) {
|
|
// Get out of limbo
|
|
if (rsocket->status == RNF_LIMBO) {
|
|
// this is our connection to the server
|
|
if (Serverconn != 0xffffffff) {
|
|
if (rcv_buff.type == RNT_ACK) {
|
|
auto *acknum = reinterpret_cast<ushort *>(&rcv_buff.data);
|
|
|
|
if (INTEL_SHORT(*acknum) == (~CONNECTSEQ & 0xffff)) {
|
|
rsocket->status = RNF_CONNECTED;
|
|
ml_string("Got ACK for IAMHERE!");
|
|
}
|
|
|
|
continue;
|
|
}
|
|
} else if (rcv_buff.type == RNT_I_AM_HERE) {
|
|
rsocket->status = RNF_CONNECTING;
|
|
psnet_rel_send_ack(&rsocket->addr, rcv_buff.seq, rcv_buff.send_time);
|
|
ml_string("Got IAMHERE!");
|
|
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if ( (rcv_buff.type == RNT_DATA) && (Serverconn != 0xffffffff) ) {
|
|
rsocket->status = RNF_CONNECTED;
|
|
} else {
|
|
rsocket->last_packet_received = psnet_get_time();
|
|
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Update the last recv variable so we don't need a heartbeat
|
|
rsocket->last_packet_received = psnet_get_time();
|
|
|
|
if (rcv_buff.type == RNT_HEARTBEAT) {
|
|
continue;
|
|
}
|
|
|
|
if (rcv_buff.type == RNT_ACK) {
|
|
// Update ping time
|
|
rsocket->num_ping_samples++;
|
|
|
|
rsocket->pings[rsocket->ping_pos] = rsocket->last_packet_received - rcv_buff.send_time;
|
|
|
|
if (rsocket->num_ping_samples >= MAX_PING_HISTORY) {
|
|
float sort_ping[MAX_PING_HISTORY];
|
|
|
|
for (auto a = 0; a < MAX_PING_HISTORY; a++) {
|
|
sort_ping[a] = rsocket->pings[a];
|
|
}
|
|
|
|
std::sort(sort_ping, sort_ping + MAX_PING_HISTORY);
|
|
rsocket->mean_ping = ( (sort_ping[MAX_PING_HISTORY/2] + sort_ping[(MAX_PING_HISTORY/2) + 1]) ) / 2;
|
|
}
|
|
|
|
rsocket->ping_pos++;
|
|
|
|
if (rsocket->ping_pos >= MAX_PING_HISTORY) {
|
|
rsocket->ping_pos = 0;
|
|
}
|
|
|
|
// if this is an ack for a send buffer on the socket, kill the send buffer. its done
|
|
auto *acksig = reinterpret_cast<ushort *>(&rcv_buff.data);
|
|
|
|
for (i = 0; i < MAXNETBUFFERS; i++) {
|
|
if ( rsocket->sbuffers[i] && (rsocket->ssequence[i] == INTEL_SHORT(*acksig)) ) {
|
|
vm_free(rsocket->sbuffers[i]);
|
|
rsocket->sbuffers[i] = nullptr;
|
|
rsocket->ssequence[i] = 0;
|
|
}
|
|
}
|
|
|
|
// remove that packet from the send buffer
|
|
rsocket->last_packet_received = psnet_get_time();
|
|
|
|
continue;
|
|
}
|
|
|
|
if (rcv_buff.type == RNT_DATA_COMP) {
|
|
// More2Come
|
|
// Decompress it. Put it back in the buffer. Process it as RNT_DATA
|
|
rcv_buff.type = RNT_DATA;
|
|
}
|
|
|
|
if (rcv_buff.type == RNT_DATA) {
|
|
// If the data is out of order by >= MAXNETBUFFERS-1 ignore that packet for now
|
|
int seqdelta = rcv_buff.seq - rsocket->oursequence;
|
|
|
|
if (seqdelta < 0) {
|
|
seqdelta = -seqdelta;
|
|
}
|
|
|
|
if ( seqdelta >= (MAXNETBUFFERS-1) ) {
|
|
ml_string("Received reliable packet out of order!");
|
|
|
|
// It's out of order, so we won't ack it, which will mean we will get it again soon.
|
|
continue;
|
|
}
|
|
|
|
//e lse move data into the proper buffer position
|
|
int savepacket = 1;
|
|
|
|
if ( rsocket->oursequence < (0xffff - (MAXNETBUFFERS-1)) ) {
|
|
if (rsocket->oursequence > rcv_buff.seq) {
|
|
savepacket = 0;
|
|
}
|
|
} else {
|
|
// Sequence is high, so prepare for wrap around
|
|
if ( static_cast<unsigned short>(rcv_buff.seq + rsocket->oursequence) > (MAXNETBUFFERS-1) ) {
|
|
savepacket = 0;
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < MAXNETBUFFERS; i++) {
|
|
if ( (rsocket->rbuffers[i] != nullptr) && (rsocket->rsequence[i] == rcv_buff.seq) ) {
|
|
// Received duplicate packet!
|
|
savepacket = 0;
|
|
}
|
|
}
|
|
|
|
if (savepacket) {
|
|
if (rcv_buff.data_len > MAX_PACKET_SIZE) {
|
|
ml_string("Received oversized reliable packet!");
|
|
|
|
// don't ack it, which will mean we will get it again soon.
|
|
continue;
|
|
}
|
|
|
|
for (i = 0; i < MAXNETBUFFERS; i++) {
|
|
if (rsocket->rbuffers[i] == nullptr) {
|
|
rsocket->recv_len[i] = rcv_buff.data_len;
|
|
|
|
rsocket->rbuffers[i] = reinterpret_cast<reliable_net_buffer *>(vm_malloc(sizeof(reliable_net_buffer)));
|
|
|
|
memcpy(rsocket->rbuffers[i]->buffer, rcv_buff.data, static_cast<size_t>(rsocket->recv_len[i]));
|
|
rsocket->rsequence[i] = rcv_buff.seq;
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
psnet_rel_send_ack(&rsocket->addr, rcv_buff.seq, rcv_buff.send_time);
|
|
}
|
|
} while (true);
|
|
|
|
// Go through each reliable socket that is connected and do any needed work.
|
|
for (j = 0; j < MAXRELIABLESOCKETS; j++) {
|
|
rsocket = &Reliable_sockets[j];
|
|
|
|
if (Serverconn == 0xffffffff) {
|
|
if (rsocket->status == RNF_LIMBO) {
|
|
if ( fl_abs((psnet_get_time() - rsocket->last_packet_received)) > Nettimeout ) {
|
|
ml_printf("Reliable (but in limbo) socket (%d) timed out in psnet_rel_work().", j);
|
|
|
|
for (auto a = 0; a < MAXNETBUFFERS; a++) {
|
|
if (rsocket->sbuffers[a] != nullptr) {
|
|
vm_free(rsocket->sbuffers[a]);
|
|
}
|
|
|
|
if (rsocket->rbuffers[a] != nullptr) {
|
|
vm_free(rsocket->rbuffers[a]);
|
|
}
|
|
}
|
|
|
|
memset(rsocket, 0, sizeof(reliable_socket));
|
|
rsocket->status = RNF_UNUSED; // Won't work if this is an outgoing connection.
|
|
}
|
|
}
|
|
} else {
|
|
if ( (rsocket->status == RNF_LIMBO) && (fl_abs((psnet_get_time() - First_sent_iamhere)) > Nettimeout) ) {
|
|
rsocket->status = RNF_BROKEN;
|
|
ml_printf("Reliable socket (%d) timed out in psnet_rel_work().", j);
|
|
}
|
|
}
|
|
|
|
if (rsocket->status == RNF_CONNECTED) {
|
|
float retry_packet_time;
|
|
|
|
if ( ((rsocket->mean_ping < 0.00001f) && (rsocket->mean_ping > -0.00001f)) || (rsocket->mean_ping > (NETRETRYTIME*4)) ) {
|
|
retry_packet_time = NETRETRYTIME;
|
|
} else {
|
|
if (rsocket->mean_ping < MIN_NET_RETRYTIME) {
|
|
retry_packet_time = static_cast<float>(MIN_NET_RETRYTIME);
|
|
} else {
|
|
retry_packet_time = rsocket->mean_ping * 1.25f;
|
|
}
|
|
}
|
|
|
|
// Iterate through send buffers.
|
|
for (i = 0;i < MAXNETBUFFERS; i++) {
|
|
// send again
|
|
if ( rsocket->sbuffers[i] && (fl_abs((psnet_get_time() - rsocket->timesent[i])) >= retry_packet_time) ) {
|
|
reliable_header send_header;
|
|
|
|
memcpy(send_header.data, rsocket->sbuffers[i]->buffer, static_cast<size_t>(rsocket->send_len[i]));
|
|
|
|
send_header.data_len = INTEL_SHORT( static_cast<ushort>(rsocket->send_len[i]) );
|
|
send_header.send_time = psnet_get_time();
|
|
send_header.send_time = INTEL_FLOAT( &send_header.send_time );
|
|
send_header.seq = INTEL_SHORT( rsocket->ssequence[i] );
|
|
send_header.type = RNT_DATA;
|
|
|
|
rcode = SENDTO(Psnet_socket, reinterpret_cast<char *>(&send_header),
|
|
static_cast<int>(RELIABLE_PACKET_HEADER_ONLY_SIZE) + rsocket->send_len[i], 0,
|
|
reinterpret_cast<LPSOCKADDR>(&rsocket->addr), sizeof(rsocket->addr),
|
|
PSNET_TYPE_RELIABLE);
|
|
|
|
if ( (rcode == SOCKET_ERROR) && (WSAGetLastError() == WSAEWOULDBLOCK) ) {
|
|
// The packet didn't get sent, flag it to try again next frame
|
|
rsocket->timesent[i] = psnet_get_time() - (NETRETRYTIME*4);
|
|
} else {
|
|
rsocket->last_packet_sent = psnet_get_time();
|
|
rsocket->timesent[i] = psnet_get_time();
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( (rsocket->status == RNF_CONNECTED) && (fl_abs((psnet_get_time() - rsocket->last_packet_sent)) > NETHEARTBEATTIME) ) {
|
|
reliable_header send_header;
|
|
|
|
send_header.send_time = psnet_get_time();
|
|
send_header.send_time = INTEL_FLOAT( &send_header.send_time );
|
|
send_header.seq = 0;
|
|
send_header.data_len = 0;
|
|
send_header.type = RNT_HEARTBEAT;
|
|
|
|
rcode = SENDTO(Psnet_socket, reinterpret_cast<char *>(&send_header),
|
|
RELIABLE_PACKET_HEADER_ONLY_SIZE, 0,
|
|
reinterpret_cast<LPSOCKADDR>(&rsocket->addr), sizeof(rsocket->addr),
|
|
PSNET_TYPE_RELIABLE);
|
|
|
|
if ( (rcode != SOCKET_ERROR) && (WSAGetLastError() != WSAEWOULDBLOCK) ) {
|
|
// It must have been sent
|
|
rsocket->last_packet_sent = psnet_get_time();
|
|
}
|
|
}
|
|
|
|
if ( (rsocket->status == RNF_CONNECTED) && (fl_abs((psnet_get_time() - rsocket->last_packet_received))>Nettimeout) ) {
|
|
// This socket is hosed.....inform someone?
|
|
ml_printf("Reliable Socket (%d) timed out in psnet_rel_work().", j);
|
|
|
|
rsocket->status = RNF_BROKEN;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the status of a reliable socket, see RNF_* defines above
|
|
*/
|
|
int psnet_rel_get_status(PSNET_SOCKET_RELIABLE socketid)
|
|
{
|
|
if (socketid >= MAXRELIABLESOCKETS) {
|
|
return -1;
|
|
}
|
|
|
|
return Reliable_sockets[socketid].status;
|
|
}
|
|
|
|
/**
|
|
* Checks the Listen_socket for possibly incoming requests to be connected.
|
|
* @return 0 on error or nothing waiting. 1 if we should try to accept
|
|
*/
|
|
PSNET_SOCKET psnet_rel_check_for_listen(net_addr *from_addr)
|
|
{
|
|
psnet_rel_work();
|
|
|
|
for (unsigned int i = 1; i < MAXRELIABLESOCKETS; i++) {
|
|
if (Reliable_sockets[i].status == RNF_CONNECTING) {
|
|
Reliable_sockets[i].status = RNF_CONNECTED;
|
|
|
|
ml_string("New reliable connection in psnet_rel_check_for_listen().");
|
|
|
|
psnet_sockaddr_to_addr(&Reliable_sockets[i].addr, from_addr);
|
|
|
|
return i;
|
|
}
|
|
}
|
|
|
|
return PSNET_INVALID_SOCKET;
|
|
}
|
|
|
|
/**
|
|
* Attempt to connect() to the server's tcp socket. socket parameter is simply assigned to the
|
|
* Reliable_socket socket created in psnet_init
|
|
*/
|
|
void psnet_rel_connect_to_server(PSNET_SOCKET *socket, net_addr *server_addr)
|
|
{
|
|
// Send out a RNT_REQ_CONN packet, and wait for it to be acked.
|
|
SOCKADDR_STORAGE rcv_addr;
|
|
SOCKADDR_IN6 srv_addr;
|
|
int addrlen;
|
|
int rcode;
|
|
float first_sent_req = 0;
|
|
reliable_header rheader;
|
|
int bytesin;
|
|
struct timeval timeout;
|
|
fd_set read_fds;
|
|
reliable_socket *rsocket;
|
|
|
|
*socket = PSNET_INVALID_SOCKET;
|
|
|
|
psnet_addr_to_sockaddr(server_addr, &srv_addr);
|
|
|
|
|
|
// Flush out any left overs
|
|
do {
|
|
timeout.tv_sec = 0;
|
|
timeout.tv_usec = 0;
|
|
|
|
FD_ZERO(&read_fds);
|
|
FD_SET(Psnet_socket, &read_fds);
|
|
|
|
if ( SELECT(static_cast<int>(Psnet_socket+1), &read_fds, nullptr, nullptr, &timeout, PSNET_TYPE_RELIABLE) == SOCKET_ERROR ) {
|
|
break;
|
|
}
|
|
|
|
// if the file descriptor is not set, then bail!
|
|
if ( !FD_ISSET(Psnet_socket, &read_fds) ) {
|
|
break;
|
|
}
|
|
|
|
addrlen = sizeof(rcv_addr);
|
|
bytesin = RECVFROM(Psnet_socket, reinterpret_cast<char *>(&rheader), sizeof(reliable_header), 0,
|
|
reinterpret_cast<LPSOCKADDR>(&rcv_addr), &addrlen, PSNET_TYPE_RELIABLE);
|
|
|
|
if (bytesin < 0) {
|
|
ml_printf("UDP recvfrom returned an error! -- %d", WSAGetLastError());
|
|
break;
|
|
}
|
|
} while (true);
|
|
|
|
|
|
rheader.type = RNT_REQ_CONN;
|
|
rheader.seq = CONNECTSEQ;
|
|
rheader.data_len = 0;
|
|
|
|
rcode = SENDTO(Psnet_socket, reinterpret_cast<char *>(&rheader), RELIABLE_PACKET_HEADER_ONLY_SIZE, 0,
|
|
reinterpret_cast<LPSOCKADDR>(&srv_addr), sizeof(srv_addr), PSNET_TYPE_RELIABLE);
|
|
|
|
if (rcode == SOCKET_ERROR) {
|
|
ml_printf("Unable to send UDP packet in psnet_rel_connect_to_server()! -- %d", WSAGetLastError());
|
|
return;
|
|
}
|
|
|
|
first_sent_req = psnet_get_time();
|
|
|
|
|
|
// Wait until we get a response from the server or we timeout
|
|
do {
|
|
PSNET_TOP_LAYER_PROCESS();
|
|
|
|
timeout.tv_sec = 0;
|
|
timeout.tv_usec = 0;
|
|
|
|
FD_ZERO(&read_fds);
|
|
FD_SET(Psnet_socket, &read_fds);
|
|
|
|
if ( SELECT(static_cast<int>(Psnet_socket+1), &read_fds, nullptr, nullptr, &timeout, PSNET_TYPE_RELIABLE) == SOCKET_ERROR ) {
|
|
break;
|
|
}
|
|
|
|
// if the file descriptor is not set, then bail!
|
|
if ( !FD_ISSET(Psnet_socket, &read_fds) ) {
|
|
continue;
|
|
}
|
|
|
|
ml_string("selected() in psnet_rel_connect_to_server()");
|
|
|
|
addrlen = sizeof(rcv_addr);
|
|
bytesin = RECVFROM(Psnet_socket, reinterpret_cast<char *>(&rheader), sizeof(reliable_header), 0,
|
|
reinterpret_cast<LPSOCKADDR>(&rcv_addr) ,&addrlen, PSNET_TYPE_RELIABLE);
|
|
|
|
if (bytesin == 0) {
|
|
ml_string("Received 0 bytes from recvfrom() in psnet_rel_connect_to_server().");
|
|
continue;
|
|
}
|
|
|
|
if (bytesin == -1) {
|
|
ml_printf("recvfrom returned an error! -- %d", WSAGetLastError());
|
|
Int3();
|
|
return;
|
|
}
|
|
|
|
ml_string("received data after select in psnet_rel_connect_to_server()");
|
|
|
|
ml_string("about to check ack_header.type");
|
|
|
|
if (rheader.type != RNT_ACK) {
|
|
ml_string("Received something that isn't an ACK in psnet_rel_connect_to_server().");
|
|
continue;
|
|
}
|
|
|
|
auto *acknum = reinterpret_cast<ushort *>(&rheader.data);
|
|
|
|
if (INTEL_SHORT(*acknum) != CONNECTSEQ) {
|
|
ml_string("Received out of sequence ACK in psnet_rel_connect_to_server().");
|
|
continue;
|
|
}
|
|
|
|
|
|
for (unsigned int i = 1; i < MAXRELIABLESOCKETS; i++) {
|
|
rsocket = &Reliable_sockets[i];
|
|
|
|
if (rsocket->status == RNF_UNUSED) {
|
|
// Add the new connection here.
|
|
memset(rsocket, 0, sizeof(reliable_socket));
|
|
|
|
rsocket->last_packet_received = psnet_get_time();
|
|
psnet_sockaddr_storage_to_in6(&rcv_addr, &rsocket->addr);
|
|
rsocket->status = RNF_LIMBO;
|
|
|
|
ml_string("Successfully connected to server in psnet_rel_connect_to_server().");
|
|
|
|
First_sent_iamhere = psnet_get_time();
|
|
Last_sent_iamhere = psnet_get_time();
|
|
|
|
// Now send I_AM_HERE packet
|
|
rheader.type = RNT_I_AM_HERE;
|
|
rheader.seq = static_cast<ushort>(~CONNECTSEQ);
|
|
rheader.data_len = 0;
|
|
|
|
rcode = SENDTO(Psnet_socket, reinterpret_cast<char *>(&rheader), RELIABLE_PACKET_HEADER_ONLY_SIZE, 0,
|
|
reinterpret_cast<LPSOCKADDR>(&srv_addr), sizeof(srv_addr), PSNET_TYPE_RELIABLE);
|
|
|
|
if (rcode == SOCKET_ERROR) {
|
|
*socket = PSNET_INVALID_SOCKET;
|
|
|
|
rsocket->status = RNF_UNUSED;
|
|
memset(rsocket, 0, sizeof(reliable_socket));
|
|
|
|
ml_string("Unable to send packet in psnet_rel_connect_to_server()");
|
|
|
|
return;
|
|
}
|
|
|
|
*socket = i;
|
|
Serverconn = i;
|
|
|
|
rsocket->last_packet_sent = psnet_get_time();
|
|
|
|
float f = psnet_get_time();
|
|
|
|
while ( (fl_abs((psnet_get_time() - f)) < 2) && (rsocket->status != RNF_CONNECTING) ) {
|
|
psnet_rel_work();
|
|
}
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
ml_string("Out of reliable socket space in psnet_rel_connect_to_server().");
|
|
|
|
return;
|
|
} while(fl_abs((psnet_get_time() - first_sent_req)) < RELIABLE_CONNECT_TIME);
|
|
}
|
|
|
|
/**
|
|
* Initialize reliable sockets
|
|
*/
|
|
void psnet_init_rel_tcp()
|
|
{
|
|
// clear reliable sockets
|
|
for (auto &rsocket : Reliable_sockets) {
|
|
memset(&rsocket, 0, sizeof(reliable_socket));
|
|
}
|
|
}
|
|
|
|
void psnet_rel_close()
|
|
{
|
|
PSNET_SOCKET_RELIABLE sock;
|
|
|
|
// kill all sockets
|
|
for (auto idx = 0; idx < MAXRELIABLESOCKETS; idx++) {
|
|
if (Reliable_sockets[idx].status != RNF_UNUSED) {
|
|
sock = static_cast<PSNET_SOCKET_RELIABLE>(idx);
|
|
psnet_rel_close_socket(sock);
|
|
}
|
|
}
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------------
|
|
// PACKET BUFFERING FUNCTIONS
|
|
//
|
|
|
|
/**
|
|
* Initialize the buffering system
|
|
*/
|
|
void psnet_buffer_init(network_packet_buffer_list *l)
|
|
{
|
|
int idx;
|
|
|
|
// blast the buffer clean
|
|
memset(l->psnet_buffers, 0, sizeof(network_packet_buffer) * MAX_PACKET_BUFFERS);
|
|
|
|
// set all buffer sequence #'s to -1
|
|
for (idx = 0;idx < MAX_PACKET_BUFFERS; idx++) {
|
|
l->psnet_buffers[idx].sequence_number = -1;
|
|
}
|
|
|
|
// initialize the sequence #
|
|
l->psnet_seq_number = 0;
|
|
l->psnet_lowest_id = -1;
|
|
l->psnet_highest_id = -1;
|
|
}
|
|
|
|
/**
|
|
* Buffer a packet (maintain order!)
|
|
*/
|
|
void psnet_buffer_packet(network_packet_buffer_list *l, ubyte *data, SSIZE_T length, SOCKADDR_IN6 *from)
|
|
{
|
|
int idx;
|
|
bool found_buf = false;
|
|
|
|
Assert(length > 0);
|
|
|
|
// find the first empty packet
|
|
for (idx = 0; idx < MAX_PACKET_BUFFERS; idx++) {
|
|
if (l->psnet_buffers[idx].sequence_number == -1) {
|
|
found_buf = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// if we didn't find the buffer, report an overrun
|
|
if ( !found_buf ) {
|
|
ml_string("WARNING - Buffer overrun in psnet");
|
|
} else {
|
|
// copy in the data
|
|
memcpy(l->psnet_buffers[idx].data, data, static_cast<size_t>(length));
|
|
l->psnet_buffers[idx].len = length;
|
|
l->psnet_buffers[idx].sequence_number = l->psnet_seq_number;
|
|
memcpy(&l->psnet_buffers[idx].from_addr, from, sizeof(l->psnet_buffers[idx].from_addr));
|
|
|
|
// keep track of the highest id#
|
|
l->psnet_highest_id = l->psnet_seq_number++;
|
|
|
|
// set the lowest id# for the first time
|
|
if (l->psnet_lowest_id == -1) {
|
|
l->psnet_lowest_id = l->psnet_highest_id;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the index of the next packet in order!
|
|
*/
|
|
int psnet_buffer_get_next(network_packet_buffer_list *l, ubyte *data, SSIZE_T *length, SOCKADDR_IN6 *from)
|
|
{
|
|
int idx;
|
|
int found_buf = 0;
|
|
|
|
// if there are no buffers, do nothing
|
|
if ( (l->psnet_lowest_id == -1) || (l->psnet_lowest_id > l->psnet_highest_id) ) {
|
|
return 0;
|
|
}
|
|
|
|
// search until we find the lowest packet index id#
|
|
for (idx = 0; idx < MAX_PACKET_BUFFERS; idx++) {
|
|
// if we found the buffer
|
|
if (l->psnet_buffers[idx].sequence_number == l->psnet_lowest_id) {
|
|
found_buf = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// at this point, we should _always_ have found the buffer
|
|
Assert(found_buf);
|
|
|
|
Assert(l->psnet_buffers[idx].len > 0);
|
|
|
|
// copy out the buffer data
|
|
memcpy(data, l->psnet_buffers[idx].data, static_cast<size_t>(l->psnet_buffers[idx].len));
|
|
*length = l->psnet_buffers[idx].len;
|
|
memcpy(from, &l->psnet_buffers[idx].from_addr, sizeof(*from));
|
|
|
|
// now we need to cleanup the packet list
|
|
|
|
// mark the buffer as free
|
|
l->psnet_buffers[idx].sequence_number = -1;
|
|
l->psnet_lowest_id++;
|
|
|
|
return 1;
|
|
}
|
|
|
|
// -------------------------------------------------------------------------------------------------------
|
|
// PSNET 2 FORWARD DEFINITIONS
|
|
//
|
|
|
|
/**
|
|
* Set some options on a socket
|
|
*/
|
|
void psnet_set_socket_options()
|
|
{
|
|
int cursize, bufsize, i_opt;
|
|
socklen_t cursizesize;
|
|
|
|
// try and increase the size of my receive buffer
|
|
bufsize = MAX_RECEIVE_BUFSIZE;
|
|
|
|
// set the current size of the receive buffer
|
|
cursizesize = sizeof(int);
|
|
getsockopt(Psnet_socket, SOL_SOCKET, SO_RCVBUF, reinterpret_cast<char *>(&cursize), &cursizesize);
|
|
|
|
if ( cursize < (MAX_RECEIVE_BUFSIZE*2) ) {
|
|
setsockopt(Psnet_socket, SOL_SOCKET, SO_RCVBUF, reinterpret_cast<const char *>(&bufsize), sizeof(bufsize));
|
|
cursizesize = sizeof(int);
|
|
getsockopt(Psnet_socket, SOL_SOCKET, SO_RCVBUF, reinterpret_cast<char *>(&cursize), &cursizesize);
|
|
}
|
|
|
|
ml_printf("Receive buffer set to %d", cursize);
|
|
|
|
// set the current size of the send buffer
|
|
cursizesize = sizeof(int);
|
|
getsockopt(Psnet_socket, SOL_SOCKET, SO_SNDBUF, reinterpret_cast<char *>(&cursize), &cursizesize);
|
|
|
|
if ( cursize < (MAX_RECEIVE_BUFSIZE*2) ) {
|
|
setsockopt(Psnet_socket, SOL_SOCKET, SO_SNDBUF, reinterpret_cast<const char *>(&bufsize), sizeof(bufsize));
|
|
cursizesize = sizeof(int);
|
|
getsockopt(Psnet_socket, SOL_SOCKET, SO_SNDBUF, reinterpret_cast<char *>(&cursize), &cursizesize);
|
|
}
|
|
|
|
ml_printf("Send buffer set to %d", cursize);
|
|
|
|
// make sure we are in dual-stack mode (not the default on Windows)
|
|
i_opt = 0;
|
|
setsockopt(Psnet_socket, IPPROTO_IPV6, IPV6_V6ONLY, reinterpret_cast<const char *>(&i_opt), sizeof(i_opt));
|
|
cursizesize = sizeof(int);
|
|
getsockopt(Psnet_socket, IPPROTO_IPV6, IPV6_V6ONLY, reinterpret_cast<char *>(&i_opt), &cursizesize);
|
|
|
|
ml_printf("Socket set to %s mode", i_opt ? "IPv6-only" : "dual-stack");
|
|
}
|
|
|
|
/**
|
|
* Initialize socket
|
|
*/
|
|
bool psnet_init_socket()
|
|
{
|
|
SOCKADDR_STORAGE sockaddr;
|
|
|
|
Psnet_socket = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP);
|
|
|
|
if (Psnet_socket == INVALID_SOCKET) {
|
|
Psnet_failure_code = WSAGetLastError();
|
|
ml_printf("Error on TCP startup %d", Psnet_failure_code);
|
|
|
|
return false;
|
|
}
|
|
|
|
// set socket options
|
|
psnet_set_socket_options();
|
|
|
|
// bind the socket
|
|
psnet_get_addr(nullptr, Psnet_default_port, &sockaddr);
|
|
|
|
if ( bind(Psnet_socket, reinterpret_cast<LPSOCKADDR>(&sockaddr), sizeof(sockaddr)) == SOCKET_ERROR) {
|
|
Psnet_failure_code = WSAGetLastError();
|
|
|
|
if (Psnet_failure_code == WSAEADDRINUSE) {
|
|
ml_printf("Socket already in use! Another instance running? (Try using the \"-port %i\" cmdline option)", Psnet_default_port + 1);
|
|
} else {
|
|
ml_printf("Couldn't bind socket (%d)!", Psnet_failure_code );
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// success
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Get time in seconds
|
|
*/
|
|
float psnet_get_time()
|
|
{
|
|
return static_cast<float>(timer_get_milliseconds()) / 1000.0f;
|
|
}
|
|
|
|
/**
|
|
* Mark a socket as having received data
|
|
*/
|
|
void psnet_mark_received(PSNET_SOCKET_RELIABLE socketid)
|
|
{
|
|
// valid socket?
|
|
if (socketid >= MAXRELIABLESOCKETS) {
|
|
return;
|
|
}
|
|
|
|
// mark it
|
|
Reliable_sockets[socketid].last_packet_received = psnet_get_time();
|
|
}
|