diff options
| author | Andrew Meadows <leviathan@lindenlab.com> | 2026-05-27 09:33:09 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-05-27 09:33:09 -0700 |
| commit | f99e2233a40e3a7b88e6c54aa89a1bc0bcd76456 (patch) | |
| tree | d9853e2f709615082770f2ea8ece4801345b866b | |
| parent | e63651672fe66fe2c60bfb6c823fe78ed6f1b28d (diff) | |
Log failed delivery of crucial vewer-->simulator connection UDP messages (#5568)
| -rw-r--r-- | indra/newview/llstartup.cpp | 157 | ||||
| -rw-r--r-- | indra/newview/llviewermessage.cpp | 21 | ||||
| -rwxr-xr-x | indra/newview/llviewerregion.cpp | 21 |
3 files changed, 165 insertions, 34 deletions
diff --git a/indra/newview/llstartup.cpp b/indra/newview/llstartup.cpp index b3ed65be82..6a85ca069a 100644 --- a/indra/newview/llstartup.cpp +++ b/indra/newview/llstartup.cpp @@ -24,6 +24,72 @@ * $/LicenseInfo$ */ +// +// LOGIN AND CONNECTION SEQUENCE OVERVIEW +// ====================================== +// The Viewer connects to the SL service in two phases: HTTP authentication +// followed by UDP "Circuit" establishment to the first Simulator. +// +// PHASE 1: HTTP LOGIN (see lllogin.cpp, process_login_success_response()) +// ----------------------------------------------------------------------- +// Viewer sends an XMLRPC HTTP POST to LoginServer containing: +// - Credentials (first name, last name, password) +// - Client version, channel, MAC address, machine ID +// - Start location preferences +// +// Login-server responds with critical connection data: +// - agent_id, session_id, secure_session_id (authentication tokens) +// - Circuit_code (used to establish UDP Circuit with Simulator) +// - sim_ip, sim_port (Simulator address to connect to) +// - seed_capability (base URL for HTTP capability requests) +// - region_x, region_y (region grid coordinates) +// +// PHASE 2: UDP CIRCUIT ESTABLISHMENT (see idle_startup() state machine below) +// --------------------------------------------------------------------------- +// After HTTP login succeeds, Viewer establishes a UDP Circuit with the +// simulator. This also happens whenever the Viewer connects to new Simulators +// in the same session. The following UDP messages are exchanged: +// +// 1. UseCircuitCode (Viewer -> Simulator) +// - Sent in STATE_WORLD_INIT +// - Contains: Circuit_code, session_id, agent_id +// - Establishes the UDP Circuit with the Simulator +// +// 2. RegionHandshake (Simulator -> Viewer) +// - Handled by process_region_handshake() in llworld.cpp +// - Contains: region name, terrain textures, water height, region flags +// - Viewer responds with RegionHandshakeReply +// +// 3. CompleteAgentMovement (Viewer -> Simulator) +// - Sent in STATE_AGENT_SEND via send_complete_agent_movement() +// - Signals the Viewer is ready to enter the world +// +// 4. AgentMovementComplete (Simulator -> Viewer) +// - Handled by process_agent_movement_complete() in llviewermessage.cpp +// - Contains: final agent position, look_at direction, region handle +// - Sets gAgentMovementCompleted = true +// - Agent is now fully connected to the region +// +// STARTUP STATE MACHINE +// --------------------- +// The connection sequence is managed by idle_startup() which progresses +// through these key states: +// +// STATE_LOGIN_WAIT - Waiting for HTTP login response +// STATE_LOGIN_PROCESS_RESPONSE - Processing login response data +// STATE_WORLD_INIT - Send UseCircuitCode, enable UDP Circuit +// STATE_WORLD_WAIT - Wait for Circuit acknowledgment +// STATE_AGENT_SEND - Send CompleteAgentMovement +// STATE_AGENT_WAIT - Wait for AgentMovementComplete +// STATE_INVENTORY_SEND - Agent connected, begin loading inventory +// +// HTTP CAPABILITIES +// ----------------- +// After UDP connection, Viewer fetches "capability" URLs from the +// seed_capability endpoint. These provide HTTP endpoints for various +// services (inventory, textures, etc.) that supplement the UDP protocol. +// + #include "llviewerprecompiledheaders.h" #include "llappviewer.h" @@ -277,7 +343,6 @@ void show_first_run_dialog(); bool first_run_dialog_callback(const LLSD& notification, const LLSD& response); void set_startup_status(const F32 frac, const std::string& string, const std::string& msg); bool login_alert_status(const LLSD& notification, const LLSD& response); -void use_circuit_callback(void**, S32 result); void register_viewer_callbacks(LLMessageSystem* msg); void asset_callback_nothing(const LLUUID&, LLAssetType::EType, void*, S32); bool callback_choose_gender(const LLSD& notification, const LLSD& response); @@ -1719,13 +1784,48 @@ bool idle_startup() gUseCircuitCallbackCalled = false; msg->enableCircuit(gFirstSim, true); - // now, use the circuit info to tell simulator about us! + + // UDP CONNECTION STEP 1: Send UseCircuitCode + // This is the first UDP message sent to Simulator after HTTP login. + // It establishes the UDP circuit using the circuit_code received from + // LoginServer. Simulator will respond with an ACK, then send + // RegionHandshake message with region details. LL_INFOS("AppInit") << "viewer: UserLoginLocationReply() Enabling " << gFirstSim << " with code " << msg->mOurCircuitCode << LL_ENDL; msg->newMessageFast(_PREHASH_UseCircuitCode); msg->nextBlockFast(_PREHASH_CircuitCode); msg->addU32Fast(_PREHASH_Code, msg->mOurCircuitCode); msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); msg->addUUIDFast(_PREHASH_ID, gAgent.getID()); + + // build a lambda to be used as callback on ACK or timeout + void (*use_circuit_callback)(void**, S32) = [](void**, S32 result) + { + // bail if we're quitting. + if(LLApp::isExiting()) return; + if( !gUseCircuitCallbackCalled ) + { + gUseCircuitCallbackCalled = true; + if (result != LL_ERR_NOERR) + { + // Make sure user knows something bad happened. JC + LL_WARNS("AppInit") << "Backing up to login screen!" << LL_ENDL; + if (gRememberPassword) + { + LLNotificationsUtil::add("LoginPacketNeverReceived", LLSD(), LLSD(), login_alert_status); + } + else + { + LLNotificationsUtil::add("LoginPacketNeverReceivedNoTP", LLSD(), LLSD(), login_alert_status); + } + reset_login(); + } + else + { + gGotUseCircuitCodeAck = true; + } + } + }; + msg->sendReliable( gFirstSim, gSavedSettings.getS32("UseCircuitCodeMaxRetries"), @@ -1743,6 +1843,9 @@ bool idle_startup() //--------------------------------------------------------------------- // World Wait //--------------------------------------------------------------------- + // UDP CONNECTION STEP 2: Wait for UseCircuitCode acknowledgment + // While waiting, Simulator also sends RegionHandshake (handled by + // process_region_handshake() in llworld.cpp) containing region info. if(STATE_WORLD_WAIT == LLStartUp::getStartupState()) { LL_DEBUGS("AppInit") << "Waiting for simulator ack...." << LL_ENDL; @@ -1758,13 +1861,16 @@ bool idle_startup() //--------------------------------------------------------------------- // Agent Send //--------------------------------------------------------------------- + // UDP CONNECTION STEP 3: Send CompleteAgentMovement + // After the circuit is established and RegionHandshake received, we signal + // to Simulator the Viewer is ready to enter the world. if (STATE_AGENT_SEND == LLStartUp::getStartupState()) { LL_DEBUGS("AppInit") << "Connecting to region..." << LL_ENDL; set_startup_status(0.60f, LLTrans::getString("LoginConnectingToRegion"), gAgent.mMOTD); do_startup_frame(); - // register with the message system so it knows we're - // expecting this message + // Register handler process_agent_movement_complete for AgentMovementComplete - + // the final UDP message confirming the agent is connected. LLMessageSystem* msg = gMessageSystem; msg->setHandlerFuncFast( _PREHASH_AgentMovementComplete, @@ -1807,12 +1913,17 @@ bool idle_startup() //--------------------------------------------------------------------- // Agent Wait //--------------------------------------------------------------------- + // UDP CONNECTION STEP 4: Wait for AgentMovementComplete + // Simulator responds with the agent's confirmed position and look_at + // direction. Once received, gAgentMovementCompleted is set true and the + // agent is fully connected to the region. if (STATE_AGENT_WAIT == LLStartUp::getStartupState()) { do_startup_frame(); if (gAgentMovementCompleted) { + // Connection complete - agent is now in-world LLStartUp::setStartupState( STATE_INVENTORY_SEND ); } do_startup_frame(); @@ -2811,34 +2922,6 @@ bool login_alert_status(const LLSD& notification, const LLSD& response) } -void use_circuit_callback(void**, S32 result) -{ - // bail if we're quitting. - if(LLApp::isExiting()) return; - if( !gUseCircuitCallbackCalled ) - { - gUseCircuitCallbackCalled = true; - if (result) - { - // Make sure user knows something bad happened. JC - LL_WARNS("AppInit") << "Backing up to login screen!" << LL_ENDL; - if (gRememberPassword) - { - LLNotificationsUtil::add("LoginPacketNeverReceived", LLSD(), LLSD(), login_alert_status); - } - else - { - LLNotificationsUtil::add("LoginPacketNeverReceivedNoTP", LLSD(), LLSD(), login_alert_status); - } - reset_login(); - } - else - { - gGotUseCircuitCodeAck = true; - } - } -} - void register_viewer_callbacks(LLMessageSystem* msg) { msg->setHandlerFuncFast(_PREHASH_LayerData, process_layer_data ); @@ -3731,6 +3814,14 @@ bool init_benefits(LLSD& response) return succ; } +// HTTP LOGIN RESPONSE PROCESSING +// Called after successful HTTP XMLRPC authentication. Extracts critical data +// from LoginServer response needed to establish the UDP connection: +// - agent_id, session_id, secure_session_id (authentication tokens) +// - circuit_code (used in UseCircuitCode UDP message) +// - sim_ip, sim_port (simulator address for UDP circuit) +// - seed_capability (URL for fetching HTTP capability endpoints) +// bool process_login_success_response() { LLSD response = LLLoginInstance::getInstance()->getResponse(); @@ -3853,6 +3944,8 @@ bool process_login_success_response() gAgentStartLocation.assign(text); } + // Extract UDP circuit parameters from login response. + // These are used in STATE_WORLD_INIT to establish the UDP circuit. text = response["circuit_code"].asString(); if(!text.empty()) { diff --git a/indra/newview/llviewermessage.cpp b/indra/newview/llviewermessage.cpp index 812ba76551..1f1ff30b8f 100644 --- a/indra/newview/llviewermessage.cpp +++ b/indra/newview/llviewermessage.cpp @@ -418,7 +418,26 @@ void send_complete_agent_movement(const LLHost& sim_host) msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); msg->addU32Fast(_PREHASH_CircuitCode, msg->mOurCircuitCode); - msg->sendReliable(sim_host); + + // build a lambda to be used as callback on ACK or timeout + void (*complete_agent_movement_callback)(void**, S32) = [](void**, S32 result) + { + if(LLApp::isExiting()) return; + if (result != LL_ERR_NOERR) + { + LL_WARNS("Messaging") << "CompleteAgentMovement failed with err=" << result << LL_ENDL; + } + }; + + // We use same retry strategy as UseCircuitCode because this is a crucial message + // that MUST arrive else we'll suffer a failed login/teleport/region-cross + msg->sendReliable( + sim_host, + gSavedSettings.getS32("UseCircuitCodeMaxRetries"), + false, + (F32Seconds)gSavedSettings.getF32("UseCircuitCodeTimeout"), + complete_agent_movement_callback, + NULL); } void process_logout_reply(LLMessageSystem* msg, void**) diff --git a/indra/newview/llviewerregion.cpp b/indra/newview/llviewerregion.cpp index dd02cca50a..5f73d8e901 100755 --- a/indra/newview/llviewerregion.cpp +++ b/indra/newview/llviewerregion.cpp @@ -3207,7 +3207,26 @@ void LLViewerRegion::unpackRegionHandshake() flags |= 0x00000002; //set the bit 1 to be 1 to tell sim the cache file is empty, no need to send cache probes. } msg->addU32("Flags", flags ); - msg->sendReliable(host); + + // build a lambda to be used as callback on ACK or timeout + void (*region_handshake_reply_callback)(void**, S32) = [](void**, S32 result) + { + if(LLApp::isExiting()) return; + if (result != LL_ERR_NOERR) + { + LL_WARNS("Messaging") << "RegionHandshakeReply failed with err=" << result << LL_ENDL; + } + }; + + // This is a crucial message for establishing a connection to a region + // (either the main region or a visible neighbor). + msg->sendReliable( + host, + gSavedSettings.getS32("UseCircuitCodeMaxRetries"), + false, + (F32Seconds)gSavedSettings.getF32("UseCircuitCodeTimeout"), + region_handshake_reply_callback, + NULL); mRegionTimer.reset(); //reset region timer. } |
