D1 phase 2+3: client TCP relay mode (L4NET) + 2-node verification
The pod can now run entirely over the relay: one outbound TCP connection
carries both the console protocol (egg/launch) and all game traffic
(envelope-multiplexed), so internet play works behind NAT with no port
forwarding. All new code is behind 'if (relayMode)' (BT_RELAY unset => mesh
mode byte-for-byte untouched; verified un-regressed).
Env gates (parsed once in the ctor, after WSAStartup so gethostbyname works):
- BT_RELAY=<host>:<consolePort> (host may be a DNS name; game port = +1)
- BT_SELF=<exact [pilots] entry> (NIC matching can't identify us across NAT)
L4NET changes:
- CreateConsoleHost: relay branch dials OUT to the relay console port
(bounded retry; console-less continue on a re-dial) instead of listening.
- StartConnecting: relay self-match by BT_SELF string; peers become VIRTUAL
hosts (INVALID_SOCKET, NoNetworkConnectionStatus) flipped online by the
relay's PEER_UP; then ConnectRelayGame dials the game port + HELLOs. HostID
assignment / remoteHostCount / the connection gate / app ladder unchanged.
- CheckRelay (new): the receive seam -- drains {route,length} envelopes,
synthesizes HostConnected/Disconnected from PEER_UP/DOWN (same messages the
mesh accept path routes), returns game frames envelope-stripped (they
self-identify via NetworkPacketHeader.fromHost; consumers route by payload).
- Send: game-host traffic -> relay unicast envelope (console host excluded --
its legacy protocol is relay-TERMINATED, not routed).
- ExclusiveBroadcast: build ONCE, send ONCE with the broadcast route -- the
relay fans out, killing the mesh's (N-1)x upload duplication.
- RelaySendAll (new): partial-send-safe transmit (required on the multiplexed
socket -- a partial write would desync framing for all peers).
- CheckBuffers: polls CheckRelay first; skips recv on virtual game hosts.
- RelayGameDown: relay-loss synthesizes all-peers-disconnected (match
continues peer-less; pod never exits mid-match).
- Mode(): now STORES the reliable/unreliable mode (was ignored) for the UDP
phase's authentic mode+flag routing.
Verified 2-node localhost relay (MP_RELAY.EGG, tagged [pilots]):
- both pods reach 'All connections completed!' via PEER_UP, then
RunningMission; both mechs' MakeMessages cross the relay (paint x2 each) and
bidirectional 148-byte update records flow (net-tx/net-rx traces; relay
stats tcp rx==tx, registered [2,3]); cockpit/HUD render; no crashes.
- driving one node transmits pose/damage/death frames through the relay.
- mesh smoke (no BT_RELAY): 2-node session still forms + simulates unchanged.
Plan: ~/.claude/plans/partitioned-snuggling-piglet.md. Next: UDP channel.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
bf8c2ffaae
commit
c029df6e6b
@@ -0,0 +1,244 @@
|
||||
[mission]
|
||||
adventure=BattleTech
|
||||
map=cavern
|
||||
scenario=freeforall
|
||||
time=night
|
||||
weather=clear
|
||||
temperature=27
|
||||
length=600
|
||||
[ordinals]
|
||||
bitmap=Ordinal::BitMap::1
|
||||
bitmap=Ordinal::BitMap::2
|
||||
bitmap=Ordinal::BitMap::3
|
||||
bitmap=Ordinal::BitMap::4
|
||||
[Ordinal::BitMap::1]
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=000038000003C000001FF00000000F00
|
||||
bitmap=0000F8000003C000003FF80000000F00
|
||||
bitmap=0001F8000003C00000707C0000000F00
|
||||
bitmap=0001F8000003C00000603C0000000F00
|
||||
bitmap=00007801FC0FF00000003C3DF807FF00
|
||||
bitmap=00007803FE0FF00000003C3FFC0FFF00
|
||||
bitmap=00007803C703C00000003C3E3C1F0F00
|
||||
bitmap=000078078303C0000000783C1E1E0F00
|
||||
bitmap=000078078003C0000000783C1E1E0F00
|
||||
bitmap=00007807C003C0000000F03C1E1E0F00
|
||||
bitmap=00007807F003C0000001E03C1E1E0F00
|
||||
bitmap=00007803FC03C0000003C03C1E1E0F00
|
||||
bitmap=00007801FE03C0000007803C1E1E0F00
|
||||
bitmap=000078007F03C000000F003C1E1E0F00
|
||||
bitmap=000078001F03C000001E003C1E1E0F00
|
||||
bitmap=000078000F03C000003C003C1E1E0F00
|
||||
bitmap=000078060F03C0000078003C1E1E0F00
|
||||
bitmap=000078071E03E0000078003C1E1F1F00
|
||||
bitmap=00007803FE01F000007FFC3C1E0FFF00
|
||||
bitmap=00007801FC00F000007FFC3C1E07EF00
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
x=128
|
||||
y=32
|
||||
width=8
|
||||
[Ordinal::BitMap::2]
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=001FFF0000003C0000003C03C0780000
|
||||
bitmap=001FFF0000003C0000007C03C0780000
|
||||
bitmap=00000E0000003C000000FC03C0780000
|
||||
bitmap=00003C0000003C000001BC03C0780000
|
||||
bitmap=0000700F3E1FFC000003BC0FF07BF000
|
||||
bitmap=0001E00F7E3FFC0000073C0FF07FF800
|
||||
bitmap=0003800FFE7C3C0000063C03C07C7800
|
||||
bitmap=0007F80FFE783C00000C3C03C0783C00
|
||||
bitmap=0007FE0F80783C0000183C03C0783C00
|
||||
bitmap=00001E0F00783C0000383C03C0783C00
|
||||
bitmap=00000F0F00783C0000703C03C0783C00
|
||||
bitmap=00000F0F00783C00007FFF03C0783C00
|
||||
bitmap=00000F0F00783C00007FFF03C0783C00
|
||||
bitmap=00000F0F00783C0000003C03C0783C00
|
||||
bitmap=00000F0F00783C0000003C03C0783C00
|
||||
bitmap=00000F0F00783C0000003C03C0783C00
|
||||
bitmap=00180F0F00783C0000003C03C0783C00
|
||||
bitmap=001C1F0F007C7C0000003C03E0783C00
|
||||
bitmap=000FFE0F003FFC0000003C01F0783C00
|
||||
bitmap=0007FC0F001FBC0000003C00F0783C00
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
x=128
|
||||
y=32
|
||||
width=8
|
||||
[Ordinal::BitMap::3]
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=001FFF03C07800000000FC03C0780000
|
||||
bitmap=001FFF03C07800000003FC03C0780000
|
||||
bitmap=001E0003C07800000007C003C0780000
|
||||
bitmap=001E0003C0780000000F0003C0780000
|
||||
bitmap=001E000FF07BF000000F000FF07BF000
|
||||
bitmap=001E000FF07FF800001E000FF07FF800
|
||||
bitmap=001E0003C07C7800001E0003C07C7800
|
||||
bitmap=001FFC03C0783C00001EFC03C0783C00
|
||||
bitmap=001FFE03C0783C00001FFE03C0783C00
|
||||
bitmap=00001F03C0783C00001F1F03C0783C00
|
||||
bitmap=00000F03C0783C00001E0F03C0783C00
|
||||
bitmap=00000F03C0783C00001E0F03C0783C00
|
||||
bitmap=00000F03C0783C00001E0F03C0783C00
|
||||
bitmap=00000F03C0783C00001E0F03C0783C00
|
||||
bitmap=00000F03C0783C00001E0F03C0783C00
|
||||
bitmap=00000F03C0783C00001E0F03C0783C00
|
||||
bitmap=00180F03C0783C00001E0F03C0783C00
|
||||
bitmap=001C1F03E0783C00001F1F03E0783C00
|
||||
bitmap=000FFE01F0783C00000FFE01F0783C00
|
||||
bitmap=0007FC00F0783C000007FC00F0783C00
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
x=128
|
||||
y=32
|
||||
width=8
|
||||
[Ordinal::BitMap::4]
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=001FFF03C07800000007FC03C0780000
|
||||
bitmap=001FFF03C0780000000FFE03C0780000
|
||||
bitmap=00000F03C0780000001F1F03C0780000
|
||||
bitmap=00000F03C0780000001E0F03C0780000
|
||||
bitmap=00000F0FF07BF000001E0F0FF07BF000
|
||||
bitmap=00001F0FF07FF800001E0F0FF07FF800
|
||||
bitmap=00001E03C07C7800001E0F03C07C7800
|
||||
bitmap=00003E03C0783C00001E0F03C0783C00
|
||||
bitmap=00003C03C0783C00000F1E03C0783C00
|
||||
bitmap=00003C03C0783C000007FC03C0783C00
|
||||
bitmap=00007803C0783C000007FC03C0783C00
|
||||
bitmap=00007803C0783C00000F1E03C0783C00
|
||||
bitmap=00007803C0783C00001E0F03C0783C00
|
||||
bitmap=0000F003C0783C00001E0F03C0783C00
|
||||
bitmap=0000F003C0783C00001E0F03C0783C00
|
||||
bitmap=0000F003C0783C00001E0F03C0783C00
|
||||
bitmap=0000F003C0783C00001E0F03C0783C00
|
||||
bitmap=0000F003E0783C00000F1E03E0783C00
|
||||
bitmap=0000F001F0783C00000FFE01F0783C00
|
||||
bitmap=0000F000F0783C000007FC00F0783C00
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
x=128
|
||||
y=32
|
||||
width=8
|
||||
[pilots]
|
||||
pilot=10.99.0.1:1502
|
||||
pilot=10.99.0.2:1502
|
||||
[10.99.0.1:1502]
|
||||
hostType=0
|
||||
advancedDamage=1
|
||||
loadzones=1
|
||||
name=Aeolus
|
||||
bitmapindex=1
|
||||
experience=expert
|
||||
badge=VGL
|
||||
patch=Yellow
|
||||
role=Role::Default
|
||||
dropzone=one
|
||||
vehicle=bhk1
|
||||
vehicleValue=1000
|
||||
color=White
|
||||
[10.99.0.2:1502]
|
||||
hostType=0
|
||||
advancedDamage=1
|
||||
loadzones=1
|
||||
name=Boreas
|
||||
bitmapindex=2
|
||||
experience=expert
|
||||
badge=VGL
|
||||
patch=Yellow
|
||||
role=Role::Default
|
||||
dropzone=one
|
||||
vehicle=madcat
|
||||
vehicleValue=1000
|
||||
color=Crimson
|
||||
[largebitmap]
|
||||
bitmap=BitMap::Large::Aeolus
|
||||
[BitMap::Large::Aeolus]
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=000003FE0000000001E0000000000000
|
||||
bitmap=000007FF0000000001E0000000000000
|
||||
bitmap=00000F8F8000000001E0000000000000
|
||||
bitmap=00000F078000000001E0000000000000
|
||||
bitmap=00000F0781FF00FF81E0F0781FC00000
|
||||
bitmap=00000F0783FF81FFC1E0F0783FE00000
|
||||
bitmap=00000F0787C7C3E3E1E0F0783C700000
|
||||
bitmap=00000F078783C3C1E1E0F07878300000
|
||||
bitmap=00000F078783C3C1E1E0F07878000000
|
||||
bitmap=00000FFF8783C3C1E1E0F0787C000000
|
||||
bitmap=00000FFF8783C3C1E1E0F0787F000000
|
||||
bitmap=00000F0787FFC3C1E1E0F0783FC00000
|
||||
bitmap=00000F0787FFC3C1E1E0F0781FE00000
|
||||
bitmap=00000F07878003C1E1E0F07807F00000
|
||||
bitmap=00000F07878003C1E1E0F07801F00000
|
||||
bitmap=00000F07878003C1E1E0F07800F00000
|
||||
bitmap=00000F078780C3C1E1E0F07860F00000
|
||||
bitmap=00000F0787C1C3E3E1E078F871E00000
|
||||
bitmap=00000F0783FF81FFC1E07FF83FE00000
|
||||
bitmap=00000F0781FF00FF81E03F781FC00000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
x=128
|
||||
y=32
|
||||
width=8
|
||||
[smallbitmap]
|
||||
bitmap=BitMap::Small::Aeolus
|
||||
[BitMap::Small::Aeolus]
|
||||
bitmap=00000000000000000000000000000000
|
||||
bitmap=0000000000000000003E00000C000000
|
||||
bitmap=006300000C00000000631F0F8CC63C00
|
||||
bitmap=00633198CCC66200007F3198CCC66000
|
||||
bitmap=00633F98CCC6780000633018CCC61E00
|
||||
bitmap=00633018CCC6060000633098CCC64600
|
||||
bitmap=00631F0F8C7E3C000000000000000000
|
||||
bitmap=00000000000000000000000000000000
|
||||
x=64
|
||||
y=16
|
||||
width=4
|
||||
[Role::Default]
|
||||
model=dfltrole
|
||||
[Role::NoReturn]
|
||||
model=noretun
|
||||
@@ -71,6 +71,34 @@
|
||||
#define CONSOLE_NET_PORT 1501 // Port number the console will connect on
|
||||
#define GAME_NET_PORT 1502 // Port number the game will connect on
|
||||
|
||||
//
|
||||
// D1 RELAY-MODE wire constants (must match tools/btconsole.py --relay).
|
||||
// TCP envelope on the game relay connection: { int32 route; uint32 length; }
|
||||
// followed by `length` payload bytes (the byte-exact classic frame:
|
||||
// NetworkPacketHeader + Receiver::Message). UDP envelope (later phase):
|
||||
// { int32 route; int32 fromHost; uint32 seq; } + frame.
|
||||
//
|
||||
enum
|
||||
{
|
||||
RELAY_ROUTE_BROADCAST = -1, // to every registered pod except the sender
|
||||
RELAY_ROUTE_HELLO = -2, // first frame: magic + our hostID
|
||||
RELAY_ROUTE_PEER_UP = -3, // relay->pod: hostID registered
|
||||
RELAY_ROUTE_PEER_DOWN = -4, // relay->pod: hostID gone
|
||||
RELAY_ROUTE_UDP_ACK = -5 // relay->pod: UDP HELLO acknowledged
|
||||
};
|
||||
#define RELAY_HELLO_MAGIC 0x31525442 // 'BTR1' little-endian
|
||||
struct RelayTcpEnvelope
|
||||
{
|
||||
int route;
|
||||
unsigned int length;
|
||||
};
|
||||
struct RelayUdpEnvelope
|
||||
{
|
||||
int route;
|
||||
int fromHost;
|
||||
unsigned int sequence;
|
||||
};
|
||||
|
||||
char GlobalEggFileName[80]; // NASTY HACK !!! should suffice till we get console up though
|
||||
|
||||
//struct RMREG //real mode registers structure
|
||||
@@ -188,6 +216,21 @@ L4NetworkManager::L4NetworkManager():
|
||||
remoteHostCount = 0x7FFFFFFF;
|
||||
wsaData = new WSADATA;
|
||||
|
||||
// D1 relay-mode defaults: inert until BT_RELAY is parsed at ctor end.
|
||||
relayMode = False;
|
||||
relaySelf[0] = '\0';
|
||||
memset(&relayConsoleAddress, 0, sizeof(relayConsoleAddress));
|
||||
memset(&relayGameAddress, 0, sizeof(relayGameAddress));
|
||||
relayGameSocket = INVALID_SOCKET;
|
||||
relayUdpSocket = INVALID_SOCKET;
|
||||
relayPadTail = 0;
|
||||
relayConsoleDialedOnce = False;
|
||||
udpUp = False;
|
||||
lastUdpKeepaliveTick = 0;
|
||||
udpTxSeq = 0;
|
||||
memset(udpRxSeq, 0, sizeof(udpRxSeq));
|
||||
currentNetworkMode = NetworkManager::ReliableMode;
|
||||
|
||||
//
|
||||
// If there is no net common, set things up for single user mode
|
||||
//
|
||||
@@ -279,6 +322,60 @@ L4NetworkManager::L4NetworkManager():
|
||||
// Force us into reliable mode
|
||||
//
|
||||
Mode(NetworkManager::ReliableMode);
|
||||
|
||||
//
|
||||
// D1 RELAY MODE: BT_RELAY=<host>:<consolePort> switches the whole net layer
|
||||
// to outbound-only (the pod dials the relay for console AND game traffic).
|
||||
// The host part may be a DNS name (internet play: one shared hostname).
|
||||
// BT_SELF must name this pod's exact [pilots] entry (NIC matching cannot
|
||||
// identify us across NAT). Parsed here, AFTER WSAStartup (gethostbyname).
|
||||
//
|
||||
{
|
||||
const char *relay_env = getenv("BT_RELAY");
|
||||
if (relay_env != NULL && relay_env[0] != '\0')
|
||||
{
|
||||
char host_part[128];
|
||||
int relay_port = 0;
|
||||
const char *colon = strrchr(relay_env, ':');
|
||||
if (colon == NULL || colon == relay_env
|
||||
|| (relay_port = atoi(colon + 1)) <= 0
|
||||
|| (colon - relay_env) >= (int)sizeof(host_part))
|
||||
{
|
||||
Fail("BT_RELAY must be <host>:<consolePort>\n");
|
||||
}
|
||||
memcpy(host_part, relay_env, colon - relay_env);
|
||||
host_part[colon - relay_env] = '\0';
|
||||
|
||||
unsigned long relay_ip = inet_addr(host_part);
|
||||
if (relay_ip == INADDR_NONE)
|
||||
{
|
||||
hostent *he = gethostbyname(host_part);
|
||||
if (he == NULL || he->h_addr_list[0] == NULL)
|
||||
{
|
||||
DEBUG_STREAM << "ERROR: BT_RELAY host '" << host_part
|
||||
<< "' did not resolve\n" << std::flush;
|
||||
Fail("BT_RELAY host unresolvable\n");
|
||||
}
|
||||
relay_ip = *(unsigned long *)he->h_addr_list[0];
|
||||
}
|
||||
relayConsoleAddress.sin_family = AF_INET;
|
||||
relayConsoleAddress.sin_addr.S_un.S_addr = relay_ip;
|
||||
relayConsoleAddress.sin_port = htons((unsigned short)relay_port);
|
||||
relayGameAddress = relayConsoleAddress;
|
||||
relayGameAddress.sin_port = htons((unsigned short)(relay_port + 1));
|
||||
|
||||
const char *self_env = getenv("BT_SELF");
|
||||
if (self_env == NULL || self_env[0] == '\0')
|
||||
{
|
||||
Fail("BT_RELAY requires BT_SELF=<your [pilots] entry>\n");
|
||||
}
|
||||
Str_Copy(relaySelf, self_env, sizeof(relaySelf));
|
||||
relayMode = True;
|
||||
DEBUG_STREAM << "[relay] mode ON: relay " << host_part << ":"
|
||||
<< relay_port << " (game " << (relay_port + 1) << "), self='"
|
||||
<< relaySelf << "'\n" << std::flush;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -309,6 +406,50 @@ void L4NetworkManager::CreateConsoleHost()
|
||||
numberOfConsoleHostsConnected = 0; // Shouldn't be any consoles yet
|
||||
numberOfMungaHostsConnected = 0; // No game machines either
|
||||
|
||||
//
|
||||
// D1 RELAY MODE: dial OUT to the relay's console port instead of listening
|
||||
// (outbound = NAT-friendly; the relay terminates the same legacy console
|
||||
// protocol, so the egg/ack/launch handlers are untouched). The console
|
||||
// host starts in OpeningConnectionStatus; CheckBuffers' existing Opening
|
||||
// completion path (getpeername + address match, which holds by
|
||||
// construction) fires the HostConnectedMessage.
|
||||
//
|
||||
if (relayMode)
|
||||
{
|
||||
// First dial waits for the relay to come up (like mesh peers wait on
|
||||
// each other); a mid-game RE-dial (console-loss handler) is bounded
|
||||
// short so a dead relay can't hang the pod -- the app's no-console
|
||||
// self-launch guard (APP.cpp CheckLoadMessageHandler) and the mesh
|
||||
// console-loss semantics both tolerate a console-less pod.
|
||||
int timeout_seconds = relayConsoleDialedOnce ? 3 : 60;
|
||||
SOCKET relay_console_socket =
|
||||
RelayDialTcp(relayConsoleAddress, timeout_seconds);
|
||||
if (relay_console_socket == INVALID_SOCKET && !relayConsoleDialedOnce)
|
||||
{
|
||||
DEBUG_STREAM << "ERROR: BT_RELAY set but the relay console port "
|
||||
"did not answer within " << timeout_seconds << "s\n" << std::flush;
|
||||
PostQuitMessage(AbortExitCodeID);
|
||||
return;
|
||||
}
|
||||
relayConsoleDialedOnce = True;
|
||||
myConsoleHost = new L4Host(
|
||||
FirstLegalHostID,
|
||||
ConsoleHostType,
|
||||
&relayConsoleAddress,
|
||||
relay_console_socket,
|
||||
"Console");
|
||||
Register_Object(myConsoleHost);
|
||||
myConsoleHost->SetConnectStatus(
|
||||
(relay_console_socket == INVALID_SOCKET)
|
||||
? L4Host::NoNetworkConnectionStatus // console-less continue
|
||||
: L4Host::OpeningConnectionStatus);
|
||||
Check(application);
|
||||
Check(application->GetHostManager());
|
||||
application->GetHostManager()->AdoptRemoteHost(myConsoleHost);
|
||||
networkEggNotationFile = NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
//
|
||||
// Post a listen for a console with an unknown network address
|
||||
// this will return a stream that the console will eventually be on
|
||||
@@ -484,8 +625,19 @@ void
|
||||
//
|
||||
// See if this host is us or someone else
|
||||
//
|
||||
// D1 RELAY MODE: local-NIC matching cannot identify us across NAT (our
|
||||
// public address differs from every NIC), so the pod self-identifies by
|
||||
// the BT_SELF string matching its [pilots] entry EXACTLY (the entries
|
||||
// become arbitrary unique ip:port-shaped tags; ports/NICs irrelevant --
|
||||
// which also lets N instances share one box with identical -net).
|
||||
//
|
||||
//WinSock support :ADB 01/06/07
|
||||
bool addressFound = false;
|
||||
if (relayMode)
|
||||
{
|
||||
addressFound = (_stricmp((const char *)host_name, relaySelf) == 0);
|
||||
}
|
||||
else
|
||||
for(int i=0; i<num_addresses; i++)
|
||||
{
|
||||
if(addresses[i] == net_address.sin_addr.S_un.S_addr && localGamePort == ntohs(net_address.sin_port))
|
||||
@@ -515,6 +667,30 @@ void
|
||||
Check(application->GetHostManager());
|
||||
application->GetHostManager()->AdoptLocalHost(my_l4host);
|
||||
}
|
||||
else if (relayMode)
|
||||
{
|
||||
//
|
||||
// D1 RELAY MODE: peers are VIRTUAL hosts -- no per-peer socket, no
|
||||
// connect/listen. They sit in NoNetworkConnectionStatus (which the
|
||||
// CheckBuffers loop skips) until the relay's PEER_UP control frame
|
||||
// synthesizes the HostConnectedMessage that flips them OnLine. The
|
||||
// hostID assignment (egg order) and remoteHostCount stay identical
|
||||
// to the mesh, so the "All connections completed!" gate and the
|
||||
// whole app ladder run UNMODIFIED.
|
||||
//
|
||||
remoteHostCount++;
|
||||
my_l4host = new L4Host(
|
||||
nextOpenHostID,
|
||||
mission_host_data->GetHostType(),
|
||||
&net_address,
|
||||
INVALID_SOCKET,
|
||||
host_name);
|
||||
Register_Object(my_l4host);
|
||||
my_l4host->SetConnectStatus(L4Host::NoNetworkConnectionStatus);
|
||||
Check(application);
|
||||
Check(application->GetHostManager());
|
||||
application->GetHostManager()->AdoptRemoteHost(my_l4host);
|
||||
}
|
||||
else
|
||||
{
|
||||
//
|
||||
@@ -555,6 +731,20 @@ void
|
||||
nextOpenHostID++;
|
||||
}
|
||||
|
||||
//
|
||||
// D1 RELAY MODE: one outbound game connection to the relay carries every
|
||||
// peer. BT_SELF must have matched an egg entry (the local host drives our
|
||||
// hostID + fromHost stamping).
|
||||
//
|
||||
if (relayMode)
|
||||
{
|
||||
if (!listen)
|
||||
{
|
||||
Fail("BT_SELF matched no [pilots] entry in the egg\n");
|
||||
}
|
||||
ConnectRelayGame(application->GetHostManager()->GetLocalHostID());
|
||||
}
|
||||
|
||||
//
|
||||
// All the hosts are created, the connects/listens done.
|
||||
//
|
||||
@@ -639,6 +829,26 @@ Logical
|
||||
return True;
|
||||
}
|
||||
|
||||
//
|
||||
// D1 RELAY MODE: drop the relay game/UDP sockets (virtual game hosts hold
|
||||
// INVALID_SOCKET; the loop below CloseConnection()s them harmlessly).
|
||||
//
|
||||
if (relayMode)
|
||||
{
|
||||
if (relayGameSocket != INVALID_SOCKET)
|
||||
{
|
||||
closesocket(relayGameSocket);
|
||||
relayGameSocket = INVALID_SOCKET;
|
||||
}
|
||||
if (relayUdpSocket != INVALID_SOCKET)
|
||||
{
|
||||
closesocket(relayUdpSocket);
|
||||
relayUdpSocket = INVALID_SOCKET;
|
||||
}
|
||||
relayPadTail = 0;
|
||||
udpUp = False;
|
||||
}
|
||||
|
||||
//
|
||||
// If we get here there was a network setup, we must drop all connections
|
||||
// deregister all the remote hosts and then deregister the local host.
|
||||
@@ -1018,6 +1228,350 @@ void L4NetworkManager::HostDisconnectedMessageHandler(HostDisconnectedMessage* H
|
||||
DEBUG_STREAM << addressString << "\n" << std::flush;
|
||||
}
|
||||
|
||||
//#############################################################################
|
||||
// D1 RELAY MODE support units. Everything below is dead unless BT_RELAY set.
|
||||
//#############################################################################
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// RelayDialTcp -- outbound TCP dial with a BOUNDED retry window (unlike
|
||||
// OpenConnection's unbounded 10061 busy-loop), then nonblocking + NODELAY.
|
||||
//
|
||||
SOCKET L4NetworkManager::RelayDialTcp(
|
||||
const SOCKADDR_IN &endpoint,
|
||||
int timeout_seconds)
|
||||
{
|
||||
unsigned long deadline = GetTickCount() + (unsigned long)timeout_seconds * 1000UL;
|
||||
for (;;)
|
||||
{
|
||||
SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
||||
if (sock == INVALID_SOCKET)
|
||||
{
|
||||
return INVALID_SOCKET;
|
||||
}
|
||||
if (connect(sock, (const sockaddr *)&endpoint, sizeof(endpoint)) == 0)
|
||||
{
|
||||
unsigned long enable = 1;
|
||||
ioctlsocket(sock, FIONBIO, &enable);
|
||||
BOOL noDelay = TRUE;
|
||||
setsockopt(sock, IPPROTO_TCP, TCP_NODELAY,
|
||||
(char *)&noDelay, sizeof(noDelay));
|
||||
return sock;
|
||||
}
|
||||
int wsa_error = WSAGetLastError();
|
||||
closesocket(sock);
|
||||
if (GetTickCount() >= deadline
|
||||
|| (wsa_error != WSAECONNREFUSED && wsa_error != WSAETIMEDOUT))
|
||||
{
|
||||
DEBUG_STREAM << "[relay] dial failed (WSA " << wsa_error
|
||||
<< ")\n" << std::flush;
|
||||
return INVALID_SOCKET;
|
||||
}
|
||||
Sleep(250);
|
||||
}
|
||||
}
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// RelaySendAll -- partial-send-safe transmit on the multiplexed relay socket.
|
||||
// REQUIRED correctness over the mesh path's fire-and-forget send(): a partial
|
||||
// write here would desync the envelope framing for every peer at once.
|
||||
//
|
||||
Logical L4NetworkManager::RelaySendAll(
|
||||
SOCKET sock,
|
||||
const char *buffer,
|
||||
int length)
|
||||
{
|
||||
int sent_total = 0;
|
||||
while (sent_total < length)
|
||||
{
|
||||
int sent = send(sock, buffer + sent_total, length - sent_total, 0);
|
||||
if (sent > 0)
|
||||
{
|
||||
sent_total += sent;
|
||||
continue;
|
||||
}
|
||||
int wsa_error = WSAGetLastError();
|
||||
if (sent == SOCKET_ERROR && wsa_error == WSAEWOULDBLOCK)
|
||||
{
|
||||
// Kernel buffer full: wait (bounded) for writability.
|
||||
fd_set write_set;
|
||||
FD_ZERO(&write_set);
|
||||
FD_SET(sock, &write_set);
|
||||
timeval tv;
|
||||
tv.tv_sec = 5;
|
||||
tv.tv_usec = 0;
|
||||
if (select(0, NULL, &write_set, NULL, &tv) == 1)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
DEBUG_STREAM << "[relay] send stalled >5s\n" << std::flush;
|
||||
return False;
|
||||
}
|
||||
DEBUG_STREAM << "[relay] send failed (WSA " << wsa_error << ")\n"
|
||||
<< std::flush;
|
||||
return False;
|
||||
}
|
||||
return True;
|
||||
}
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// BuildRelayPacket -- the classic frame builder (the malloc + header fill the
|
||||
// live mesh Send() does inline; kept as a SEPARATE unit so the verified mesh
|
||||
// path stays byte-for-byte untouched). Caller frees.
|
||||
//
|
||||
NetworkPacket *L4NetworkManager::BuildRelayPacket(
|
||||
Message *message,
|
||||
ClientID client,
|
||||
int *packet_size)
|
||||
{
|
||||
Check_Pointer(message);
|
||||
Verify(message->messageLength >= 8);
|
||||
NetworkPacket *packet =
|
||||
(NetworkPacket *)malloc(message->messageLength + sizeof(NetworkPacketHeader));
|
||||
memcpy(&packet->messageData, message, message->messageLength);
|
||||
packet->clientID = client;
|
||||
packet->gameID = gameID;
|
||||
packet->timeStamp = Now();
|
||||
Host *local_host = application->GetHostManager()->GetLocalHost();
|
||||
packet->fromHost = (local_host == NULL) ? 0 : local_host->GetHostID();
|
||||
*packet_size = message->messageLength + sizeof(NetworkPacketHeader);
|
||||
return packet;
|
||||
}
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// RelaySendFrame -- envelope + frame on the relay game connection.
|
||||
//
|
||||
Logical L4NetworkManager::RelaySendFrame(
|
||||
int route,
|
||||
const NetworkPacket *packet,
|
||||
int packet_size)
|
||||
{
|
||||
if (relayGameSocket == INVALID_SOCKET)
|
||||
{
|
||||
return False;
|
||||
}
|
||||
char wire[sizeof(RelayTcpEnvelope) + NETWORKMANAGER_BUFFER_SIZE];
|
||||
Verify(packet_size <= NETWORKMANAGER_BUFFER_SIZE);
|
||||
RelayTcpEnvelope *envelope = (RelayTcpEnvelope *)wire;
|
||||
envelope->route = route;
|
||||
envelope->length = (unsigned int)packet_size;
|
||||
memcpy(wire + sizeof(RelayTcpEnvelope), packet, packet_size);
|
||||
if (!RelaySendAll(relayGameSocket,
|
||||
wire, (int)sizeof(RelayTcpEnvelope) + packet_size))
|
||||
{
|
||||
RelayGameDown("send failure");
|
||||
return False;
|
||||
}
|
||||
if (getenv("BT_NET_TRACE"))
|
||||
{
|
||||
DEBUG_STREAM << "[net-tx] relay route=" << route
|
||||
<< " client=" << (int)packet->clientID
|
||||
<< " msgID=" << (int)packet->messageData.messageID
|
||||
<< " len=" << (int)packet->messageData.messageLength
|
||||
<< "\n" << std::flush;
|
||||
}
|
||||
return True;
|
||||
}
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// ConnectRelayGame -- dial the relay's game port and register with HELLO.
|
||||
// (UDP channel comes in the later phase; TCP carries everything until then.)
|
||||
//
|
||||
void L4NetworkManager::ConnectRelayGame(HostID local_host_ID)
|
||||
{
|
||||
relayGameSocket = RelayDialTcp(relayGameAddress, 60);
|
||||
if (relayGameSocket == INVALID_SOCKET)
|
||||
{
|
||||
Fail("BT_RELAY set but the relay game port did not answer\n");
|
||||
}
|
||||
relayPadTail = 0;
|
||||
struct
|
||||
{
|
||||
RelayTcpEnvelope envelope;
|
||||
int magic;
|
||||
unsigned int hostID;
|
||||
} hello;
|
||||
hello.envelope.route = RELAY_ROUTE_HELLO;
|
||||
hello.envelope.length = 8;
|
||||
hello.magic = RELAY_HELLO_MAGIC;
|
||||
hello.hostID = (unsigned int)local_host_ID;
|
||||
if (!RelaySendAll(relayGameSocket, (const char *)&hello, sizeof(hello)))
|
||||
{
|
||||
Fail("relay HELLO transmit failed\n");
|
||||
}
|
||||
DEBUG_STREAM << "[relay] game connection up, HELLO sent (hostID "
|
||||
<< (int)local_host_ID << ")\n" << std::flush;
|
||||
}
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// RelayGameDown -- the relay game connection died: close it and synthesize a
|
||||
// HostDisconnectedMessage for every online game host (the match continues
|
||||
// peer-less, exactly like an all-peers mesh disconnect; the pod never exits
|
||||
// mid-match).
|
||||
//
|
||||
void L4NetworkManager::RelayGameDown(const char *why)
|
||||
{
|
||||
if (relayGameSocket == INVALID_SOCKET)
|
||||
{
|
||||
return;
|
||||
}
|
||||
DEBUG_STREAM << "[relay] game connection DOWN (" << why << ")\n" << std::flush;
|
||||
closesocket(relayGameSocket);
|
||||
relayGameSocket = INVALID_SOCKET;
|
||||
relayPadTail = 0;
|
||||
udpUp = False;
|
||||
|
||||
HostManager::RemoteHostIterator remote_hosts(application->GetHostManager());
|
||||
Host *host;
|
||||
while ((host = remote_hosts.ReadAndNext()) != NULL)
|
||||
{
|
||||
if (host->GetHostType() == ConsoleHostType)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
L4Host *l4host = Cast_Object(L4Host*, host);
|
||||
if (l4host->GetConnectStatus() != L4Host::OnLineConnectionStatus)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
// INVALID_SOCKET stream pointer: the disconnect handler's
|
||||
// CloseConnection(streamPointer) must not touch a real socket.
|
||||
HostDisconnectedMessage host_down(host->GetHostID(),
|
||||
(unsigned long)INVALID_SOCKET);
|
||||
NetworkClient *client = GetNetworkClientPointer(NetworkManagerClientID);
|
||||
Check(client);
|
||||
client->ReceiveNetworkPacket(NULL, &host_down);
|
||||
}
|
||||
}
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// CheckRelay -- THE relay receive seam, called from the top of CheckBuffers.
|
||||
// Drains complete envelopes from the relay pad: control frames (PEER_UP/DOWN)
|
||||
// are handled inline by synthesizing the SAME HostConnected/Disconnected
|
||||
// messages the mesh accept path routes, so the connection gate and app ladder
|
||||
// run unmodified; game frames are copied out envelope-stripped (they
|
||||
// self-identify via NetworkPacketHeader.fromHost -- every consumer routes by
|
||||
// payload ids, never by arrival socket). Returns True with one game frame.
|
||||
//
|
||||
Logical L4NetworkManager::CheckRelay(NetworkPacket *network_packet)
|
||||
{
|
||||
if (!relayMode || relayGameSocket == INVALID_SOCKET)
|
||||
{
|
||||
return False;
|
||||
}
|
||||
for (;;)
|
||||
{
|
||||
//
|
||||
// Drain complete envelopes already buffered.
|
||||
//
|
||||
while (relayPadTail >= (int)sizeof(RelayTcpEnvelope))
|
||||
{
|
||||
RelayTcpEnvelope *envelope = (RelayTcpEnvelope *)relayPad;
|
||||
int frame_length = (int)envelope->length;
|
||||
if (frame_length > NETWORKMANAGER_BUFFER_SIZE)
|
||||
{
|
||||
RelayGameDown("oversize frame (protocol desync)");
|
||||
return False;
|
||||
}
|
||||
int whole = (int)sizeof(RelayTcpEnvelope) + frame_length;
|
||||
if (relayPadTail < whole)
|
||||
{
|
||||
break; // partial frame; need more bytes
|
||||
}
|
||||
int route = envelope->route;
|
||||
char *payload = relayPad + sizeof(RelayTcpEnvelope);
|
||||
|
||||
Logical is_game_frame = False;
|
||||
if (route == RELAY_ROUTE_PEER_UP && frame_length >= 4)
|
||||
{
|
||||
HostID peer_id = (HostID)(*(int *)payload);
|
||||
Host *peer = application->GetHostManager()->GetRemoteHost(peer_id);
|
||||
if (peer != NULL)
|
||||
{
|
||||
L4Host *l4peer = Cast_Object(L4Host*, peer);
|
||||
HostConnectedMessage peer_up(peer_id,
|
||||
*l4peer->GetNetworkAddress(), // the egg-parsed tag address
|
||||
(unsigned long)INVALID_SOCKET);
|
||||
NetworkClient *client =
|
||||
GetNetworkClientPointer(NetworkManagerClientID);
|
||||
Check(client);
|
||||
client->ReceiveNetworkPacket(NULL, &peer_up);
|
||||
}
|
||||
}
|
||||
else if (route == RELAY_ROUTE_PEER_DOWN && frame_length >= 4)
|
||||
{
|
||||
HostID peer_id = (HostID)(*(int *)payload);
|
||||
Host *peer = application->GetHostManager()->GetRemoteHost(peer_id);
|
||||
if (peer != NULL
|
||||
&& Cast_Object(L4Host*, peer)->GetConnectStatus()
|
||||
== L4Host::OnLineConnectionStatus)
|
||||
{
|
||||
HostDisconnectedMessage peer_down(peer_id,
|
||||
(unsigned long)INVALID_SOCKET);
|
||||
NetworkClient *client =
|
||||
GetNetworkClientPointer(NetworkManagerClientID);
|
||||
Check(client);
|
||||
client->ReceiveNetworkPacket(NULL, &peer_down);
|
||||
}
|
||||
}
|
||||
else if (route >= FirstLegalHostID)
|
||||
{
|
||||
is_game_frame = True;
|
||||
Mem_Copy(network_packet, payload, frame_length,
|
||||
NETWORKMANAGER_BUFFER_SIZE);
|
||||
}
|
||||
// (unknown negative routes: ignore -- forward compatibility)
|
||||
|
||||
int remainder = relayPadTail - whole;
|
||||
if (remainder > 0)
|
||||
{
|
||||
memmove(relayPad, relayPad + whole, remainder);
|
||||
}
|
||||
relayPadTail = remainder;
|
||||
|
||||
if (is_game_frame)
|
||||
{
|
||||
if (getenv("BT_NET_TRACE"))
|
||||
{
|
||||
DEBUG_STREAM << "[net-rx] relay client="
|
||||
<< (int)network_packet->clientID
|
||||
<< " msgID=" << (int)network_packet->messageData.messageID
|
||||
<< " len=" << (int)network_packet->messageData.messageLength
|
||||
<< " from host " << (int)network_packet->fromHost
|
||||
<< "\n" << std::flush;
|
||||
}
|
||||
return True;
|
||||
}
|
||||
}
|
||||
//
|
||||
// Refill from the socket.
|
||||
//
|
||||
int space = (int)sizeof(relayPad) - relayPadTail;
|
||||
int received = recv(relayGameSocket, relayPad + relayPadTail, space, 0);
|
||||
if (received > 0)
|
||||
{
|
||||
relayPadTail += received;
|
||||
continue;
|
||||
}
|
||||
if (received == 0)
|
||||
{
|
||||
RelayGameDown("closed by relay");
|
||||
return False;
|
||||
}
|
||||
int wsa_error = WSAGetLastError();
|
||||
if (wsa_error == WSAEWOULDBLOCK)
|
||||
{
|
||||
return False; // no more data this poll
|
||||
}
|
||||
if (wsa_error == WSAECONNRESET)
|
||||
{
|
||||
RelayGameDown("connection reset");
|
||||
return False;
|
||||
}
|
||||
DEBUG_STREAM << "[relay] recv error WSA " << wsa_error << "\n" << std::flush;
|
||||
return False;
|
||||
}
|
||||
}
|
||||
|
||||
#if MESSAGE_BUFFERING
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// L4NetworkManager::Send
|
||||
@@ -1098,6 +1652,23 @@ void L4NetworkManager::Send(
|
||||
return;
|
||||
}
|
||||
Verify(l4host->GetConnectStatus() == L4Host::OnLineConnectionStatus);
|
||||
|
||||
//
|
||||
// D1 RELAY MODE: game-host traffic rides the ONE relay connection inside a
|
||||
// {route, length} envelope (route = destination hostID; the relay demuxes).
|
||||
// The CONSOLE host is excluded -- its connection speaks the legacy raw
|
||||
// protocol which the relay TERMINATES rather than routes (egg ack, etc).
|
||||
//
|
||||
if (relayMode && l4host->GetHostType() != ConsoleHostType)
|
||||
{
|
||||
int relay_packet_size;
|
||||
NetworkPacket *relay_packet =
|
||||
BuildRelayPacket(message, client, &relay_packet_size);
|
||||
RelaySendFrame((int)host_ID, relay_packet, relay_packet_size);
|
||||
free(relay_packet);
|
||||
CLEAR_SEND_PACKET();
|
||||
return;
|
||||
}
|
||||
//
|
||||
// figure out where to put the packet we're assembling
|
||||
//
|
||||
@@ -1507,6 +2078,26 @@ void
|
||||
#if !BATCHED_TRANSMIT
|
||||
Host
|
||||
*this_host;
|
||||
|
||||
//
|
||||
// D1 RELAY MODE: build ONCE, send ONCE with the broadcast route -- the
|
||||
// relay fans out server-side. This kills the mesh's (N-1)x upload
|
||||
// duplication at the exact seam where EntityBroadcastToReplicants already
|
||||
// serializes the update record once.
|
||||
//
|
||||
if (relayMode)
|
||||
{
|
||||
if (relayGameSocket != INVALID_SOCKET)
|
||||
{
|
||||
int relay_packet_size;
|
||||
NetworkPacket *relay_packet =
|
||||
BuildRelayPacket(message, client, &relay_packet_size);
|
||||
RelaySendFrame(RELAY_ROUTE_BROADCAST, relay_packet, relay_packet_size);
|
||||
free(relay_packet);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
//
|
||||
// Iterate through the host list, resolving network addresses, sending and
|
||||
// making sure we don't send to ourselves.
|
||||
@@ -2020,6 +2611,19 @@ Logical L4NetworkManager::CheckBuffers(NetworkPacket *network_packet)
|
||||
//
|
||||
Check(application);
|
||||
Check(application->GetHostManager());
|
||||
|
||||
//
|
||||
// D1 RELAY MODE: the relay game connection is polled FIRST (control frames
|
||||
// synthesize HostConnected/Disconnected inline; game frames return here).
|
||||
// The classic per-host loop below still services the CONSOLE host; game
|
||||
// hosts are virtual (skipped via the relay gate in the OnLine recv block).
|
||||
//
|
||||
if (relayMode && CheckRelay(network_packet))
|
||||
{
|
||||
CLEAR_CHECK_BUFFERS();
|
||||
return True;
|
||||
}
|
||||
|
||||
//
|
||||
// Make an iterator to take us through all the hosts
|
||||
//
|
||||
@@ -2239,6 +2843,16 @@ Logical L4NetworkManager::CheckBuffers(NetworkPacket *network_packet)
|
||||
break;
|
||||
}
|
||||
#if !BATCHED_RECEIVE
|
||||
//
|
||||
// D1 RELAY MODE: online GAME hosts are virtual (no socket of their own;
|
||||
// their traffic arrives via CheckRelay above) -- recv'ing their
|
||||
// INVALID_SOCKET would just spam WSAENOTSOCK. The console host keeps
|
||||
// its real socket and is polled normally.
|
||||
//
|
||||
if (relayMode && remote_host->GetHostType() != ConsoleHostType)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
//
|
||||
// If we get this far the host is on line, if there is room to do a receive
|
||||
// into this buffer then do one.
|
||||
@@ -2921,6 +3535,15 @@ void L4NetworkManager::CloseConnection(SOCKET socket_ptr) // socket address fro
|
||||
//WinSock support :ADB 01/06/07
|
||||
void L4NetworkManager::Mode(NetworkMode myMode)
|
||||
{
|
||||
//
|
||||
// D1: the mode is now STORED (the 2007 port ignored it). The app ladder
|
||||
// drives it authentically -- UnreliableMode at LoadingMission, back to
|
||||
// Reliable at mission end (APP.cpp) -- and the relay UDP phase routes
|
||||
// unreliable-window traffic by (mode == UnreliableMode && !ReliableFlag),
|
||||
// restoring the 1995 NETNUB reliable/unreliable split.
|
||||
//
|
||||
currentNetworkMode = myMode;
|
||||
|
||||
//SetSwitchesRequestPtr set_switches_request;
|
||||
//
|
||||
// No netnub present? Just return
|
||||
|
||||
@@ -327,6 +327,30 @@ private:
|
||||
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);
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Private Data
|
||||
//
|
||||
@@ -353,6 +377,23 @@ private:
|
||||
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
|
||||
unsigned long lastUdpKeepaliveTick;
|
||||
unsigned long udpTxSeq;
|
||||
unsigned long udpRxSeq[64]; // per-fromHost last-seen sequence
|
||||
NetworkMode currentNetworkMode; // Reliable/Unreliable (Mode() stores)
|
||||
};
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~ L4NetworkManager inlines ~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Reference in New Issue
Block a user