diff options
Diffstat (limited to 'OpenSim/Region/ClientStack/Linden/UDP/LLUDPServer.cs')
-rw-r--r-- | OpenSim/Region/ClientStack/Linden/UDP/LLUDPServer.cs | 1274 |
1 files changed, 1274 insertions, 0 deletions
diff --git a/OpenSim/Region/ClientStack/Linden/UDP/LLUDPServer.cs b/OpenSim/Region/ClientStack/Linden/UDP/LLUDPServer.cs new file mode 100644 index 0000000..aff90c5 --- /dev/null +++ b/OpenSim/Region/ClientStack/Linden/UDP/LLUDPServer.cs | |||
@@ -0,0 +1,1274 @@ | |||
1 | /* | ||
2 | * Copyright (c) Contributors, http://opensimulator.org/ | ||
3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. | ||
4 | * | ||
5 | * Redistribution and use in source and binary forms, with or without | ||
6 | * modification, are permitted provided that the following conditions are met: | ||
7 | * * Redistributions of source code must retain the above copyright | ||
8 | * notice, this list of conditions and the following disclaimer. | ||
9 | * * Redistributions in binary form must reproduce the above copyright | ||
10 | * notice, this list of conditions and the following disclaimer in the | ||
11 | * documentation and/or other materials provided with the distribution. | ||
12 | * * Neither the name of the OpenSimulator Project nor the | ||
13 | * names of its contributors may be used to endorse or promote products | ||
14 | * derived from this software without specific prior written permission. | ||
15 | * | ||
16 | * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY | ||
17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
19 | * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY | ||
20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
26 | */ | ||
27 | |||
28 | using System; | ||
29 | using System.Collections.Generic; | ||
30 | using System.Diagnostics; | ||
31 | using System.IO; | ||
32 | using System.Net; | ||
33 | using System.Net.Sockets; | ||
34 | using System.Reflection; | ||
35 | using System.Threading; | ||
36 | using log4net; | ||
37 | using Nini.Config; | ||
38 | using OpenMetaverse.Packets; | ||
39 | using OpenSim.Framework; | ||
40 | using OpenSim.Framework.Statistics; | ||
41 | using OpenSim.Region.Framework.Scenes; | ||
42 | using OpenMetaverse; | ||
43 | |||
44 | using TokenBucket = OpenSim.Region.ClientStack.LindenUDP.TokenBucket; | ||
45 | |||
46 | namespace OpenSim.Region.ClientStack.LindenUDP | ||
47 | { | ||
48 | /// <summary> | ||
49 | /// A shim around LLUDPServer that implements the IClientNetworkServer interface | ||
50 | /// </summary> | ||
51 | public sealed class LLUDPServerShim : IClientNetworkServer | ||
52 | { | ||
53 | LLUDPServer m_udpServer; | ||
54 | |||
55 | public LLUDPServerShim() | ||
56 | { | ||
57 | } | ||
58 | |||
59 | public void Initialise(IPAddress listenIP, ref uint port, int proxyPortOffsetParm, bool allow_alternate_port, IConfigSource configSource, AgentCircuitManager circuitManager) | ||
60 | { | ||
61 | m_udpServer = new LLUDPServer(listenIP, ref port, proxyPortOffsetParm, allow_alternate_port, configSource, circuitManager); | ||
62 | } | ||
63 | |||
64 | public void NetworkStop() | ||
65 | { | ||
66 | m_udpServer.Stop(); | ||
67 | } | ||
68 | |||
69 | public void AddScene(IScene scene) | ||
70 | { | ||
71 | m_udpServer.AddScene(scene); | ||
72 | } | ||
73 | |||
74 | public bool HandlesRegion(Location x) | ||
75 | { | ||
76 | return m_udpServer.HandlesRegion(x); | ||
77 | } | ||
78 | |||
79 | public void Start() | ||
80 | { | ||
81 | m_udpServer.Start(); | ||
82 | } | ||
83 | |||
84 | public void Stop() | ||
85 | { | ||
86 | m_udpServer.Stop(); | ||
87 | } | ||
88 | } | ||
89 | |||
90 | /// <summary> | ||
91 | /// The LLUDP server for a region. This handles incoming and outgoing | ||
92 | /// packets for all UDP connections to the region | ||
93 | /// </summary> | ||
94 | public class LLUDPServer : OpenSimUDPBase | ||
95 | { | ||
96 | /// <summary>Maximum transmission unit, or UDP packet size, for the LLUDP protocol</summary> | ||
97 | public const int MTU = 1400; | ||
98 | |||
99 | private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); | ||
100 | |||
101 | /// <summary>The measured resolution of Environment.TickCount</summary> | ||
102 | public readonly float TickCountResolution; | ||
103 | /// <summary>Number of prim updates to put on the queue each time the | ||
104 | /// OnQueueEmpty event is triggered for updates</summary> | ||
105 | public readonly int PrimUpdatesPerCallback; | ||
106 | /// <summary>Number of texture packets to put on the queue each time the | ||
107 | /// OnQueueEmpty event is triggered for textures</summary> | ||
108 | public readonly int TextureSendLimit; | ||
109 | |||
110 | /// <summary>Handlers for incoming packets</summary> | ||
111 | //PacketEventDictionary packetEvents = new PacketEventDictionary(); | ||
112 | /// <summary>Incoming packets that are awaiting handling</summary> | ||
113 | private OpenMetaverse.BlockingQueue<IncomingPacket> packetInbox = new OpenMetaverse.BlockingQueue<IncomingPacket>(); | ||
114 | /// <summary></summary> | ||
115 | //private UDPClientCollection m_clients = new UDPClientCollection(); | ||
116 | /// <summary>Bandwidth throttle for this UDP server</summary> | ||
117 | protected TokenBucket m_throttle; | ||
118 | |||
119 | /// <summary>Bandwidth throttle rates for this UDP server</summary> | ||
120 | public ThrottleRates ThrottleRates { get; private set; } | ||
121 | |||
122 | /// <summary>Manages authentication for agent circuits</summary> | ||
123 | private AgentCircuitManager m_circuitManager; | ||
124 | /// <summary>Reference to the scene this UDP server is attached to</summary> | ||
125 | protected Scene m_scene; | ||
126 | /// <summary>The X/Y coordinates of the scene this UDP server is attached to</summary> | ||
127 | private Location m_location; | ||
128 | /// <summary>The size of the receive buffer for the UDP socket. This value | ||
129 | /// is passed up to the operating system and used in the system networking | ||
130 | /// stack. Use zero to leave this value as the default</summary> | ||
131 | private int m_recvBufferSize; | ||
132 | /// <summary>Flag to process packets asynchronously or synchronously</summary> | ||
133 | private bool m_asyncPacketHandling; | ||
134 | /// <summary>Tracks whether or not a packet was sent each round so we know | ||
135 | /// whether or not to sleep</summary> | ||
136 | private bool m_packetSent; | ||
137 | |||
138 | /// <summary>Environment.TickCount of the last time that packet stats were reported to the scene</summary> | ||
139 | private int m_elapsedMSSinceLastStatReport = 0; | ||
140 | /// <summary>Environment.TickCount of the last time the outgoing packet handler executed</summary> | ||
141 | private int m_tickLastOutgoingPacketHandler; | ||
142 | /// <summary>Keeps track of the number of elapsed milliseconds since the last time the outgoing packet handler looped</summary> | ||
143 | private int m_elapsedMSOutgoingPacketHandler; | ||
144 | /// <summary>Keeps track of the number of 100 millisecond periods elapsed in the outgoing packet handler executed</summary> | ||
145 | private int m_elapsed100MSOutgoingPacketHandler; | ||
146 | /// <summary>Keeps track of the number of 500 millisecond periods elapsed in the outgoing packet handler executed</summary> | ||
147 | private int m_elapsed500MSOutgoingPacketHandler; | ||
148 | |||
149 | /// <summary>Flag to signal when clients should check for resends</summary> | ||
150 | private bool m_resendUnacked; | ||
151 | /// <summary>Flag to signal when clients should send ACKs</summary> | ||
152 | private bool m_sendAcks; | ||
153 | /// <summary>Flag to signal when clients should send pings</summary> | ||
154 | private bool m_sendPing; | ||
155 | |||
156 | private int m_defaultRTO = 0; | ||
157 | private int m_maxRTO = 0; | ||
158 | |||
159 | private bool m_disableFacelights = false; | ||
160 | |||
161 | public Socket Server { get { return null; } } | ||
162 | |||
163 | public LLUDPServer(IPAddress listenIP, ref uint port, int proxyPortOffsetParm, bool allow_alternate_port, IConfigSource configSource, AgentCircuitManager circuitManager) | ||
164 | : base(listenIP, (int)port) | ||
165 | { | ||
166 | #region Environment.TickCount Measurement | ||
167 | |||
168 | // Measure the resolution of Environment.TickCount | ||
169 | TickCountResolution = 0f; | ||
170 | for (int i = 0; i < 5; i++) | ||
171 | { | ||
172 | int start = Environment.TickCount; | ||
173 | int now = start; | ||
174 | while (now == start) | ||
175 | now = Environment.TickCount; | ||
176 | TickCountResolution += (float)(now - start) * 0.2f; | ||
177 | } | ||
178 | m_log.Info("[LLUDPSERVER]: Average Environment.TickCount resolution: " + TickCountResolution + "ms"); | ||
179 | TickCountResolution = (float)Math.Ceiling(TickCountResolution); | ||
180 | |||
181 | #endregion Environment.TickCount Measurement | ||
182 | |||
183 | m_circuitManager = circuitManager; | ||
184 | int sceneThrottleBps = 0; | ||
185 | |||
186 | IConfig config = configSource.Configs["ClientStack.LindenUDP"]; | ||
187 | if (config != null) | ||
188 | { | ||
189 | m_asyncPacketHandling = config.GetBoolean("async_packet_handling", true); | ||
190 | m_recvBufferSize = config.GetInt("client_socket_rcvbuf_size", 0); | ||
191 | sceneThrottleBps = config.GetInt("scene_throttle_max_bps", 0); | ||
192 | |||
193 | PrimUpdatesPerCallback = config.GetInt("PrimUpdatesPerCallback", 100); | ||
194 | TextureSendLimit = config.GetInt("TextureSendLimit", 20); | ||
195 | |||
196 | m_defaultRTO = config.GetInt("DefaultRTO", 0); | ||
197 | m_maxRTO = config.GetInt("MaxRTO", 0); | ||
198 | m_disableFacelights = config.GetBoolean("DisableFacelights", false); | ||
199 | } | ||
200 | else | ||
201 | { | ||
202 | PrimUpdatesPerCallback = 100; | ||
203 | TextureSendLimit = 20; | ||
204 | } | ||
205 | |||
206 | #region BinaryStats | ||
207 | config = configSource.Configs["Statistics.Binary"]; | ||
208 | m_shouldCollectStats = false; | ||
209 | if (config != null) | ||
210 | { | ||
211 | if (config.Contains("enabled") && config.GetBoolean("enabled")) | ||
212 | { | ||
213 | if (config.Contains("collect_packet_headers")) | ||
214 | m_shouldCollectStats = config.GetBoolean("collect_packet_headers"); | ||
215 | if (config.Contains("packet_headers_period_seconds")) | ||
216 | { | ||
217 | binStatsMaxFilesize = TimeSpan.FromSeconds(config.GetInt("region_stats_period_seconds")); | ||
218 | } | ||
219 | if (config.Contains("stats_dir")) | ||
220 | { | ||
221 | binStatsDir = config.GetString("stats_dir"); | ||
222 | } | ||
223 | } | ||
224 | else | ||
225 | { | ||
226 | m_shouldCollectStats = false; | ||
227 | } | ||
228 | } | ||
229 | #endregion BinaryStats | ||
230 | |||
231 | m_throttle = new TokenBucket(null, sceneThrottleBps); | ||
232 | ThrottleRates = new ThrottleRates(configSource); | ||
233 | } | ||
234 | |||
235 | public void Start() | ||
236 | { | ||
237 | if (m_scene == null) | ||
238 | throw new InvalidOperationException("[LLUDPSERVER]: Cannot LLUDPServer.Start() without an IScene reference"); | ||
239 | |||
240 | m_log.Info("[LLUDPSERVER]: Starting the LLUDP server in " + (m_asyncPacketHandling ? "asynchronous" : "synchronous") + " mode"); | ||
241 | |||
242 | base.Start(m_recvBufferSize, m_asyncPacketHandling); | ||
243 | |||
244 | // Start the packet processing threads | ||
245 | Watchdog.StartThread(IncomingPacketHandler, "Incoming Packets (" + m_scene.RegionInfo.RegionName + ")", ThreadPriority.Normal, false); | ||
246 | Watchdog.StartThread(OutgoingPacketHandler, "Outgoing Packets (" + m_scene.RegionInfo.RegionName + ")", ThreadPriority.Normal, false); | ||
247 | m_elapsedMSSinceLastStatReport = Environment.TickCount; | ||
248 | } | ||
249 | |||
250 | public new void Stop() | ||
251 | { | ||
252 | m_log.Info("[LLUDPSERVER]: Shutting down the LLUDP server for " + m_scene.RegionInfo.RegionName); | ||
253 | base.Stop(); | ||
254 | } | ||
255 | |||
256 | public void AddScene(IScene scene) | ||
257 | { | ||
258 | if (m_scene != null) | ||
259 | { | ||
260 | m_log.Error("[LLUDPSERVER]: AddScene() called on an LLUDPServer that already has a scene"); | ||
261 | return; | ||
262 | } | ||
263 | |||
264 | if (!(scene is Scene)) | ||
265 | { | ||
266 | m_log.Error("[LLUDPSERVER]: AddScene() called with an unrecognized scene type " + scene.GetType()); | ||
267 | return; | ||
268 | } | ||
269 | |||
270 | m_scene = (Scene)scene; | ||
271 | m_location = new Location(m_scene.RegionInfo.RegionHandle); | ||
272 | } | ||
273 | |||
274 | public bool HandlesRegion(Location x) | ||
275 | { | ||
276 | return x == m_location; | ||
277 | } | ||
278 | |||
279 | public void BroadcastPacket(Packet packet, ThrottleOutPacketType category, bool sendToPausedAgents, bool allowSplitting) | ||
280 | { | ||
281 | // CoarseLocationUpdate and AvatarGroupsReply packets cannot be split in an automated way | ||
282 | if ((packet.Type == PacketType.CoarseLocationUpdate || packet.Type == PacketType.AvatarGroupsReply) && allowSplitting) | ||
283 | allowSplitting = false; | ||
284 | |||
285 | if (allowSplitting && packet.HasVariableBlocks) | ||
286 | { | ||
287 | byte[][] datas = packet.ToBytesMultiple(); | ||
288 | int packetCount = datas.Length; | ||
289 | |||
290 | if (packetCount < 1) | ||
291 | m_log.Error("[LLUDPSERVER]: Failed to split " + packet.Type + " with estimated length " + packet.Length); | ||
292 | |||
293 | for (int i = 0; i < packetCount; i++) | ||
294 | { | ||
295 | byte[] data = datas[i]; | ||
296 | m_scene.ForEachClient( | ||
297 | delegate(IClientAPI client) | ||
298 | { | ||
299 | if (client is LLClientView) | ||
300 | SendPacketData(((LLClientView)client).UDPClient, data, packet.Type, category, null); | ||
301 | } | ||
302 | ); | ||
303 | } | ||
304 | } | ||
305 | else | ||
306 | { | ||
307 | byte[] data = packet.ToBytes(); | ||
308 | m_scene.ForEachClient( | ||
309 | delegate(IClientAPI client) | ||
310 | { | ||
311 | if (client is LLClientView) | ||
312 | SendPacketData(((LLClientView)client).UDPClient, data, packet.Type, category, null); | ||
313 | } | ||
314 | ); | ||
315 | } | ||
316 | } | ||
317 | |||
318 | /// <summary> | ||
319 | /// Start the process of sending a packet to the client. | ||
320 | /// </summary> | ||
321 | /// <param name="udpClient"></param> | ||
322 | /// <param name="packet"></param> | ||
323 | /// <param name="category"></param> | ||
324 | /// <param name="allowSplitting"></param> | ||
325 | public void SendPacket(LLUDPClient udpClient, Packet packet, ThrottleOutPacketType category, bool allowSplitting, UnackedPacketMethod method) | ||
326 | { | ||
327 | // CoarseLocationUpdate packets cannot be split in an automated way | ||
328 | if (packet.Type == PacketType.CoarseLocationUpdate && allowSplitting) | ||
329 | allowSplitting = false; | ||
330 | |||
331 | if (allowSplitting && packet.HasVariableBlocks) | ||
332 | { | ||
333 | byte[][] datas = packet.ToBytesMultiple(); | ||
334 | int packetCount = datas.Length; | ||
335 | |||
336 | if (packetCount < 1) | ||
337 | m_log.Error("[LLUDPSERVER]: Failed to split " + packet.Type + " with estimated length " + packet.Length); | ||
338 | |||
339 | for (int i = 0; i < packetCount; i++) | ||
340 | { | ||
341 | byte[] data = datas[i]; | ||
342 | SendPacketData(udpClient, data, packet.Type, category, method); | ||
343 | } | ||
344 | } | ||
345 | else | ||
346 | { | ||
347 | byte[] data = packet.ToBytes(); | ||
348 | SendPacketData(udpClient, data, packet.Type, category, method); | ||
349 | } | ||
350 | } | ||
351 | |||
352 | /// <summary> | ||
353 | /// Start the process of sending a packet to the client. | ||
354 | /// </summary> | ||
355 | /// <param name="udpClient"></param> | ||
356 | /// <param name="data"></param> | ||
357 | /// <param name="type"></param> | ||
358 | /// <param name="category"></param> | ||
359 | public void SendPacketData(LLUDPClient udpClient, byte[] data, PacketType type, ThrottleOutPacketType category, UnackedPacketMethod method) | ||
360 | { | ||
361 | int dataLength = data.Length; | ||
362 | bool doZerocode = (data[0] & Helpers.MSG_ZEROCODED) != 0; | ||
363 | bool doCopy = true; | ||
364 | |||
365 | // Frequency analysis of outgoing packet sizes shows a large clump of packets at each end of the spectrum. | ||
366 | // The vast majority of packets are less than 200 bytes, although due to asset transfers and packet splitting | ||
367 | // there are a decent number of packets in the 1000-1140 byte range. We allocate one of two sizes of data here | ||
368 | // to accomodate for both common scenarios and provide ample room for ACK appending in both | ||
369 | int bufferSize = (dataLength > 180) ? LLUDPServer.MTU : 200; | ||
370 | |||
371 | UDPPacketBuffer buffer = new UDPPacketBuffer(udpClient.RemoteEndPoint, bufferSize); | ||
372 | |||
373 | // Zerocode if needed | ||
374 | if (doZerocode) | ||
375 | { | ||
376 | try | ||
377 | { | ||
378 | dataLength = Helpers.ZeroEncode(data, dataLength, buffer.Data); | ||
379 | doCopy = false; | ||
380 | } | ||
381 | catch (IndexOutOfRangeException) | ||
382 | { | ||
383 | // The packet grew larger than the bufferSize while zerocoding. | ||
384 | // Remove the MSG_ZEROCODED flag and send the unencoded data | ||
385 | // instead | ||
386 | m_log.Debug("[LLUDPSERVER]: Packet exceeded buffer size during zerocoding for " + type + ". DataLength=" + dataLength + | ||
387 | " and BufferLength=" + buffer.Data.Length + ". Removing MSG_ZEROCODED flag"); | ||
388 | data[0] = (byte)(data[0] & ~Helpers.MSG_ZEROCODED); | ||
389 | } | ||
390 | } | ||
391 | |||
392 | // If the packet data wasn't already copied during zerocoding, copy it now | ||
393 | if (doCopy) | ||
394 | { | ||
395 | if (dataLength <= buffer.Data.Length) | ||
396 | { | ||
397 | Buffer.BlockCopy(data, 0, buffer.Data, 0, dataLength); | ||
398 | } | ||
399 | else | ||
400 | { | ||
401 | bufferSize = dataLength; | ||
402 | buffer = new UDPPacketBuffer(udpClient.RemoteEndPoint, bufferSize); | ||
403 | |||
404 | // m_log.Error("[LLUDPSERVER]: Packet exceeded buffer size! This could be an indication of packet assembly not obeying the MTU. Type=" + | ||
405 | // type + ", DataLength=" + dataLength + ", BufferLength=" + buffer.Data.Length + ". Dropping packet"); | ||
406 | Buffer.BlockCopy(data, 0, buffer.Data, 0, dataLength); | ||
407 | } | ||
408 | } | ||
409 | |||
410 | buffer.DataLength = dataLength; | ||
411 | |||
412 | #region Queue or Send | ||
413 | |||
414 | OutgoingPacket outgoingPacket = new OutgoingPacket(udpClient, buffer, category, null); | ||
415 | // If we were not provided a method for handling unacked, use the UDPServer default method | ||
416 | outgoingPacket.UnackedMethod = ((method == null) ? delegate(OutgoingPacket oPacket) { ResendUnacked(oPacket); } : method); | ||
417 | |||
418 | // If a Linden Lab 1.23.5 client receives an update packet after a kill packet for an object, it will | ||
419 | // continue to display the deleted object until relog. Therefore, we need to always queue a kill object | ||
420 | // packet so that it isn't sent before a queued update packet. | ||
421 | bool requestQueue = type == PacketType.KillObject; | ||
422 | if (!outgoingPacket.Client.EnqueueOutgoing(outgoingPacket, requestQueue)) | ||
423 | SendPacketFinal(outgoingPacket); | ||
424 | |||
425 | #endregion Queue or Send | ||
426 | } | ||
427 | |||
428 | public void SendAcks(LLUDPClient udpClient) | ||
429 | { | ||
430 | uint ack; | ||
431 | |||
432 | if (udpClient.PendingAcks.Dequeue(out ack)) | ||
433 | { | ||
434 | List<PacketAckPacket.PacketsBlock> blocks = new List<PacketAckPacket.PacketsBlock>(); | ||
435 | PacketAckPacket.PacketsBlock block = new PacketAckPacket.PacketsBlock(); | ||
436 | block.ID = ack; | ||
437 | blocks.Add(block); | ||
438 | |||
439 | while (udpClient.PendingAcks.Dequeue(out ack)) | ||
440 | { | ||
441 | block = new PacketAckPacket.PacketsBlock(); | ||
442 | block.ID = ack; | ||
443 | blocks.Add(block); | ||
444 | } | ||
445 | |||
446 | PacketAckPacket packet = new PacketAckPacket(); | ||
447 | packet.Header.Reliable = false; | ||
448 | packet.Packets = blocks.ToArray(); | ||
449 | |||
450 | SendPacket(udpClient, packet, ThrottleOutPacketType.Unknown, true, null); | ||
451 | } | ||
452 | } | ||
453 | |||
454 | public void SendPing(LLUDPClient udpClient) | ||
455 | { | ||
456 | StartPingCheckPacket pc = (StartPingCheckPacket)PacketPool.Instance.GetPacket(PacketType.StartPingCheck); | ||
457 | pc.Header.Reliable = false; | ||
458 | |||
459 | pc.PingID.PingID = (byte)udpClient.CurrentPingSequence++; | ||
460 | // We *could* get OldestUnacked, but it would hurt performance and not provide any benefit | ||
461 | pc.PingID.OldestUnacked = 0; | ||
462 | |||
463 | SendPacket(udpClient, pc, ThrottleOutPacketType.Unknown, false, null); | ||
464 | } | ||
465 | |||
466 | public void CompletePing(LLUDPClient udpClient, byte pingID) | ||
467 | { | ||
468 | CompletePingCheckPacket completePing = new CompletePingCheckPacket(); | ||
469 | completePing.PingID.PingID = pingID; | ||
470 | SendPacket(udpClient, completePing, ThrottleOutPacketType.Unknown, false, null); | ||
471 | } | ||
472 | |||
473 | public void HandleUnacked(LLUDPClient udpClient) | ||
474 | { | ||
475 | if (!udpClient.IsConnected) | ||
476 | return; | ||
477 | |||
478 | // Disconnect an agent if no packets are received for some time | ||
479 | //FIXME: Make 60 an .ini setting | ||
480 | if ((Environment.TickCount & Int32.MaxValue) - udpClient.TickLastPacketReceived > 1000 * 60) | ||
481 | { | ||
482 | m_log.Warn("[LLUDPSERVER]: Ack timeout, disconnecting " + udpClient.AgentID); | ||
483 | |||
484 | RemoveClient(udpClient); | ||
485 | return; | ||
486 | } | ||
487 | |||
488 | // Get a list of all of the packets that have been sitting unacked longer than udpClient.RTO | ||
489 | List<OutgoingPacket> expiredPackets = udpClient.NeedAcks.GetExpiredPackets(udpClient.RTO); | ||
490 | |||
491 | if (expiredPackets != null) | ||
492 | { | ||
493 | //m_log.Debug("[LLUDPSERVER]: Handling " + expiredPackets.Count + " packets to " + udpClient.AgentID + ", RTO=" + udpClient.RTO); | ||
494 | // Exponential backoff of the retransmission timeout | ||
495 | udpClient.BackoffRTO(); | ||
496 | for (int i = 0; i < expiredPackets.Count; ++i) | ||
497 | expiredPackets[i].UnackedMethod(expiredPackets[i]); | ||
498 | } | ||
499 | } | ||
500 | |||
501 | public void ResendUnacked(OutgoingPacket outgoingPacket) | ||
502 | { | ||
503 | //m_log.DebugFormat("[LLUDPSERVER]: Resending packet #{0} (attempt {1}), {2}ms have passed", | ||
504 | // outgoingPacket.SequenceNumber, outgoingPacket.ResendCount, Environment.TickCount - outgoingPacket.TickCount); | ||
505 | |||
506 | // Set the resent flag | ||
507 | outgoingPacket.Buffer.Data[0] = (byte)(outgoingPacket.Buffer.Data[0] | Helpers.MSG_RESENT); | ||
508 | outgoingPacket.Category = ThrottleOutPacketType.Resend; | ||
509 | |||
510 | // Bump up the resend count on this packet | ||
511 | Interlocked.Increment(ref outgoingPacket.ResendCount); | ||
512 | |||
513 | // Requeue or resend the packet | ||
514 | if (!outgoingPacket.Client.EnqueueOutgoing(outgoingPacket, false)) | ||
515 | SendPacketFinal(outgoingPacket); | ||
516 | } | ||
517 | |||
518 | public void Flush(LLUDPClient udpClient) | ||
519 | { | ||
520 | // FIXME: Implement? | ||
521 | } | ||
522 | |||
523 | /// <summary> | ||
524 | /// Actually send a packet to a client. | ||
525 | /// </summary> | ||
526 | /// <param name="outgoingPacket"></param> | ||
527 | internal void SendPacketFinal(OutgoingPacket outgoingPacket) | ||
528 | { | ||
529 | UDPPacketBuffer buffer = outgoingPacket.Buffer; | ||
530 | byte flags = buffer.Data[0]; | ||
531 | bool isResend = (flags & Helpers.MSG_RESENT) != 0; | ||
532 | bool isReliable = (flags & Helpers.MSG_RELIABLE) != 0; | ||
533 | bool isZerocoded = (flags & Helpers.MSG_ZEROCODED) != 0; | ||
534 | LLUDPClient udpClient = outgoingPacket.Client; | ||
535 | |||
536 | if (!udpClient.IsConnected) | ||
537 | return; | ||
538 | |||
539 | #region ACK Appending | ||
540 | |||
541 | int dataLength = buffer.DataLength; | ||
542 | |||
543 | // NOTE: I'm seeing problems with some viewers when ACKs are appended to zerocoded packets so I've disabled that here | ||
544 | if (!isZerocoded) | ||
545 | { | ||
546 | // Keep appending ACKs until there is no room left in the buffer or there are | ||
547 | // no more ACKs to append | ||
548 | uint ackCount = 0; | ||
549 | uint ack; | ||
550 | while (dataLength + 5 < buffer.Data.Length && udpClient.PendingAcks.Dequeue(out ack)) | ||
551 | { | ||
552 | Utils.UIntToBytesBig(ack, buffer.Data, dataLength); | ||
553 | dataLength += 4; | ||
554 | ++ackCount; | ||
555 | } | ||
556 | |||
557 | if (ackCount > 0) | ||
558 | { | ||
559 | // Set the last byte of the packet equal to the number of appended ACKs | ||
560 | buffer.Data[dataLength++] = (byte)ackCount; | ||
561 | // Set the appended ACKs flag on this packet | ||
562 | buffer.Data[0] = (byte)(buffer.Data[0] | Helpers.MSG_APPENDED_ACKS); | ||
563 | } | ||
564 | } | ||
565 | |||
566 | buffer.DataLength = dataLength; | ||
567 | |||
568 | #endregion ACK Appending | ||
569 | |||
570 | #region Sequence Number Assignment | ||
571 | |||
572 | if (!isResend) | ||
573 | { | ||
574 | // Not a resend, assign a new sequence number | ||
575 | uint sequenceNumber = (uint)Interlocked.Increment(ref udpClient.CurrentSequence); | ||
576 | Utils.UIntToBytesBig(sequenceNumber, buffer.Data, 1); | ||
577 | outgoingPacket.SequenceNumber = sequenceNumber; | ||
578 | |||
579 | if (isReliable) | ||
580 | { | ||
581 | // Add this packet to the list of ACK responses we are waiting on from the server | ||
582 | udpClient.NeedAcks.Add(outgoingPacket); | ||
583 | } | ||
584 | } | ||
585 | else | ||
586 | { | ||
587 | Interlocked.Increment(ref udpClient.PacketsResent); | ||
588 | } | ||
589 | |||
590 | #endregion Sequence Number Assignment | ||
591 | |||
592 | // Stats tracking | ||
593 | Interlocked.Increment(ref udpClient.PacketsSent); | ||
594 | |||
595 | // Put the UDP payload on the wire | ||
596 | AsyncBeginSend(buffer); | ||
597 | |||
598 | // Keep track of when this packet was sent out (right now) | ||
599 | outgoingPacket.TickCount = Environment.TickCount & Int32.MaxValue; | ||
600 | } | ||
601 | |||
602 | protected override void PacketReceived(UDPPacketBuffer buffer) | ||
603 | { | ||
604 | // Debugging/Profiling | ||
605 | //try { Thread.CurrentThread.Name = "PacketReceived (" + m_scene.RegionInfo.RegionName + ")"; } | ||
606 | //catch (Exception) { } | ||
607 | |||
608 | LLUDPClient udpClient = null; | ||
609 | Packet packet = null; | ||
610 | int packetEnd = buffer.DataLength - 1; | ||
611 | IPEndPoint address = (IPEndPoint)buffer.RemoteEndPoint; | ||
612 | |||
613 | #region Decoding | ||
614 | |||
615 | try | ||
616 | { | ||
617 | packet = Packet.BuildPacket(buffer.Data, ref packetEnd, | ||
618 | // Only allocate a buffer for zerodecoding if the packet is zerocoded | ||
619 | ((buffer.Data[0] & Helpers.MSG_ZEROCODED) != 0) ? new byte[4096] : null); | ||
620 | } | ||
621 | catch (MalformedDataException) | ||
622 | { | ||
623 | } | ||
624 | |||
625 | // Fail-safe check | ||
626 | if (packet == null) | ||
627 | { | ||
628 | m_log.ErrorFormat("[LLUDPSERVER]: Malformed data, cannot parse {0} byte packet from {1}:", | ||
629 | buffer.DataLength, buffer.RemoteEndPoint); | ||
630 | m_log.Error(Utils.BytesToHexString(buffer.Data, buffer.DataLength, null)); | ||
631 | return; | ||
632 | } | ||
633 | |||
634 | #endregion Decoding | ||
635 | |||
636 | #region Packet to Client Mapping | ||
637 | |||
638 | // UseCircuitCode handling | ||
639 | if (packet.Type == PacketType.UseCircuitCode) | ||
640 | { | ||
641 | object[] array = new object[] { buffer, packet }; | ||
642 | |||
643 | Util.FireAndForget(HandleUseCircuitCode, array); | ||
644 | |||
645 | return; | ||
646 | } | ||
647 | |||
648 | // Determine which agent this packet came from | ||
649 | IClientAPI client; | ||
650 | if (!m_scene.TryGetClient(address, out client) || !(client is LLClientView)) | ||
651 | { | ||
652 | //m_log.Debug("[LLUDPSERVER]: Received a " + packet.Type + " packet from an unrecognized source: " + address + " in " + m_scene.RegionInfo.RegionName); | ||
653 | return; | ||
654 | } | ||
655 | |||
656 | udpClient = ((LLClientView)client).UDPClient; | ||
657 | |||
658 | if (!udpClient.IsConnected) | ||
659 | return; | ||
660 | |||
661 | #endregion Packet to Client Mapping | ||
662 | |||
663 | // Stats tracking | ||
664 | Interlocked.Increment(ref udpClient.PacketsReceived); | ||
665 | |||
666 | int now = Environment.TickCount & Int32.MaxValue; | ||
667 | udpClient.TickLastPacketReceived = now; | ||
668 | |||
669 | #region ACK Receiving | ||
670 | |||
671 | // Handle appended ACKs | ||
672 | if (packet.Header.AppendedAcks && packet.Header.AckList != null) | ||
673 | { | ||
674 | for (int i = 0; i < packet.Header.AckList.Length; i++) | ||
675 | udpClient.NeedAcks.Acknowledge(packet.Header.AckList[i], now, packet.Header.Resent); | ||
676 | } | ||
677 | |||
678 | // Handle PacketAck packets | ||
679 | if (packet.Type == PacketType.PacketAck) | ||
680 | { | ||
681 | PacketAckPacket ackPacket = (PacketAckPacket)packet; | ||
682 | |||
683 | for (int i = 0; i < ackPacket.Packets.Length; i++) | ||
684 | udpClient.NeedAcks.Acknowledge(ackPacket.Packets[i].ID, now, packet.Header.Resent); | ||
685 | |||
686 | // We don't need to do anything else with PacketAck packets | ||
687 | return; | ||
688 | } | ||
689 | |||
690 | #endregion ACK Receiving | ||
691 | |||
692 | #region ACK Sending | ||
693 | |||
694 | if (packet.Header.Reliable) | ||
695 | { | ||
696 | udpClient.PendingAcks.Enqueue(packet.Header.Sequence); | ||
697 | |||
698 | // This is a somewhat odd sequence of steps to pull the client.BytesSinceLastACK value out, | ||
699 | // add the current received bytes to it, test if 2*MTU bytes have been sent, if so remove | ||
700 | // 2*MTU bytes from the value and send ACKs, and finally add the local value back to | ||
701 | // client.BytesSinceLastACK. Lockless thread safety | ||
702 | int bytesSinceLastACK = Interlocked.Exchange(ref udpClient.BytesSinceLastACK, 0); | ||
703 | bytesSinceLastACK += buffer.DataLength; | ||
704 | if (bytesSinceLastACK > LLUDPServer.MTU * 2) | ||
705 | { | ||
706 | bytesSinceLastACK -= LLUDPServer.MTU * 2; | ||
707 | SendAcks(udpClient); | ||
708 | } | ||
709 | Interlocked.Add(ref udpClient.BytesSinceLastACK, bytesSinceLastACK); | ||
710 | } | ||
711 | |||
712 | #endregion ACK Sending | ||
713 | |||
714 | #region Incoming Packet Accounting | ||
715 | |||
716 | // Check the archive of received reliable packet IDs to see whether we already received this packet | ||
717 | if (packet.Header.Reliable && !udpClient.PacketArchive.TryEnqueue(packet.Header.Sequence)) | ||
718 | { | ||
719 | if (packet.Header.Resent) | ||
720 | m_log.DebugFormat( | ||
721 | "[LLUDPSERVER]: Received a resend of already processed packet #{0}, type {1} from {2}", | ||
722 | packet.Header.Sequence, packet.Type, client.Name); | ||
723 | else | ||
724 | m_log.WarnFormat( | ||
725 | "[LLUDPSERVER]: Received a duplicate (not marked as resend) of packet #{0}, type {1} from {2}", | ||
726 | packet.Header.Sequence, packet.Type, client.Name); | ||
727 | |||
728 | // Avoid firing a callback twice for the same packet | ||
729 | return; | ||
730 | } | ||
731 | |||
732 | #endregion Incoming Packet Accounting | ||
733 | |||
734 | #region BinaryStats | ||
735 | LogPacketHeader(true, udpClient.CircuitCode, 0, packet.Type, (ushort)packet.Length); | ||
736 | #endregion BinaryStats | ||
737 | |||
738 | #region Ping Check Handling | ||
739 | |||
740 | if (packet.Type == PacketType.StartPingCheck) | ||
741 | { | ||
742 | // We don't need to do anything else with ping checks | ||
743 | StartPingCheckPacket startPing = (StartPingCheckPacket)packet; | ||
744 | CompletePing(udpClient, startPing.PingID.PingID); | ||
745 | |||
746 | if ((Environment.TickCount - m_elapsedMSSinceLastStatReport) >= 3000) | ||
747 | { | ||
748 | udpClient.SendPacketStats(); | ||
749 | m_elapsedMSSinceLastStatReport = Environment.TickCount; | ||
750 | } | ||
751 | return; | ||
752 | } | ||
753 | else if (packet.Type == PacketType.CompletePingCheck) | ||
754 | { | ||
755 | // We don't currently track client ping times | ||
756 | return; | ||
757 | } | ||
758 | |||
759 | #endregion Ping Check Handling | ||
760 | |||
761 | // Inbox insertion | ||
762 | packetInbox.Enqueue(new IncomingPacket(udpClient, packet)); | ||
763 | } | ||
764 | |||
765 | #region BinaryStats | ||
766 | |||
767 | public class PacketLogger | ||
768 | { | ||
769 | public DateTime StartTime; | ||
770 | public string Path = null; | ||
771 | public System.IO.BinaryWriter Log = null; | ||
772 | } | ||
773 | |||
774 | public static PacketLogger PacketLog; | ||
775 | |||
776 | protected static bool m_shouldCollectStats = false; | ||
777 | // Number of seconds to log for | ||
778 | static TimeSpan binStatsMaxFilesize = TimeSpan.FromSeconds(300); | ||
779 | static object binStatsLogLock = new object(); | ||
780 | static string binStatsDir = ""; | ||
781 | |||
782 | public static void LogPacketHeader(bool incoming, uint circuit, byte flags, PacketType packetType, ushort size) | ||
783 | { | ||
784 | if (!m_shouldCollectStats) return; | ||
785 | |||
786 | // Binary logging format is TTTTTTTTCCCCFPPPSS, T=Time, C=Circuit, F=Flags, P=PacketType, S=size | ||
787 | |||
788 | // Put the incoming bit into the least significant bit of the flags byte | ||
789 | if (incoming) | ||
790 | flags |= 0x01; | ||
791 | else | ||
792 | flags &= 0xFE; | ||
793 | |||
794 | // Put the flags byte into the most significant bits of the type integer | ||
795 | uint type = (uint)packetType; | ||
796 | type |= (uint)flags << 24; | ||
797 | |||
798 | // m_log.Debug("1 LogPacketHeader(): Outside lock"); | ||
799 | lock (binStatsLogLock) | ||
800 | { | ||
801 | DateTime now = DateTime.Now; | ||
802 | |||
803 | // m_log.Debug("2 LogPacketHeader(): Inside lock. now is " + now.Ticks); | ||
804 | try | ||
805 | { | ||
806 | if (PacketLog == null || (now > PacketLog.StartTime + binStatsMaxFilesize)) | ||
807 | { | ||
808 | if (PacketLog != null && PacketLog.Log != null) | ||
809 | { | ||
810 | PacketLog.Log.Close(); | ||
811 | } | ||
812 | |||
813 | // First log file or time has expired, start writing to a new log file | ||
814 | PacketLog = new PacketLogger(); | ||
815 | PacketLog.StartTime = now; | ||
816 | PacketLog.Path = (binStatsDir.Length > 0 ? binStatsDir + System.IO.Path.DirectorySeparatorChar.ToString() : "") | ||
817 | + String.Format("packets-{0}.log", now.ToString("yyyyMMddHHmmss")); | ||
818 | PacketLog.Log = new BinaryWriter(File.Open(PacketLog.Path, FileMode.Append, FileAccess.Write)); | ||
819 | } | ||
820 | |||
821 | // Serialize the data | ||
822 | byte[] output = new byte[18]; | ||
823 | Buffer.BlockCopy(BitConverter.GetBytes(now.Ticks), 0, output, 0, 8); | ||
824 | Buffer.BlockCopy(BitConverter.GetBytes(circuit), 0, output, 8, 4); | ||
825 | Buffer.BlockCopy(BitConverter.GetBytes(type), 0, output, 12, 4); | ||
826 | Buffer.BlockCopy(BitConverter.GetBytes(size), 0, output, 16, 2); | ||
827 | |||
828 | // Write the serialized data to disk | ||
829 | if (PacketLog != null && PacketLog.Log != null) | ||
830 | PacketLog.Log.Write(output); | ||
831 | } | ||
832 | catch (Exception ex) | ||
833 | { | ||
834 | m_log.Error("Packet statistics gathering failed: " + ex.Message, ex); | ||
835 | if (PacketLog.Log != null) | ||
836 | { | ||
837 | PacketLog.Log.Close(); | ||
838 | } | ||
839 | PacketLog = null; | ||
840 | } | ||
841 | } | ||
842 | } | ||
843 | |||
844 | #endregion BinaryStats | ||
845 | |||
846 | private void HandleUseCircuitCode(object o) | ||
847 | { | ||
848 | // DateTime startTime = DateTime.Now; | ||
849 | object[] array = (object[])o; | ||
850 | UDPPacketBuffer buffer = (UDPPacketBuffer)array[0]; | ||
851 | UseCircuitCodePacket packet = (UseCircuitCodePacket)array[1]; | ||
852 | |||
853 | m_log.DebugFormat("[LLUDPSERVER]: Handling UseCircuitCode request from {0}", buffer.RemoteEndPoint); | ||
854 | |||
855 | IPEndPoint remoteEndPoint = (IPEndPoint)buffer.RemoteEndPoint; | ||
856 | |||
857 | // Begin the process of adding the client to the simulator | ||
858 | AddNewClient((UseCircuitCodePacket)packet, remoteEndPoint); | ||
859 | |||
860 | // Send ack | ||
861 | SendAckImmediate(remoteEndPoint, packet.Header.Sequence); | ||
862 | |||
863 | // m_log.DebugFormat( | ||
864 | // "[LLUDPSERVER]: Handling UseCircuitCode request from {0} took {1}ms", | ||
865 | // buffer.RemoteEndPoint, (DateTime.Now - startTime).Milliseconds); | ||
866 | } | ||
867 | |||
868 | private void SendAckImmediate(IPEndPoint remoteEndpoint, uint sequenceNumber) | ||
869 | { | ||
870 | PacketAckPacket ack = new PacketAckPacket(); | ||
871 | ack.Header.Reliable = false; | ||
872 | ack.Packets = new PacketAckPacket.PacketsBlock[1]; | ||
873 | ack.Packets[0] = new PacketAckPacket.PacketsBlock(); | ||
874 | ack.Packets[0].ID = sequenceNumber; | ||
875 | |||
876 | byte[] packetData = ack.ToBytes(); | ||
877 | int length = packetData.Length; | ||
878 | |||
879 | UDPPacketBuffer buffer = new UDPPacketBuffer(remoteEndpoint, length); | ||
880 | buffer.DataLength = length; | ||
881 | |||
882 | Buffer.BlockCopy(packetData, 0, buffer.Data, 0, length); | ||
883 | |||
884 | AsyncBeginSend(buffer); | ||
885 | } | ||
886 | |||
887 | private bool IsClientAuthorized(UseCircuitCodePacket useCircuitCode, out AuthenticateResponse sessionInfo) | ||
888 | { | ||
889 | UUID agentID = useCircuitCode.CircuitCode.ID; | ||
890 | UUID sessionID = useCircuitCode.CircuitCode.SessionID; | ||
891 | uint circuitCode = useCircuitCode.CircuitCode.Code; | ||
892 | |||
893 | sessionInfo = m_circuitManager.AuthenticateSession(sessionID, agentID, circuitCode); | ||
894 | return sessionInfo.Authorised; | ||
895 | } | ||
896 | |||
897 | private void AddNewClient(UseCircuitCodePacket useCircuitCode, IPEndPoint remoteEndPoint) | ||
898 | { | ||
899 | UUID agentID = useCircuitCode.CircuitCode.ID; | ||
900 | UUID sessionID = useCircuitCode.CircuitCode.SessionID; | ||
901 | uint circuitCode = useCircuitCode.CircuitCode.Code; | ||
902 | |||
903 | if (m_scene.RegionStatus != RegionStatus.SlaveScene) | ||
904 | { | ||
905 | AuthenticateResponse sessionInfo; | ||
906 | if (IsClientAuthorized(useCircuitCode, out sessionInfo)) | ||
907 | { | ||
908 | AddClient(circuitCode, agentID, sessionID, remoteEndPoint, sessionInfo); | ||
909 | } | ||
910 | else | ||
911 | { | ||
912 | // Don't create circuits for unauthorized clients | ||
913 | m_log.WarnFormat( | ||
914 | "[LLUDPSERVER]: Connection request for client {0} connecting with unnotified circuit code {1} from {2}", | ||
915 | useCircuitCode.CircuitCode.ID, useCircuitCode.CircuitCode.Code, remoteEndPoint); | ||
916 | } | ||
917 | } | ||
918 | else | ||
919 | { | ||
920 | // Slave regions don't accept new clients | ||
921 | m_log.Debug("[LLUDPSERVER]: Slave region " + m_scene.RegionInfo.RegionName + " ignoring UseCircuitCode packet"); | ||
922 | } | ||
923 | } | ||
924 | |||
925 | protected virtual void AddClient(uint circuitCode, UUID agentID, UUID sessionID, IPEndPoint remoteEndPoint, AuthenticateResponse sessionInfo) | ||
926 | { | ||
927 | // In priciple there shouldn't be more than one thread here, ever. | ||
928 | // But in case that happens, we need to synchronize this piece of code | ||
929 | // because it's too important | ||
930 | lock (this) | ||
931 | { | ||
932 | IClientAPI existingClient; | ||
933 | |||
934 | if (!m_scene.TryGetClient(agentID, out existingClient)) | ||
935 | { | ||
936 | // Create the LLUDPClient | ||
937 | LLUDPClient udpClient = new LLUDPClient(this, ThrottleRates, m_throttle, circuitCode, agentID, remoteEndPoint, m_defaultRTO, m_maxRTO); | ||
938 | // Create the LLClientView | ||
939 | LLClientView client = new LLClientView(remoteEndPoint, m_scene, this, udpClient, sessionInfo, agentID, sessionID, circuitCode); | ||
940 | client.OnLogout += LogoutHandler; | ||
941 | |||
942 | client.DisableFacelights = m_disableFacelights; | ||
943 | |||
944 | // Start the IClientAPI | ||
945 | client.Start(); | ||
946 | |||
947 | } | ||
948 | else | ||
949 | { | ||
950 | m_log.WarnFormat("[LLUDPSERVER]: Ignoring a repeated UseCircuitCode from {0} at {1} for circuit {2}", | ||
951 | existingClient.AgentId, remoteEndPoint, circuitCode); | ||
952 | } | ||
953 | } | ||
954 | } | ||
955 | |||
956 | private void RemoveClient(LLUDPClient udpClient) | ||
957 | { | ||
958 | // Remove this client from the scene | ||
959 | IClientAPI client; | ||
960 | if (m_scene.TryGetClient(udpClient.AgentID, out client)) | ||
961 | { | ||
962 | client.IsLoggingOut = true; | ||
963 | client.Close(); | ||
964 | } | ||
965 | } | ||
966 | |||
967 | private void IncomingPacketHandler() | ||
968 | { | ||
969 | // Set this culture for the thread that incoming packets are received | ||
970 | // on to en-US to avoid number parsing issues | ||
971 | Culture.SetCurrentCulture(); | ||
972 | |||
973 | while (base.IsRunning) | ||
974 | { | ||
975 | try | ||
976 | { | ||
977 | IncomingPacket incomingPacket = null; | ||
978 | |||
979 | // HACK: This is a test to try and rate limit packet handling on Mono. | ||
980 | // If it works, a more elegant solution can be devised | ||
981 | if (Util.FireAndForgetCount() < 2) | ||
982 | { | ||
983 | //m_log.Debug("[LLUDPSERVER]: Incoming packet handler is sleeping"); | ||
984 | Thread.Sleep(30); | ||
985 | } | ||
986 | |||
987 | if (packetInbox.Dequeue(100, ref incomingPacket)) | ||
988 | ProcessInPacket(incomingPacket);//, incomingPacket); Util.FireAndForget(ProcessInPacket, incomingPacket); | ||
989 | } | ||
990 | catch (Exception ex) | ||
991 | { | ||
992 | m_log.Error("[LLUDPSERVER]: Error in the incoming packet handler loop: " + ex.Message, ex); | ||
993 | } | ||
994 | |||
995 | Watchdog.UpdateThread(); | ||
996 | } | ||
997 | |||
998 | if (packetInbox.Count > 0) | ||
999 | m_log.Warn("[LLUDPSERVER]: IncomingPacketHandler is shutting down, dropping " + packetInbox.Count + " packets"); | ||
1000 | packetInbox.Clear(); | ||
1001 | |||
1002 | Watchdog.RemoveThread(); | ||
1003 | } | ||
1004 | |||
1005 | private void OutgoingPacketHandler() | ||
1006 | { | ||
1007 | // Set this culture for the thread that outgoing packets are sent | ||
1008 | // on to en-US to avoid number parsing issues | ||
1009 | Culture.SetCurrentCulture(); | ||
1010 | |||
1011 | // Typecast the function to an Action<IClientAPI> once here to avoid allocating a new | ||
1012 | // Action generic every round | ||
1013 | Action<IClientAPI> clientPacketHandler = ClientOutgoingPacketHandler; | ||
1014 | |||
1015 | while (base.IsRunning) | ||
1016 | { | ||
1017 | try | ||
1018 | { | ||
1019 | m_packetSent = false; | ||
1020 | |||
1021 | #region Update Timers | ||
1022 | |||
1023 | m_resendUnacked = false; | ||
1024 | m_sendAcks = false; | ||
1025 | m_sendPing = false; | ||
1026 | |||
1027 | // Update elapsed time | ||
1028 | int thisTick = Environment.TickCount & Int32.MaxValue; | ||
1029 | if (m_tickLastOutgoingPacketHandler > thisTick) | ||
1030 | m_elapsedMSOutgoingPacketHandler += ((Int32.MaxValue - m_tickLastOutgoingPacketHandler) + thisTick); | ||
1031 | else | ||
1032 | m_elapsedMSOutgoingPacketHandler += (thisTick - m_tickLastOutgoingPacketHandler); | ||
1033 | |||
1034 | m_tickLastOutgoingPacketHandler = thisTick; | ||
1035 | |||
1036 | // Check for pending outgoing resends every 100ms | ||
1037 | if (m_elapsedMSOutgoingPacketHandler >= 100) | ||
1038 | { | ||
1039 | m_resendUnacked = true; | ||
1040 | m_elapsedMSOutgoingPacketHandler = 0; | ||
1041 | m_elapsed100MSOutgoingPacketHandler += 1; | ||
1042 | } | ||
1043 | |||
1044 | // Check for pending outgoing ACKs every 500ms | ||
1045 | if (m_elapsed100MSOutgoingPacketHandler >= 5) | ||
1046 | { | ||
1047 | m_sendAcks = true; | ||
1048 | m_elapsed100MSOutgoingPacketHandler = 0; | ||
1049 | m_elapsed500MSOutgoingPacketHandler += 1; | ||
1050 | } | ||
1051 | |||
1052 | // Send pings to clients every 5000ms | ||
1053 | if (m_elapsed500MSOutgoingPacketHandler >= 10) | ||
1054 | { | ||
1055 | m_sendPing = true; | ||
1056 | m_elapsed500MSOutgoingPacketHandler = 0; | ||
1057 | } | ||
1058 | |||
1059 | #endregion Update Timers | ||
1060 | |||
1061 | // Use this for emergency monitoring -- bug hunting | ||
1062 | //if (m_scene.EmergencyMonitoring) | ||
1063 | // clientPacketHandler = MonitoredClientOutgoingPacketHandler; | ||
1064 | //else | ||
1065 | // clientPacketHandler = ClientOutgoingPacketHandler; | ||
1066 | |||
1067 | // Handle outgoing packets, resends, acknowledgements, and pings for each | ||
1068 | // client. m_packetSent will be set to true if a packet is sent | ||
1069 | m_scene.ForEachClient(clientPacketHandler); | ||
1070 | |||
1071 | // If nothing was sent, sleep for the minimum amount of time before a | ||
1072 | // token bucket could get more tokens | ||
1073 | if (!m_packetSent) | ||
1074 | Thread.Sleep((int)TickCountResolution); | ||
1075 | |||
1076 | Watchdog.UpdateThread(); | ||
1077 | } | ||
1078 | catch (Exception ex) | ||
1079 | { | ||
1080 | m_log.Error("[LLUDPSERVER]: OutgoingPacketHandler loop threw an exception: " + ex.Message, ex); | ||
1081 | } | ||
1082 | |||
1083 | } | ||
1084 | |||
1085 | Watchdog.RemoveThread(); | ||
1086 | } | ||
1087 | |||
1088 | private void ClientOutgoingPacketHandler(IClientAPI client) | ||
1089 | { | ||
1090 | try | ||
1091 | { | ||
1092 | if (client is LLClientView) | ||
1093 | { | ||
1094 | LLUDPClient udpClient = ((LLClientView)client).UDPClient; | ||
1095 | |||
1096 | if (udpClient.IsConnected) | ||
1097 | { | ||
1098 | if (m_resendUnacked) | ||
1099 | HandleUnacked(udpClient); | ||
1100 | |||
1101 | if (m_sendAcks) | ||
1102 | SendAcks(udpClient); | ||
1103 | |||
1104 | if (m_sendPing) | ||
1105 | SendPing(udpClient); | ||
1106 | |||
1107 | // Dequeue any outgoing packets that are within the throttle limits | ||
1108 | if (udpClient.DequeueOutgoing()) | ||
1109 | m_packetSent = true; | ||
1110 | } | ||
1111 | } | ||
1112 | } | ||
1113 | catch (Exception ex) | ||
1114 | { | ||
1115 | m_log.Error("[LLUDPSERVER]: OutgoingPacketHandler iteration for " + client.Name + | ||
1116 | " threw an exception: " + ex.Message, ex); | ||
1117 | } | ||
1118 | } | ||
1119 | |||
1120 | #region Emergency Monitoring | ||
1121 | // Alternative packet handler fuull of instrumentation | ||
1122 | // Handy for hunting bugs | ||
1123 | private Stopwatch watch1 = new Stopwatch(); | ||
1124 | private Stopwatch watch2 = new Stopwatch(); | ||
1125 | |||
1126 | private float avgProcessingTicks = 0; | ||
1127 | private float avgResendUnackedTicks = 0; | ||
1128 | private float avgSendAcksTicks = 0; | ||
1129 | private float avgSendPingTicks = 0; | ||
1130 | private float avgDequeueTicks = 0; | ||
1131 | private long nticks = 0; | ||
1132 | private long nticksUnack = 0; | ||
1133 | private long nticksAck = 0; | ||
1134 | private long nticksPing = 0; | ||
1135 | private int npacksSent = 0; | ||
1136 | private int npackNotSent = 0; | ||
1137 | |||
1138 | private void MonitoredClientOutgoingPacketHandler(IClientAPI client) | ||
1139 | { | ||
1140 | nticks++; | ||
1141 | watch1.Start(); | ||
1142 | try | ||
1143 | { | ||
1144 | if (client is LLClientView) | ||
1145 | { | ||
1146 | LLUDPClient udpClient = ((LLClientView)client).UDPClient; | ||
1147 | |||
1148 | if (udpClient.IsConnected) | ||
1149 | { | ||
1150 | if (m_resendUnacked) | ||
1151 | { | ||
1152 | nticksUnack++; | ||
1153 | watch2.Start(); | ||
1154 | |||
1155 | HandleUnacked(udpClient); | ||
1156 | |||
1157 | watch2.Stop(); | ||
1158 | avgResendUnackedTicks = (nticksUnack - 1)/(float)nticksUnack * avgResendUnackedTicks + (watch2.ElapsedTicks / (float)nticksUnack); | ||
1159 | watch2.Reset(); | ||
1160 | } | ||
1161 | |||
1162 | if (m_sendAcks) | ||
1163 | { | ||
1164 | nticksAck++; | ||
1165 | watch2.Start(); | ||
1166 | |||
1167 | SendAcks(udpClient); | ||
1168 | |||
1169 | watch2.Stop(); | ||
1170 | avgSendAcksTicks = (nticksAck - 1) / (float)nticksAck * avgSendAcksTicks + (watch2.ElapsedTicks / (float)nticksAck); | ||
1171 | watch2.Reset(); | ||
1172 | } | ||
1173 | |||
1174 | if (m_sendPing) | ||
1175 | { | ||
1176 | nticksPing++; | ||
1177 | watch2.Start(); | ||
1178 | |||
1179 | SendPing(udpClient); | ||
1180 | |||
1181 | watch2.Stop(); | ||
1182 | avgSendPingTicks = (nticksPing - 1) / (float)nticksPing * avgSendPingTicks + (watch2.ElapsedTicks / (float)nticksPing); | ||
1183 | watch2.Reset(); | ||
1184 | } | ||
1185 | |||
1186 | watch2.Start(); | ||
1187 | // Dequeue any outgoing packets that are within the throttle limits | ||
1188 | if (udpClient.DequeueOutgoing()) | ||
1189 | { | ||
1190 | m_packetSent = true; | ||
1191 | npacksSent++; | ||
1192 | } | ||
1193 | else | ||
1194 | npackNotSent++; | ||
1195 | |||
1196 | watch2.Stop(); | ||
1197 | avgDequeueTicks = (nticks - 1) / (float)nticks * avgDequeueTicks + (watch2.ElapsedTicks / (float)nticks); | ||
1198 | watch2.Reset(); | ||
1199 | |||
1200 | } | ||
1201 | else | ||
1202 | m_log.WarnFormat("[LLUDPSERVER]: Client is not connected"); | ||
1203 | } | ||
1204 | } | ||
1205 | catch (Exception ex) | ||
1206 | { | ||
1207 | m_log.Error("[LLUDPSERVER]: OutgoingPacketHandler iteration for " + client.Name + | ||
1208 | " threw an exception: " + ex.Message, ex); | ||
1209 | } | ||
1210 | watch1.Stop(); | ||
1211 | avgProcessingTicks = (nticks - 1) / (float)nticks * avgProcessingTicks + (watch1.ElapsedTicks / (float)nticks); | ||
1212 | watch1.Reset(); | ||
1213 | |||
1214 | // reuse this -- it's every ~100ms | ||
1215 | if (m_scene.EmergencyMonitoring && nticks % 100 == 0) | ||
1216 | { | ||
1217 | m_log.InfoFormat("[LLUDPSERVER]: avg processing ticks: {0} avg unacked: {1} avg acks: {2} avg ping: {3} avg dequeue: {4} (TickCountRes: {5} sent: {6} notsent: {7})", | ||
1218 | avgProcessingTicks, avgResendUnackedTicks, avgSendAcksTicks, avgSendPingTicks, avgDequeueTicks, TickCountResolution, npacksSent, npackNotSent); | ||
1219 | npackNotSent = npacksSent = 0; | ||
1220 | } | ||
1221 | |||
1222 | } | ||
1223 | |||
1224 | #endregion | ||
1225 | |||
1226 | private void ProcessInPacket(object state) | ||
1227 | { | ||
1228 | IncomingPacket incomingPacket = (IncomingPacket)state; | ||
1229 | Packet packet = incomingPacket.Packet; | ||
1230 | LLUDPClient udpClient = incomingPacket.Client; | ||
1231 | IClientAPI client; | ||
1232 | |||
1233 | // Sanity check | ||
1234 | if (packet == null || udpClient == null) | ||
1235 | { | ||
1236 | m_log.WarnFormat("[LLUDPSERVER]: Processing a packet with incomplete state. Packet=\"{0}\", UDPClient=\"{1}\"", | ||
1237 | packet, udpClient); | ||
1238 | } | ||
1239 | |||
1240 | // Make sure this client is still alive | ||
1241 | if (m_scene.TryGetClient(udpClient.AgentID, out client)) | ||
1242 | { | ||
1243 | try | ||
1244 | { | ||
1245 | // Process this packet | ||
1246 | client.ProcessInPacket(packet); | ||
1247 | } | ||
1248 | catch (ThreadAbortException) | ||
1249 | { | ||
1250 | // If something is trying to abort the packet processing thread, take that as a hint that it's time to shut down | ||
1251 | m_log.Info("[LLUDPSERVER]: Caught a thread abort, shutting down the LLUDP server"); | ||
1252 | Stop(); | ||
1253 | } | ||
1254 | catch (Exception e) | ||
1255 | { | ||
1256 | // Don't let a failure in an individual client thread crash the whole sim. | ||
1257 | m_log.ErrorFormat("[LLUDPSERVER]: Client packet handler for {0} for packet {1} threw an exception", udpClient.AgentID, packet.Type); | ||
1258 | m_log.Error(e.Message, e); | ||
1259 | } | ||
1260 | } | ||
1261 | else | ||
1262 | { | ||
1263 | m_log.DebugFormat("[LLUDPSERVER]: Dropping incoming {0} packet for dead client {1}", packet.Type, udpClient.AgentID); | ||
1264 | } | ||
1265 | } | ||
1266 | |||
1267 | protected void LogoutHandler(IClientAPI client) | ||
1268 | { | ||
1269 | client.SendLogoutPacket(); | ||
1270 | if (client.IsActive) | ||
1271 | RemoveClient(((LLClientView)client).UDPClient); | ||
1272 | } | ||
1273 | } | ||
1274 | } | ||