<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>http://osimdev.org/wiki/index.php?action=history&amp;feed=atom&amp;title=OpenSimulator_Internals%2FCode_Map%2FLLUDP</id>
	<title>OpenSimulator Internals/Code Map/LLUDP - Revision history</title>
	<link rel="self" type="application/atom+xml" href="http://osimdev.org/wiki/index.php?action=history&amp;feed=atom&amp;title=OpenSimulator_Internals%2FCode_Map%2FLLUDP"/>
	<link rel="alternate" type="text/html" href="http://osimdev.org/wiki/index.php?title=OpenSimulator_Internals/Code_Map/LLUDP&amp;action=history"/>
	<updated>2026-08-04T15:31:29Z</updated>
	<subtitle>Revision history for this page on the wiki</subtitle>
	<generator>MediaWiki 1.45.3</generator>
	<entry>
		<id>http://osimdev.org/wiki/index.php?title=OpenSimulator_Internals/Code_Map/LLUDP&amp;diff=55&amp;oldid=prev</id>
		<title>Jwbshaw: First</title>
		<link rel="alternate" type="text/html" href="http://osimdev.org/wiki/index.php?title=OpenSimulator_Internals/Code_Map/LLUDP&amp;diff=55&amp;oldid=prev"/>
		<updated>2026-07-07T11:37:27Z</updated>

		<summary type="html">&lt;p&gt;First&lt;/p&gt;
&lt;p&gt;&lt;b&gt;New page&lt;/b&gt;&lt;/p&gt;&lt;div&gt;= OpenSimulator Internals/Code Map/LLUDP =&lt;br /&gt;
&lt;br /&gt;
== Overview ==&lt;br /&gt;
&lt;br /&gt;
LLUDP is the network transport layer for viewer communication. It implements the Second Life UDP wire protocol -- packet framing, zero-encoding, reliable delivery with ACKs, resend handling, and per-client throttling. It sits below IClientAPI and LLClientView.&lt;br /&gt;
&lt;br /&gt;
Source: OpenSim/Region/ClientStack/Linden/UDP/LLUDPServer.cs&lt;br /&gt;
&lt;br /&gt;
Class hierarchy:&lt;br /&gt;
&lt;br /&gt;
 LLUDPServer → OpenSimUDPBase&lt;br /&gt;
&lt;br /&gt;
One LLUDPServer instance exists per region, attached via LLUDPServerShim (an INonSharedRegionModule).&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== LLUDPServerShim ==&lt;br /&gt;
&lt;br /&gt;
Thin adapter that lets LLUDPServer participate in the region module system.&lt;br /&gt;
&lt;br /&gt;
# AddRegion(scene) -- creates the LLUDPServer bound to the region&amp;#039;s internal IP/port, sets scene.RegionInfo.InternalEndPoint.Port to whatever the server actually bound to&lt;br /&gt;
# RegionLoaded(scene) -- calls Start()&lt;br /&gt;
# RemoveRegion(scene) -- calls Stop()&lt;br /&gt;
# AddScene(scene) -- registers ~10 StatsManager stats (client logouts, UDP receives/sends, packets processed/malformed/orphaned/resent, average process time)&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Constants ==&lt;br /&gt;
&lt;br /&gt;
* MTU = 1400 bytes (maximum transmission unit for a single UDP packet)&lt;br /&gt;
* MAXPAYLOAD = 1200 bytes&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Construction ==&lt;br /&gt;
&lt;br /&gt;
Config read from [ClientStack.LindenUDP]:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Setting !! Default !! Notes&lt;br /&gt;
|-&lt;br /&gt;
| client_socket_rcvbuf_size || 0 (OS default) || Receive buffer size passed to the socket&lt;br /&gt;
|-&lt;br /&gt;
| scene_throttle_max_bps || 6250000 || Total bandwidth throttle for the whole region&lt;br /&gt;
|-&lt;br /&gt;
| TextureSendLimit || 20 || Texture packets queued per OnQueueEmpty trigger&lt;br /&gt;
|-&lt;br /&gt;
| DefaultRTO || 0 || Default retransmission timeout&lt;br /&gt;
|-&lt;br /&gt;
| MaxRTO || 0 || Maximum retransmission timeout&lt;br /&gt;
|-&lt;br /&gt;
| AckTimeout || 60 (seconds) || Time without any packet before a client is disconnected&lt;br /&gt;
|-&lt;br /&gt;
| PausedAckTimeout || 300 (seconds) || Extended timeout when client is paused (e.g. file upload dialog open)&lt;br /&gt;
|-&lt;br /&gt;
| DisableFacelights || false ||&lt;br /&gt;
|-&lt;br /&gt;
| SupportViewerObjectsCache || true ||&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Constructor also measures the actual resolution of Environment.TickCount and Util.GetTimeStampMS() on this hardware -- logged at startup, used for timing accuracy elsewhere.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Start / Stop ==&lt;br /&gt;
&lt;br /&gt;
Start() launches three things:&lt;br /&gt;
# StartInbound() -- base class UDP receive loop, plus a dedicated &amp;quot;Incoming Packets (RegionName)&amp;quot; thread running IncomingPacketHandler()&lt;br /&gt;
# StartOutbound() -- base class UDP send loop, plus a dedicated &amp;quot;Outgoing Packets (RegionName)&amp;quot; thread running OutgoingPacketHandler()&lt;br /&gt;
# OqrEngine.Start() -- JobEngine for outgoing queue refills, decoupled from per-connection thread pool jobs to avoid performance problems with many connections&lt;br /&gt;
&lt;br /&gt;
Stop() reverses all three.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Incoming Packet Path ==&lt;br /&gt;
&lt;br /&gt;
 PacketReceived(buffer)  -- called by OpenSimUDPBase for every UDP datagram&lt;br /&gt;
