Merge branch 'hole-punch-backport' into 'public_next'

Hole punching backport

See merge request KartKrew/Kart!445
This commit is contained in:
toaster 2022-05-31 15:48:00 +00:00
commit 8fa22d562f
7 changed files with 234 additions and 24 deletions

View file

@ -0,0 +1,34 @@
Bird's Hole Punching Protocol
Hole punch - a mechanism to bypass a firewall
Server - a third party which is not behind a firewall
Client - anything contacting the server
Magic - the four bytes 00 52 EB 11
Address - an IPv4 address
0 1 2 3 4 5 6 7 8 9
+-------+-------+----+
| Magic |Address|Port|
+-------+-------+----+
Relay Packet
A client that expects to be the target of a hole punch
must contact the server frequently, to keep a UDP
"connection" open, so that the server may relay hole
punching requests to them.
A client makes a hole punching request to another client
by sending a Relay Packet to the server. The server then
sends another Relay Packet to the client described by the
first packet. The second packet is filled with the source
address and port of the first packet.
Once a client receives a Relay Packet, this protocol's
purpose is fulfilled and the client is aware that another
client requests a hole punch.
vim: noai

View file

@ -1916,6 +1916,12 @@ static void SendAskInfo(INT32 node)
// now allowed traffic from the host to us in, so once the MS relays // now allowed traffic from the host to us in, so once the MS relays
// our address to the host, it'll be able to speak to us. // our address to the host, it'll be able to speak to us.
HSendPacket(node, false, 0, sizeof (askinfo_pak)); HSendPacket(node, false, 0, sizeof (askinfo_pak));
if (node != 0 && node != BROADCASTADDR &&
cv_rendezvousserver.string[0])
{
I_NetRequestHolePunch();
}
} }
serverelem_t serverlist[MAXSERVERLIST]; serverelem_t serverlist[MAXSERVERLIST];
@ -5754,6 +5760,22 @@ static void UpdatePingTable(void)
} }
} }
static void RenewHolePunch(void)
{
if (cv_rendezvousserver.string[0])
{
static time_t past;
const time_t now = time(NULL);
if ((now - past) > 20)
{
I_NetRegisterHolePunch();
past = now;
}
}
}
// Handle timeouts to prevent definitive freezes from happenning // Handle timeouts to prevent definitive freezes from happenning
static void HandleNodeTimeouts(void) static void HandleNodeTimeouts(void)
{ {
@ -5788,6 +5810,11 @@ FILESTAMP
MasterClient_Ticker(); MasterClient_Ticker();
#endif #endif
if (netgame && serverrunning)
{
RenewHolePunch();
}
if (client) if (client)
{ {
// send keep alive // send keep alive
@ -5847,6 +5874,11 @@ FILESTAMP
MasterClient_Ticker(); // Acking the Master Server MasterClient_Ticker(); // Acking the Master Server
#endif #endif
if (netgame && serverrunning)
{
RenewHolePunch();
}
if (client) if (client)
{ {
if (!resynch_local_inprogress) if (!resynch_local_inprogress)

View file

@ -49,6 +49,8 @@ tic_t connectiontimeout = (10*TICRATE);
doomcom_t *doomcom = NULL; doomcom_t *doomcom = NULL;
/// \brief network packet data, points inside doomcom /// \brief network packet data, points inside doomcom
doomdata_t *netbuffer = NULL; doomdata_t *netbuffer = NULL;
/// \brief hole punching packet, also points inside doomcom
holepunch_t *holepunchpacket = NULL;
#ifdef DEBUGFILE #ifdef DEBUGFILE
FILE *debugfile = NULL; // put some net info in a file during the game FILE *debugfile = NULL; // put some net info in a file during the game
@ -72,6 +74,8 @@ boolean (*I_NetCanGet)(void) = NULL;
void (*I_NetCloseSocket)(void) = NULL; void (*I_NetCloseSocket)(void) = NULL;
void (*I_NetFreeNodenum)(INT32 nodenum) = NULL; void (*I_NetFreeNodenum)(INT32 nodenum) = NULL;
SINT8 (*I_NetMakeNodewPort)(const char *address, const char* port) = NULL; SINT8 (*I_NetMakeNodewPort)(const char *address, const char* port) = NULL;
void (*I_NetRequestHolePunch)(void) = NULL;
void (*I_NetRegisterHolePunch)(void) = NULL;
boolean (*I_NetOpenSocket)(void) = NULL; boolean (*I_NetOpenSocket)(void) = NULL;
boolean (*I_Ban) (INT32 node) = NULL; boolean (*I_Ban) (INT32 node) = NULL;
void (*I_ClearBans)(void) = NULL; void (*I_ClearBans)(void) = NULL;
@ -1335,6 +1339,7 @@ boolean D_CheckNetGame(void)
I_Error("Too many nodes (%d), max:%d", doomcom->numnodes, MAXNETNODES); I_Error("Too many nodes (%d), max:%d", doomcom->numnodes, MAXNETNODES);
netbuffer = (doomdata_t *)(void *)&doomcom->data; netbuffer = (doomdata_t *)(void *)&doomcom->data;
holepunchpacket = (holepunch_t *)(void *)&doomcom->data;
#ifdef DEBUGFILE #ifdef DEBUGFILE
#ifdef _arch_dreamcast #ifdef _arch_dreamcast

View file

@ -77,11 +77,19 @@ typedef struct
char data[MAXPACKETLENGTH]; char data[MAXPACKETLENGTH];
} ATTRPACK doomcom_t; } ATTRPACK doomcom_t;
typedef struct
{
INT32 magic;
INT32 addr;
INT16 port;
} ATTRPACK holepunch_t;
#if defined(_MSC_VER) #if defined(_MSC_VER)
#pragma pack() #pragma pack()
#endif #endif
extern doomcom_t *doomcom; extern doomcom_t *doomcom;
extern holepunch_t *holepunchpacket;
/** \brief return packet in doomcom struct /** \brief return packet in doomcom struct
*/ */
@ -140,6 +148,15 @@ extern boolean (*I_NetOpenSocket)(void);
extern void (*I_NetCloseSocket)(void); extern void (*I_NetCloseSocket)(void);
/** \brief send a hole punching request
*/
extern void (*I_NetRequestHolePunch)(void);
/** \brief register this machine on the hole punching server
*/
extern void (*I_NetRegisterHolePunch)(void);
extern boolean (*I_Ban) (INT32 node); extern boolean (*I_Ban) (INT32 node);
extern void (*I_ClearBans)(void); extern void (*I_ClearBans)(void);
extern const char *(*I_GetNodeAddress) (INT32 node); extern const char *(*I_GetNodeAddress) (INT32 node);

View file

@ -241,6 +241,8 @@ static size_t broadcastaddresses = 0;
static boolean nodeconnected[MAXNETNODES+1]; static boolean nodeconnected[MAXNETNODES+1];
static mysockaddr_t banned[MAXBANS]; static mysockaddr_t banned[MAXBANS];
static UINT8 bannedmask[MAXBANS]; static UINT8 bannedmask[MAXBANS];
/* See ../doc/Holepunch-Protocol.txt */
static const INT32 hole_punch_magic = MSBF_LONG (0x52eb11);
#endif #endif
static size_t numbans = 0; static size_t numbans = 0;
@ -597,6 +599,55 @@ void Command_Numnodes(void)
#endif #endif
#ifndef NONET #ifndef NONET
/* not one of the reserved "local" addresses */
static boolean
is_external_address (UINT32 p)
{
UINT8 a = (p & 255);
UINT8 b = ((p >> 8) & 255);
if (p == (UINT32)~0)/* 255.255.255.255 */
return 0;
switch (a)
{
case 0:
case 10:
case 127:
return false;
case 172:
return (b & ~15) != 16;/* 16 - 31 */
case 192:
return b != 168;
default:
return true;
}
}
static boolean hole_punch(ssize_t c)
{
/* See ../doc/Holepunch-Protocol.txt */
if (cv_rendezvousserver.string[0] &&
c == 10 && holepunchpacket->magic == hole_punch_magic &&
is_external_address(ntohl(holepunchpacket->addr)))
{
mysockaddr_t addr;
addr.ip4.sin_family = AF_INET;
addr.ip4.sin_addr.s_addr = holepunchpacket->addr;
addr.ip4.sin_port = holepunchpacket->port;
sendto(mysockets[0], NULL, 0, 0, &addr.any, sizeof addr.ip4);
CONS_Debug(DBG_NETPLAY,
"hole punching request from %s\n", SOCK_AddrToStr(&addr));
return true;
}
else
{
return false;
}
}
// Returns true if a packet was received from a new node, false in all other cases // Returns true if a packet was received from a new node, false in all other cases
static boolean SOCK_Get(void) static boolean SOCK_Get(void)
{ {
@ -611,7 +662,7 @@ static boolean SOCK_Get(void)
fromlen = (socklen_t)sizeof(fromaddress); fromlen = (socklen_t)sizeof(fromaddress);
c = recvfrom(mysockets[n], (char *)&doomcom->data, MAXPACKETLENGTH, 0, c = recvfrom(mysockets[n], (char *)&doomcom->data, MAXPACKETLENGTH, 0,
(void *)&fromaddress, &fromlen); (void *)&fromaddress, &fromlen);
if (c != ERRSOCKET) if (c > 0)
{ {
#ifdef USE_STUN #ifdef USE_STUN
if (STUN_got_response(doomcom->data, c)) if (STUN_got_response(doomcom->data, c))
@ -620,6 +671,11 @@ static boolean SOCK_Get(void)
} }
#endif #endif
if (hole_punch(c))
{
return false;
}
// find remote node number // find remote node number
for (j = 1; j <= MAXNETNODES; j++) //include LAN for (j = 1; j <= MAXNETNODES; j++) //include LAN
{ {
@ -1319,17 +1375,14 @@ void I_ShutdownTcpDriver(void)
} }
#ifndef NONET #ifndef NONET
static SINT8 SOCK_NetMakeNodewPort(const char *address, const char *port) static boolean SOCK_GetAddr(struct sockaddr_in *sin, const char *address, const char *port, boolean test)
{ {
SINT8 newnode = -1;
struct my_addrinfo *ai = NULL, *runp, hints; struct my_addrinfo *ai = NULL, *runp, hints;
int gaie; int gaie;
if (!port || !port[0]) if (!port || !port[0])
port = DEFAULTPORT; port = DEFAULTPORT;
DEBFILE(va("Creating new node: %s@%s\n", address, port));
memset (&hints, 0x00, sizeof (hints)); memset (&hints, 0x00, sizeof (hints));
hints.ai_flags = 0; hints.ai_flags = 0;
hints.ai_family = AF_UNSPEC; hints.ai_family = AF_UNSPEC;
@ -1337,31 +1390,94 @@ static SINT8 SOCK_NetMakeNodewPort(const char *address, const char *port)
hints.ai_protocol = IPPROTO_UDP; hints.ai_protocol = IPPROTO_UDP;
gaie = I_getaddrinfo(address, port, &hints, &ai); gaie = I_getaddrinfo(address, port, &hints, &ai);
if (gaie == 0)
{ if (gaie != 0)
newnode = getfreenode();
}
if (newnode == -1)
{ {
I_freeaddrinfo(ai); I_freeaddrinfo(ai);
return -1; return false;
} }
else
runp = ai; runp = ai;
if (test)
{
while (runp != NULL) while (runp != NULL)
{ {
// find ip of the server
if (sendto(mysockets[0], NULL, 0, 0, runp->ai_addr, runp->ai_addrlen) == 0) if (sendto(mysockets[0], NULL, 0, 0, runp->ai_addr, runp->ai_addrlen) == 0)
{
memcpy(&clientaddress[newnode], runp->ai_addr, runp->ai_addrlen);
break; break;
}
runp = runp->ai_next; runp = runp->ai_next;
} }
}
if (runp != NULL)
memcpy(sin, runp->ai_addr, runp->ai_addrlen);
I_freeaddrinfo(ai); I_freeaddrinfo(ai);
return (runp != NULL);
}
static SINT8 SOCK_NetMakeNodewPort(const char *address, const char *port)
{
SINT8 newnode = getfreenode();
DEBFILE(va("Creating new node: %s@%s\n", address, port));
if (newnode != -1)
{
if (!SOCK_GetAddr(&clientaddress[newnode].ip4, address, port, true))
{
nodeconnected[newnode] = false;
return -1;
}
}
return newnode; return newnode;
} }
/* See ../doc/Holepunch-Protocol.txt */
static void rendezvous(int size)
{
char *addrs = strdup(cv_rendezvousserver.string);
char *host = strtok(addrs, ":");
char *port = strtok(NULL, ":");
mysockaddr_t rzv;
if (SOCK_GetAddr(&rzv.ip4, host, (port ? port : "7777"), false))
{
holepunchpacket->magic = hole_punch_magic;
sendto(mysockets[0], doomcom->data, size, 0, &rzv.any, sizeof rzv.ip4);
}
else
{
CONS_Alert(CONS_ERROR, "Failed to contact rendezvous server (%s).\n",
cv_rendezvousserver.string);
}
free(addrs);
}
static void SOCK_RequestHolePunch(void)
{
mysockaddr_t * addr = &clientaddress[doomcom->remotenode];
holepunchpacket->addr = addr->ip4.sin_addr.s_addr;
holepunchpacket->port = addr->ip4.sin_port;
CONS_Debug(DBG_NETPLAY,
"requesting hole punch to node %s\n", SOCK_AddrToStr(addr));
rendezvous(10);
}
static void SOCK_RegisterHolePunch(void)
{
rendezvous(4);
}
#endif #endif
static boolean SOCK_OpenSocket(void) static boolean SOCK_OpenSocket(void)
@ -1387,6 +1503,9 @@ static boolean SOCK_OpenSocket(void)
I_NetCanGet = SOCK_CanGet; I_NetCanGet = SOCK_CanGet;
#endif #endif
I_NetRequestHolePunch = SOCK_RequestHolePunch;
I_NetRegisterHolePunch = SOCK_RegisterHolePunch;
// build the socket but close it first // build the socket but close it first
SOCK_CloseSocket(); SOCK_CloseSocket();
return UDP_Socket(); return UDP_Socket();

View file

@ -68,6 +68,7 @@ static CV_PossibleValue_t masterserver_update_rate_cons_t[] = {
}; };
consvar_t cv_masterserver = {"masterserver", "https://ms.kartkrew.org/ms/api", CV_SAVE|CV_CALL, NULL, MasterServer_OnChange, 0, NULL, NULL, 0, 0, NULL}; consvar_t cv_masterserver = {"masterserver", "https://ms.kartkrew.org/ms/api", CV_SAVE|CV_CALL, NULL, MasterServer_OnChange, 0, NULL, NULL, 0, 0, NULL};
consvar_t cv_rendezvousserver = {"rendezvousserver", "relay.kartkrew.org", CV_SAVE, NULL, NULL, 0, NULL, NULL, 0, 0, NULL};
consvar_t cv_servername = {"servername", "SRB2Kart server", CV_SAVE|CV_CALL|CV_NOINIT, NULL, Update_parameters, 0, NULL, NULL, 0, 0, NULL}; consvar_t cv_servername = {"servername", "SRB2Kart server", CV_SAVE|CV_CALL|CV_NOINIT, NULL, Update_parameters, 0, NULL, NULL, 0, 0, NULL};
consvar_t cv_server_contact = {"server_contact", "", CV_SAVE|CV_CALL|CV_NOINIT, NULL, Update_parameters, 0, NULL, NULL, 0, 0, NULL}; consvar_t cv_server_contact = {"server_contact", "", CV_SAVE|CV_CALL|CV_NOINIT, NULL, Update_parameters, 0, NULL, NULL, 0, 0, NULL};
@ -99,6 +100,7 @@ void AddMServCommands(void)
CV_RegisterVar(&cv_masterserver_debug); CV_RegisterVar(&cv_masterserver_debug);
CV_RegisterVar(&cv_masterserver_token); CV_RegisterVar(&cv_masterserver_token);
CV_RegisterVar(&cv_advertise); CV_RegisterVar(&cv_advertise);
CV_RegisterVar(&cv_rendezvousserver);
CV_RegisterVar(&cv_servername); CV_RegisterVar(&cv_servername);
CV_RegisterVar(&cv_server_contact); CV_RegisterVar(&cv_server_contact);
#ifdef MASTERSERVER #ifdef MASTERSERVER

View file

@ -58,6 +58,7 @@ extern consvar_t cv_masterserver_update_rate;
extern consvar_t cv_masterserver_timeout; extern consvar_t cv_masterserver_timeout;
extern consvar_t cv_masterserver_debug; extern consvar_t cv_masterserver_debug;
extern consvar_t cv_masterserver_token; extern consvar_t cv_masterserver_token;
extern consvar_t cv_rendezvousserver;
extern consvar_t cv_advertise; extern consvar_t cv_advertise;