Files
BT411/engine/MUNGA_L4/L4NET.H
T
arcattackandClaude Fable 5 dbb9af2dfa D1 phase 5+6: UDP unreliable channel (restores the 1995 reliable/unreliable split)
The ~60Hz entity update records now ride UDP in relay mode, so a lost/late
datagram is dropped (dead reckoning absorbs the gap) instead of head-of-line-
blocking the reliable TCP stream -- the fix for internet rubber-banding.
Reliable traffic (make/damage/death/control/egg/launch) stays on TCP.

This RESTORES the authentic 1995 NETNUB split, it doesn't invent one:
Receiver::Message defaults messageFlags=ReliableFlag; the update path clears
it (flags=0, ENTITY.cpp:590); Mode(UnreliableMode) fires at LoadingMission
(APP.cpp:704 -- the 2007 port's Mode() was a no-op, now it STORES the mode).
Routing gate = (mode==UnreliableMode && !(flags & ReliableFlag)) -- flag AND
mode, so the map-stream creation messages (also flags=0 but flowing during the
still-Reliable CreatingMission window) ride TCP for free.

Client (L4NET):
- Mode() stores currentNetworkMode (was ignored).
- ConnectRelayUdp: UDP socket connect()ed to the relay game port; HELLO
  (outbound punch-through, relay learns our endpoint); udpUp on HELLO-ACK.
  BT_RELAY_TCP_ONLY=1 disables the channel (UDP-blocked nets degrade to the
  pure-TCP checkpoint automatically -- no ACK => everything stays on TCP).
- RelayUdpSendFrame: {route, fromHost, seq} envelope + frame, best-effort.
- RelayUdpKeepalive: ~15s NAT-hold + ~1s HELLO retry until acked.
- CheckRelayUdp: HELLO-ACK sets udpUp; per-sender seq gate drops stale/
  out-of-order datagrams; frames handed up envelope-stripped like TCP.
- Send / ExclusiveBroadcast: route unreliable-window traffic to UDP, else TCP.
- CheckBuffers polls the UDP channel first, then TCP; keepalive tick.
(The relay-side UDP forwarder + endpoint learning + --udp-drop hook landed in
phase 1.)

Verified 2-node localhost relay: during a drive the update stream flows on UDP
(relay udp tx 2->190+, TCP frozen at 53) and the peer's replicant tracks the
master to sub-2u; 15% forced drop (--udp-drop 15) stays coherent (tracked to
0.1u); TCP-only fallback confirmed (udp-known drops the TCP-only pod); no
crashes; mesh mode un-regressed.  KB: context/multiplayer.md gains the D1
RELAY MODE section.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 10:00:26 -05:00

483 lines
14 KiB
C++

//===========================================================================//
// File: l4net.hh //
// Project: MUNGA Brick: Network Manager //
// Contents: Interface specification for network brick //
//---------------------------------------------------------------------------//
// Date Who Modification //
// -------- --- ---------------------------------------------------------- //
// 03/02/95 GAC Initial coding. //
//---------------------------------------------------------------------------//
// Copyright (C) 1994-1995, Virtual World Entertainment, Inc. //
// PROPRIETARY AND CONFIDENTIAL //
//===========================================================================//
#pragma once
#include "l4host.h"
#include "..\munga\network.h"
#include "..\munga\hostmgr.h"
//WinSock support :ADB 01/06/07
//#include "..\munga\netnub.h"
#define NETNUB_TCP_OPEN 3 // Opens a TCP stream to another computer
#define NETNUB_TCP_LISTEN 4 // Queue's a TCP listen that we can accept a connection on
#define MULTIPLE_SEND_PACKET_MAX 10
#define MAX_RECEIVE_DATA_SIZE 1600
#include <Winsock2.h>
#include <Ws2tcpip.h>
class NotationFile;
class L4NetworkManager__ReceiveEggFileMessage;
class L4NetworkManager__AcknowledgeEggFileMessage;
class L4NetworkManager__HostConnectedMessage;
class L4NetworkManager__HostDisconnectedMessage;
class NetNub
{
friend int
Netnub_Open_File(
const char* filename,
int access,
unsigned int model
);
friend int
Netnub_Write_File(
int handle,
void *buffer,
size_t length
);
friend int
Netnub_Close_File(int handle);
public:
//WinSock support :ADB 01/06/07
//static void SendCommand();
};
//extern Netcom_Ptr
// Net_Common_Ptr;
//~~~~~~~~~~~~~~~~~~~~~ MessageQueue__SendRequest ~~~~~~~~~~~~~~~~~~~~~~~~~~
class MessageQueue__SendRequest:
public Plug
{
friend class HostMessageBuffer__MessageQueue;
friend class L4NetworkManager__MessageBuffer;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Private methods
//
private:
MessageQueue__SendRequest(
NetworkClient::ClientID client_ID,
Receiver::Message *message
);
~MessageQueue__SendRequest();
Logical
TestInstance() const;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Private data
//
private:
NetworkClient::ClientID
clientID;
Receiver::Message
*messageToSend;
};
//~~~~~~~~~~~~~~~~~~~ HostMessageBuffer__MessageQueue ~~~~~~~~~~~~~~~~~~~~~~
class HostMessageBuffer__MessageQueue:
public Node
{
friend class L4NetworkManager__MessageBuffer;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Private methods
//
private:
HostMessageBuffer__MessageQueue(HostID host_ID);
~HostMessageBuffer__MessageQueue();
Logical
TestInstance() const;
void
AddSendRequest(
NetworkClient::ClientID client_ID,
Receiver::Message *message
);
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Private data
//
private:
HostID
hostID;
typedef MessageQueue__SendRequest
SendRequest;
ChainOf<SendRequest*>
sendRequestSocket;
};
//~~~~~~~~~~~~~~~~~~ L4NetworkManager__MessageBuffer ~~~~~~~~~~~~~~~~~~~~~~~
class L4NetworkManager__MessageBuffer:
public Node
{
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Public methods
//
public:
L4NetworkManager__MessageBuffer(L4NetworkManager *network_manager);
~L4NetworkManager__MessageBuffer();
Logical
TestInstance() const;
Logical
IsEmpty();
void
AddSendRequest(
HostID host_ID,
NetworkClient::ClientID client_ID,
Receiver::Message *message
);
void
AttemptToSend();
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Private data
//
private:
L4NetworkManager
*networkManager;
typedef HostMessageBuffer__MessageQueue
MessageQueue;
TableOf<MessageQueue*, HostID>
messageQueueSocket;
IteratorPosition
currentQueueIndex;
CollectionSize
bufferSize;
#ifdef LAB_ONLY
CollectionSize
messageCount,
maxBufferSize;
#endif
};
inline Logical
L4NetworkManager__MessageBuffer::IsEmpty()
{
Check(this);
return (bufferSize == 0);
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~ l4Network manager~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
class L4NetworkManager: public NetworkManager
{
friend class L4NetworkManager__MessageBuffer;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Constructor, Destructor, Testing
//
public:
L4NetworkManager();
~L4NetworkManager();
static Logical TestClass();
Logical TestInstance() const;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Network message methods
//
public:
void Send(
Message *what,
ClientID to,
HostID host_ID);
void ExclusiveBroadcast(
Message *what,
ClientID to);
void StartConnecting(Mission *mission);
Logical Shutdown();
Logical CheckBuffers(NetworkPacket *packet);
void RemovePacket(NetworkPacket *packet);
Logical ExecuteBackground();
void Marker(char *marker_text);
void Mode(NetworkMode myMode);
void CreateConsoleHost();
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Network maintenance support
//
public:
int netPlayerCount;
enum NetworkState
{
NormalState,
ConsoleOnly
};
enum NetworkStartupMode
{
SlaveMode,
MasterMode
};
bool CheckSocket(SOCKET socket, SOCKADDR_IN *remoteEndpoint);
bool ResolveAddress(CString host_name, SOCKADDR_IN *address);
//WinSock support :ADB 01/06/07
NetworkAddress* GetMyAddress();
//WinSock support :ADB 01/06/07
SOCKET OpenConnection(
int connection_type, // NETNUB_TCP_LISTEN or NETNUB_TCP_OPEN
int local_port,
int remote_port,
int internet_address);
void CloseConnection(SOCKET socket_ptr); // socket address from netnub (to close)
Logical GetNextMungaPacket(
NetworkPacket *network_packet,
HostManager::RemoteHostIterator* all_iterator);
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Shared Data support
//
public:
static Derivation *GetClassDerivations();
static SharedData DefaultData;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Message Support
//
public:
//
// Message IDs
//
enum
{
AcknowledgeEggFileMessageID = NetworkManager::NextMessageID,
HostConnectedMessageID,
HostDisconnectedMessageID,
NextMessageID
};
//
// Message types
//
typedef L4NetworkManager__AcknowledgeEggFileMessage
AcknowledgeEggFileMessage;
typedef L4NetworkManager__HostConnectedMessage
HostConnectedMessage;
typedef L4NetworkManager__HostDisconnectedMessage
HostDisconnectedMessage;
//
// Message table
//
static const HandlerEntry MessageHandlerEntries[];
//static MessageHandlerSet MessageHandlers;
static MessageHandlerSet& GetMessageHandlers();
void ReceiveEggFileMessageHandler(
ReceiveEggFileMessage *EggMessage);
void AcknowledgeEggFileMessageHandler(
AcknowledgeEggFileMessage *AcknowledgeEgg);
void HostConnectedMessageHandler(
HostConnectedMessage *HostConnected);
void HostDisconnectedMessageHandler(
HostDisconnectedMessage *ConsoleDisconnect);
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Private methods
//
private:
typedef ChainOf<L4Host*>
DroppedMessageHostSocket;
typedef ChainIteratorOf<L4Host*>
DroppedMessageHostIterator;
Logical
SendMessageToNetnub(
Message *message,
ClientID client_ID,
HostID host_ID
);
void
SendBatchedMessageToNetnub(
Message *message,
ClientID client,
DroppedMessageHostSocket *dropped_message_host_socket
);
//
// D1 RELAY MODE (BT_RELAY=host:port + BT_SELF=<[pilots] entry>): outbound-
// only topology -- the pod dials the relay (extended btconsole.py) for BOTH
// the console protocol (egg/launch; legacy raw frames, direction flipped)
// and the game traffic (envelope-framed, one TCP connection multiplexing
// every peer; UDP for the unreliable channel in a later phase). BT_RELAY
// unset => relayMode False => every relay branch is dead and the classic
// mesh is untouched.
//
SOCKET
RelayDialTcp(const SOCKADDR_IN &endpoint, int timeout_seconds);
Logical
RelaySendAll(SOCKET sock, const char *buffer, int length);
NetworkPacket *
BuildRelayPacket(Message *message, ClientID client, int *packet_size);
Logical
RelaySendFrame(int route, const NetworkPacket *packet, int packet_size);
void
ConnectRelayGame(HostID local_host_ID);
void
RelayGameDown(const char *why);
Logical
CheckRelay(NetworkPacket *network_packet);
//
// UDP unreliable channel (restores the 1995 NETNUB reliable/unreliable
// split): the ~60Hz update records ride UDP so a lost/late datagram is
// dropped (dead reckoning absorbs the gap) instead of head-of-line-
// blocking the reliable stream. Reliable traffic + control stay on TCP.
//
void
ConnectRelayUdp(HostID local_host_ID);
Logical
RelayUdpSendFrame(int route, const NetworkPacket *packet, int packet_size);
void
RelayUdpKeepalive(HostID local_host_ID);
Logical
RelayShouldUseUdp(const Message *message) const;
Logical
CheckRelayUdp(NetworkPacket *network_packet);
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Private Data
//
private:
int
numberOfMungaHostsConnected,
numberOfConsoleHostsConnected,
remoteHostCount;
NetworkStartupMode networkStartupMode;
Logical eggAcknowledged;
NetworkState currentNetworkState;
L4Host *myConsoleHost;
HostID nextOpenHostID;
IteratorPosition lastHostIteratorPosition;
typedef L4NetworkManager__MessageBuffer
MessageBuffer;
MessageBuffer
messageBuffer;
//WinSock Support
WSADATA* wsaData;
//hostent* thisHost;
addrinfo* thisHost;
SOCKET gameListenerSocket;
SOCKET consoleListenerSocket;
// D1 relay mode state (see the private-methods block above). All inert
// unless BT_RELAY is set.
Logical relayMode;
char relaySelf[64]; // BT_SELF -- our [pilots] entry string
SOCKADDR_IN relayConsoleAddress; // relay console endpoint (BT_RELAY port)
SOCKADDR_IN relayGameAddress; // relay game endpoint (port + 1, tcp+udp)
SOCKET relayGameSocket; // the ONE multiplexed game connection
SOCKET relayUdpSocket; // unreliable channel (later phase)
char relayPad[16384]; // relay-connection reassembly buffer
int relayPadTail;
Logical relayConsoleDialedOnce; // first dial gets the long timeout
Logical udpUp; // UDP HELLO acked by the relay
HostID relayLocalHostID; // our hostID (for UDP HELLO/keepalive/tx)
unsigned long lastUdpKeepaliveTick;
unsigned long lastUdpHelloTick; // HELLO retry until acked
unsigned long udpTxSeq;
unsigned long udpRxSeq[64]; // per-fromHost last-seen sequence
NetworkMode currentNetworkMode; // Reliable/Unreliable (Mode() stores)
};
//~~~~~~~~~~~~~~~~~~~~~~ L4NetworkManager inlines ~~~~~~~~~~~~~~~~~~~~~~~~~~
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// L4NetworkManager__AcknowledgeEggFileMessage
// This message is sent back to the console to acknowledge receipt of the
// egg, it indicates this computer has established all it's connections and
// the console can procede with the next host
//
class L4NetworkManager__AcknowledgeEggFileMessage:
public NetworkManager::Message
{
public:
L4NetworkManager__AcknowledgeEggFileMessage():
NetworkManager::Message(
L4NetworkManager::AcknowledgeEggFileMessageID,
sizeof(L4NetworkManager__AcknowledgeEggFileMessage)
){}
};
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// L4NetworkManager__HostConnectedMessage
// This message is generated internally by CheckBuffers and indicates that
// a host has connected up to us. This is an INTERNAL message only and is
// not ment to be sent on the network.
class L4NetworkManager__HostConnectedMessage :
public NetworkManager::Message
{
public:
L4NetworkManager__HostConnectedMessage(HostID host_id, const SOCKADDR_IN &network_address, unsigned long stream_pointer) :
NetworkManager::Message(L4NetworkManager::HostConnectedMessageID, sizeof(L4NetworkManager__HostConnectedMessage)),
hostID(host_id),
networkAddress(network_address),
streamPointer(stream_pointer)
{
}
HostID hostID;
SOCKADDR_IN networkAddress;
unsigned long streamPointer;
};
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// L4NetworkManager__HostDisconnectedMessage
// The console is expected to send us this message if it disconnects from us
// for any reason. This allows the local host to setup the internal state
// so the game will auto start even if the console can't stay connected to us
// for some reason (for example, the console simulator in the game code can't
// connect to more than one host at once).
// !!! CheckBuffers should poll the connection state of all the streams and
// generate this message automatically whenever a host drops off the net.
// !!! HACK At the moment this message is always treated as if it came from
// the console host, so it should only be sent down the console stream.
//
class L4NetworkManager__HostDisconnectedMessage :
public NetworkManager::Message
{
public:
L4NetworkManager__HostDisconnectedMessage(HostID host_id, unsigned long stream_pointer) :
NetworkManager::Message(L4NetworkManager::HostDisconnectedMessageID, sizeof(L4NetworkManager__HostDisconnectedMessage)),
hostID(host_id),
streamPointer(stream_pointer)
{
}
HostID hostID;
unsigned long streamPointer;
};