&lt;br /&gt;
# Decoding: validates minimum length (7 bytes), computes header length (7/8/10 bytes depending on extra header flags), zero-decodes if MSG_ZEROCODED flag set, calls Packet.BuildPacket()&lt;br /&gt;
# Malformed packets are dropped and counted (IncomingMalformedPacketCount) -- logs a warning every 10000 as a probable network attack indicator&lt;br /&gt;
# Packet-to-client mapping:&lt;br /&gt;
#* If the endpoint has a pending queue (client still being created), the packet is queued rather than processed, except UseCircuitCode resends which are acked and dropped&lt;br /&gt;
#* If no client exists yet and the packet is UseCircuitCode, a pending queue is created and HandleUseCircuitCode() is dispatched via FireAndForget&lt;br /&gt;
#* Otherwise the client is looked up by endpoint via Scene.TryGetClient() -- unrecognized senders are counted as orphaned packets&lt;br /&gt;
# ACK receiving: appended ACKs and standalone PacketAck packets are applied to udpClient.NeedAcks&lt;br /&gt;
# ACK sending: reliable packets get their sequence number queued in PendingAcks; ACKs are flushed once 2*MTU bytes have been received since the last flush&lt;br /&gt;
# Duplicate detection: PacketArchive tracks recently seen reliable sequence numbers -- duplicates (resent or not) are dropped after the ack machinery has already run&lt;br /&gt;
# Ping handling: StartPingCheck/CompletePingCheck are handled inline and never reach the packet inbox&lt;br /&gt;
# Everything else is wrapped in an IncomingPacket and added to packetInbox (a BlockingCollection) for the IncomingPacketHandler thread to process&lt;br /&gt;
&lt;br /&gt;
IncomingPacketHandler() thread: pulls from packetInbox with a 4500ms timeout, calls client.ProcessInPacket() for each. Exceptions in one client&amp;#039;s packet do not crash the loop.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Outgoing Packet Path ==&lt;br /&gt;
&lt;br /&gt;
 SendPacket() / SendPacketData()&lt;br /&gt;
&lt;br /&gt;
# Packets larger than MTU with variable blocks are split via packet.ToBytesMultiple() (CoarseLocationUpdate is exempt -- cannot be split)&lt;br /&gt;
# Zero-encoding applied if MSG_ZEROCODED flag is set on the packet&lt;br /&gt;
# Wrapped in an OutgoingPacket and either queued via udpClient.EnqueueOutgoing() or sent immediately via SendPacketFinal()&lt;br /&gt;
&lt;br /&gt;
SendPacketFinal():&lt;br /&gt;
# Appends pending ACKs to plain reliable packets if there&amp;#039;s room (up to 256, then flags MSG_APPENDED_ACKS)&lt;br /&gt;
# Assigns a new sequence number (unless this is a resend, which keeps its original data but sets MSG_RESENT)&lt;br /&gt;
# Calls SyncSend() -- actual socket write&lt;br /&gt;
# If the packet has no custom UnackedMethod, the buffer is freed immediately (fire-and-forget). Otherwise it&amp;#039;s added to udpClient.NeedAcks awaiting acknowledgment&lt;br /&gt;
&lt;br /&gt;
OutgoingPacketHandler() thread loop (runs continuously):&lt;br /&gt;
# Every 100ms: triggers HandleUnacked() checks (resend expired packets)&lt;br /&gt;
# Every 500ms (5 x 100ms): triggers SendAcks()&lt;br /&gt;
# Every 5000ms (10 x 500ms): triggers SendPing()&lt;br /&gt;
# Calls Scene.ForEachClient() with ClientOutgoingPacketHandler, which dequeues throttled outgoing packets per client&lt;br /&gt;
# Sleeps 100ms if no clients connected, 15ms if nothing was sent this round (matches typical OS tick granularity), otherwise loops immediately&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Reliability ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Mechanism !! Notes&lt;br /&gt;
|-&lt;br /&gt;
| NeedAcks || Per-client collection of sent reliable packets awaiting acknowledgment, with expiry based on RTO (retransmission timeout)&lt;br /&gt;
|-&lt;br /&gt;
| PendingAcks || Per-client queue of sequence numbers to be acknowledged back to the sender&lt;br /&gt;
|-&lt;br /&gt;
| PacketArchive || Per-client record of recently received reliable sequence numbers, used to detect and drop duplicates&lt;br /&gt;
|-&lt;br /&gt;
| ResendUnacked() || Sets MSG_RESENT flag, increments ResendCount, requeues or resends immediately&lt;br /&gt;
|-&lt;br /&gt;
| HandleUnacked() || Called every 100ms per client. Disconnects the client if no packet received within AckTimeout (or PausedAckTimeout if paused). Otherwise resends expired unacked packets.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Connection Establishment (HandleUseCircuitCode) ==&lt;br /&gt;
&lt;br /&gt;
Triggered when a UseCircuitCode packet arrives from an endpoint with no existing client:&lt;br /&gt;
&lt;br /&gt;
# IsClientAuthorized() -- validates session/agent/circuit code via AgentCircuitManager.AuthenticateSession()&lt;br /&gt;
# On success: AddClient() creates the LLUDPClient and LLClientView, starts the client&lt;br /&gt;
# If AgentCircuitData is missing (shouldn&amp;#039;t happen for a legitimately authorized circuit), the agent is force-closed&lt;br /&gt;
# Any packets that arrived and were queued in m_pendingCache while the client was being created are reinjected into PacketReceived()&lt;br /&gt;
# SendRegionHandshake() is called if this is not a teleport (teleportFlags &amp;lt;= 0)&lt;br /&gt;
# On authorization failure: pending cache entry removed, warning logged, no client created&lt;br /&gt;
&lt;br /&gt;
AddClient() is synchronized across the whole scene (lock (this)) to avoid race conditions -- referenced Mantis #5365 in source comments as the reason.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Client Disconnection ==&lt;br /&gt;
&lt;br /&gt;
DeactivateClientDueToTimeout() -- called when HandleUnacked() detects no packets received within the timeout window. Logs a warning, then calls Scene.CloseAgent() (falls back to client.Close() if that fails).&lt;br /&gt;
&lt;br /&gt;
LogoutHandler() -- sends the logout packet, then calls Scene.CloseAgent() if not already logging out.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Zero-Encoding ==&lt;br /&gt;
&lt;br /&gt;
ZeroEncode() implements run-length encoding of zero bytes in outgoing packets (a bandwidth optimization from the original SL protocol) -- runs of zero bytes become a 0x00 marker followed by a count byte. Applied conditionally: if the zero-encoded result is not smaller than the original, the MSG_ZEROCODED flag is stripped and the packet is sent unencoded instead.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== Notable Details ==&lt;br /&gt;
&lt;br /&gt;
* Malformed packet warnings fire every 10000 occurrences with the message &amp;quot;probable network attack&amp;quot; -- this is the closest thing to built-in DoS detection at this layer&lt;br /&gt;
* The AddClient() lock is scene-wide, not per-agent -- a documented but still-present performance/correctness tradeoff (source comment references wanting to move to per-circuit locking eventually)&lt;br /&gt;
* BinaryStats packet logging exists (LogPacketHeader) but is disabled by default -- controlled by [Statistics.Binary] Enabled&lt;br /&gt;
* The outgoing loop&amp;#039;s sleep constant (15ms) has a comment explicitly referencing Windows&amp;#039; ~16ms scheduler granularity to avoid the OS rounding up to 32ms&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
== See Also ==&lt;br /&gt;
&lt;br /&gt;
* [[OpenSimulator Internals/Code Map]]&lt;br /&gt;
* [[OpenSimulator Internals/Code Map/Scene]]&lt;br /&gt;
* [[OpenSimulator Internals/Code Map/Shared]]&lt;br /&gt;
* [[OpenSimulator Internals/Walkthroughs/Bring Up Region]]&lt;/div&gt;</summary>
		<author><name>Jwbshaw</name></author>
	</entry>
</feed>