diff options
Diffstat (limited to '')
-rw-r--r-- | OpenSim/Region/ClientStack/LindenUDP/LLUDPClient.cs | 370 |
1 files changed, 370 insertions, 0 deletions
diff --git a/OpenSim/Region/ClientStack/LindenUDP/LLUDPClient.cs b/OpenSim/Region/ClientStack/LindenUDP/LLUDPClient.cs new file mode 100644 index 0000000..ad01135 --- /dev/null +++ b/OpenSim/Region/ClientStack/LindenUDP/LLUDPClient.cs | |||
@@ -0,0 +1,370 @@ | |||
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.Net; | ||
31 | using OpenSim.Framework; | ||
32 | using OpenMetaverse; | ||
33 | |||
34 | namespace OpenSim.Region.ClientStack.LindenUDP | ||
35 | { | ||
36 | public delegate void QueueEmpty(ThrottleOutPacketType category); | ||
37 | |||
38 | public class LLUDPClient | ||
39 | { | ||
40 | /// <summary>The number of packet categories to throttle on. If a throttle category is added | ||
41 | /// or removed, this number must also change</summary> | ||
42 | const int THROTTLE_CATEGORY_COUNT = 7; | ||
43 | |||
44 | public event QueueEmpty OnQueueEmpty; | ||
45 | |||
46 | /// <summary>AgentID for this client</summary> | ||
47 | public readonly UUID AgentID; | ||
48 | /// <summary>The remote address of the connected client</summary> | ||
49 | public readonly IPEndPoint RemoteEndPoint; | ||
50 | /// <summary>Circuit code that this client is connected on</summary> | ||
51 | public readonly uint CircuitCode; | ||
52 | /// <summary>Sequence numbers of packets we've received (for duplicate checking)</summary> | ||
53 | public readonly IncomingPacketHistoryCollection PacketArchive = new IncomingPacketHistoryCollection(200); | ||
54 | /// <summary>Packets we have sent that need to be ACKed by the client</summary> | ||
55 | public readonly UnackedPacketCollection NeedAcks = new UnackedPacketCollection(); | ||
56 | /// <summary>ACKs that are queued up, waiting to be sent to the client</summary> | ||
57 | public readonly LocklessQueue<uint> PendingAcks = new LocklessQueue<uint>(); | ||
58 | |||
59 | /// <summary>Reference to the IClientAPI for this client</summary> | ||
60 | public LLClientView ClientAPI; | ||
61 | /// <summary>Current packet sequence number</summary> | ||
62 | public int CurrentSequence; | ||
63 | /// <summary>Current ping sequence number</summary> | ||
64 | public byte CurrentPingSequence; | ||
65 | /// <summary>True when this connection is alive, otherwise false</summary> | ||
66 | public bool IsConnected = true; | ||
67 | /// <summary>True when this connection is paused, otherwise false</summary> | ||
68 | public bool IsPaused = true; | ||
69 | /// <summary>Environment.TickCount when the last packet was received for this client</summary> | ||
70 | public int TickLastPacketReceived; | ||
71 | |||
72 | /// <summary>Timer granularity. This is set to the measured resolution of Environment.TickCount</summary> | ||
73 | public readonly float G; | ||
74 | /// <summary>Smoothed round-trip time. A smoothed average of the round-trip time for sending a | ||
75 | /// reliable packet to the client and receiving an ACK</summary> | ||
76 | public float SRTT; | ||
77 | /// <summary>Round-trip time variance. Measures the consistency of round-trip times</summary> | ||
78 | public float RTTVAR; | ||
79 | /// <summary>Retransmission timeout. Packets that have not been acknowledged in this number of | ||
80 | /// milliseconds or longer will be resent</summary> | ||
81 | /// <remarks>Calculated from <seealso cref="SRTT"/> and <seealso cref="RTTVAR"/> using the | ||
82 | /// guidelines in RFC 2988</remarks> | ||
83 | public int RTO; | ||
84 | /// <summary>Number of bytes received since the last acknowledgement was sent out. This is used | ||
85 | /// to loosely follow the TCP delayed ACK algorithm in RFC 1122 (4.2.3.2)</summary> | ||
86 | public int BytesSinceLastACK; | ||
87 | |||
88 | /// <summary>Throttle bucket for this agent's connection</summary> | ||
89 | private readonly TokenBucket throttle; | ||
90 | /// <summary>Throttle buckets for each packet category</summary> | ||
91 | private readonly TokenBucket[] throttleCategories; | ||
92 | /// <summary>Throttle rate defaults and limits</summary> | ||
93 | private readonly ThrottleRates defaultThrottleRates; | ||
94 | /// <summary>Outgoing queues for throttled packets</summary> | ||
95 | private readonly LocklessQueue<OutgoingPacket>[] packetOutboxes = new LocklessQueue<OutgoingPacket>[THROTTLE_CATEGORY_COUNT]; | ||
96 | /// <summary>A container that can hold one packet for each outbox, used to store | ||
97 | /// dequeued packets that are being held for throttling</summary> | ||
98 | private readonly OutgoingPacket[] nextPackets = new OutgoingPacket[THROTTLE_CATEGORY_COUNT]; | ||
99 | /// <summary>An optimization to store the length of dequeued packets being held | ||
100 | /// for throttling. This avoids expensive calls to Packet.Length</summary> | ||
101 | private readonly int[] nextPacketLengths = new int[THROTTLE_CATEGORY_COUNT]; | ||
102 | /// <summary>A reference to the LLUDPServer that is managing this client</summary> | ||
103 | private readonly LLUDPServer udpServer; | ||
104 | |||
105 | public LLUDPClient(LLUDPServer server, ThrottleRates rates, TokenBucket parentThrottle, uint circuitCode, UUID agentID, IPEndPoint remoteEndPoint) | ||
106 | { | ||
107 | udpServer = server; | ||
108 | AgentID = agentID; | ||
109 | RemoteEndPoint = remoteEndPoint; | ||
110 | CircuitCode = circuitCode; | ||
111 | defaultThrottleRates = rates; | ||
112 | |||
113 | for (int i = 0; i < THROTTLE_CATEGORY_COUNT; i++) | ||
114 | packetOutboxes[i] = new LocklessQueue<OutgoingPacket>(); | ||
115 | |||
116 | throttle = new TokenBucket(parentThrottle, 0, 0); | ||
117 | throttleCategories = new TokenBucket[THROTTLE_CATEGORY_COUNT]; | ||
118 | throttleCategories[(int)ThrottleOutPacketType.Resend] = new TokenBucket(throttle, rates.ResendLimit, rates.Resend); | ||
119 | throttleCategories[(int)ThrottleOutPacketType.Land] = new TokenBucket(throttle, rates.LandLimit, rates.Land); | ||
120 | throttleCategories[(int)ThrottleOutPacketType.Wind] = new TokenBucket(throttle, rates.WindLimit, rates.Wind); | ||
121 | throttleCategories[(int)ThrottleOutPacketType.Cloud] = new TokenBucket(throttle, rates.CloudLimit, rates.Cloud); | ||
122 | throttleCategories[(int)ThrottleOutPacketType.Task] = new TokenBucket(throttle, rates.TaskLimit, rates.Task); | ||
123 | throttleCategories[(int)ThrottleOutPacketType.Texture] = new TokenBucket(throttle, rates.TextureLimit, rates.Texture); | ||
124 | throttleCategories[(int)ThrottleOutPacketType.Asset] = new TokenBucket(throttle, rates.AssetLimit, rates.Asset); | ||
125 | |||
126 | // Set the granularity variable used for retransmission calculations to | ||
127 | // the measured resolution of Environment.TickCount | ||
128 | G = server.TickCountResolution; | ||
129 | |||
130 | // Default the retransmission timeout to three seconds | ||
131 | RTO = 3000; | ||
132 | } | ||
133 | |||
134 | public void Shutdown() | ||
135 | { | ||
136 | IsConnected = false; | ||
137 | } | ||
138 | |||
139 | public ClientInfo GetClientInfo() | ||
140 | { | ||
141 | // TODO: This data structure is wrong in so many ways | ||
142 | ClientInfo info = new ClientInfo(); | ||
143 | info.pendingAcks = new Dictionary<uint, uint>(); | ||
144 | info.needAck = new Dictionary<uint, byte[]>(); | ||
145 | |||
146 | info.resendThrottle = throttleCategories[(int)ThrottleOutPacketType.Resend].DripRate; | ||
147 | info.landThrottle = throttleCategories[(int)ThrottleOutPacketType.Land].DripRate; | ||
148 | info.windThrottle = throttleCategories[(int)ThrottleOutPacketType.Wind].DripRate; | ||
149 | info.cloudThrottle = throttleCategories[(int)ThrottleOutPacketType.Cloud].DripRate; | ||
150 | info.taskThrottle = throttleCategories[(int)ThrottleOutPacketType.Task].DripRate; | ||
151 | info.assetThrottle = throttleCategories[(int)ThrottleOutPacketType.Asset].DripRate; | ||
152 | info.textureThrottle = throttleCategories[(int)ThrottleOutPacketType.Texture].DripRate; | ||
153 | info.totalThrottle = info.resendThrottle + info.landThrottle + info.windThrottle + info.cloudThrottle + | ||
154 | info.taskThrottle + info.assetThrottle + info.textureThrottle; | ||
155 | |||
156 | return info; | ||
157 | } | ||
158 | |||
159 | public void SetClientInfo(ClientInfo info) | ||
160 | { | ||
161 | } | ||
162 | |||
163 | public string GetStats() | ||
164 | { | ||
165 | return string.Format("{0,7} {1,7} {2,7} {3,7} {4,7} {5,7} {6,7} {7,7} {8,7} {9,7}", | ||
166 | 0, | ||
167 | 0, | ||
168 | 0, | ||
169 | 0, | ||
170 | 0, | ||
171 | 0, | ||
172 | 0, | ||
173 | 0, | ||
174 | 0, | ||
175 | 0); | ||
176 | } | ||
177 | |||
178 | public void SetThrottles(byte[] throttleData) | ||
179 | { | ||
180 | byte[] adjData; | ||
181 | int pos = 0; | ||
182 | |||
183 | if (!BitConverter.IsLittleEndian) | ||
184 | { | ||
185 | byte[] newData = new byte[7 * 4]; | ||
186 | Buffer.BlockCopy(throttleData, 0, newData, 0, 7 * 4); | ||
187 | |||
188 | for (int i = 0; i < 7; i++) | ||
189 | Array.Reverse(newData, i * 4, 4); | ||
190 | |||
191 | adjData = newData; | ||
192 | } | ||
193 | else | ||
194 | { | ||
195 | adjData = throttleData; | ||
196 | } | ||
197 | |||
198 | int resend = (int)(BitConverter.ToSingle(adjData, pos) * 0.125f); pos += 4; | ||
199 | int land = (int)(BitConverter.ToSingle(adjData, pos) * 0.125f); pos += 4; | ||
200 | int wind = (int)(BitConverter.ToSingle(adjData, pos) * 0.125f); pos += 4; | ||
201 | int cloud = (int)(BitConverter.ToSingle(adjData, pos) * 0.125f); pos += 4; | ||
202 | int task = (int)(BitConverter.ToSingle(adjData, pos) * 0.125f); pos += 4; | ||
203 | int texture = (int)(BitConverter.ToSingle(adjData, pos) * 0.125f); pos += 4; | ||
204 | int asset = (int)(BitConverter.ToSingle(adjData, pos) * 0.125f); | ||
205 | |||
206 | resend = (resend <= defaultThrottleRates.ResendLimit) ? resend : defaultThrottleRates.ResendLimit; | ||
207 | land = (land <= defaultThrottleRates.LandLimit) ? land : defaultThrottleRates.LandLimit; | ||
208 | wind = (wind <= defaultThrottleRates.WindLimit) ? wind : defaultThrottleRates.WindLimit; | ||
209 | cloud = (cloud <= defaultThrottleRates.CloudLimit) ? cloud : defaultThrottleRates.CloudLimit; | ||
210 | task = (task <= defaultThrottleRates.TaskLimit) ? task : defaultThrottleRates.TaskLimit; | ||
211 | texture = (texture <= defaultThrottleRates.TextureLimit) ? texture : defaultThrottleRates.TextureLimit; | ||
212 | asset = (asset <= defaultThrottleRates.AssetLimit) ? asset : defaultThrottleRates.AssetLimit; | ||
213 | |||
214 | SetThrottle(ThrottleOutPacketType.Resend, resend); | ||
215 | SetThrottle(ThrottleOutPacketType.Land, land); | ||
216 | SetThrottle(ThrottleOutPacketType.Wind, wind); | ||
217 | SetThrottle(ThrottleOutPacketType.Cloud, cloud); | ||
218 | SetThrottle(ThrottleOutPacketType.Task, task); | ||
219 | SetThrottle(ThrottleOutPacketType.Texture, texture); | ||
220 | SetThrottle(ThrottleOutPacketType.Asset, asset); | ||
221 | } | ||
222 | |||
223 | public byte[] GetThrottlesPacked() | ||
224 | { | ||
225 | byte[] data = new byte[7 * 4]; | ||
226 | int i = 0; | ||
227 | |||
228 | Buffer.BlockCopy(Utils.FloatToBytes((float)throttleCategories[(int)ThrottleOutPacketType.Resend].DripRate), 0, data, i, 4); i += 4; | ||
229 | Buffer.BlockCopy(Utils.FloatToBytes((float)throttleCategories[(int)ThrottleOutPacketType.Land].DripRate), 0, data, i, 4); i += 4; | ||
230 | Buffer.BlockCopy(Utils.FloatToBytes((float)throttleCategories[(int)ThrottleOutPacketType.Wind].DripRate), 0, data, i, 4); i += 4; | ||
231 | Buffer.BlockCopy(Utils.FloatToBytes((float)throttleCategories[(int)ThrottleOutPacketType.Cloud].DripRate), 0, data, i, 4); i += 4; | ||
232 | Buffer.BlockCopy(Utils.FloatToBytes((float)throttleCategories[(int)ThrottleOutPacketType.Task].DripRate), 0, data, i, 4); i += 4; | ||
233 | Buffer.BlockCopy(Utils.FloatToBytes((float)throttleCategories[(int)ThrottleOutPacketType.Texture].DripRate), 0, data, i, 4); i += 4; | ||
234 | Buffer.BlockCopy(Utils.FloatToBytes((float)throttleCategories[(int)ThrottleOutPacketType.Asset].DripRate), 0, data, i, 4); i += 4; | ||
235 | |||
236 | return data; | ||
237 | } | ||
238 | |||
239 | public void SetThrottle(ThrottleOutPacketType category, int rate) | ||
240 | { | ||
241 | int i = (int)category; | ||
242 | if (i >= 0 && i < throttleCategories.Length) | ||
243 | { | ||
244 | TokenBucket bucket = throttleCategories[(int)category]; | ||
245 | bucket.MaxBurst = rate; | ||
246 | bucket.DripRate = rate; | ||
247 | } | ||
248 | } | ||
249 | |||
250 | public bool EnqueueOutgoing(OutgoingPacket packet) | ||
251 | { | ||
252 | int category = (int)packet.Category; | ||
253 | |||
254 | if (category >= 0 && category < packetOutboxes.Length) | ||
255 | { | ||
256 | LocklessQueue<OutgoingPacket> queue = packetOutboxes[category]; | ||
257 | TokenBucket bucket = throttleCategories[category]; | ||
258 | |||
259 | if (throttleCategories[category].RemoveTokens(packet.Buffer.DataLength)) | ||
260 | { | ||
261 | // Enough tokens were removed from the bucket, the packet will not be queued | ||
262 | return false; | ||
263 | } | ||
264 | else | ||
265 | { | ||
266 | // Not enough tokens in the bucket, queue this packet | ||
267 | queue.Enqueue(packet); | ||
268 | return true; | ||
269 | } | ||
270 | } | ||
271 | else | ||
272 | { | ||
273 | // We don't have a token bucket for this category, so it will not be queued | ||
274 | return false; | ||
275 | } | ||
276 | } | ||
277 | |||
278 | /// <summary> | ||
279 | /// Loops through all of the packet queues for this client and tries to send | ||
280 | /// any outgoing packets, obeying the throttling bucket limits | ||
281 | /// </summary> | ||
282 | /// <remarks>This function is only called from a synchronous loop in the | ||
283 | /// UDPServer so we don't need to bother making this thread safe</remarks> | ||
284 | /// <returns>True if any packets were sent, otherwise false</returns> | ||
285 | public bool DequeueOutgoing() | ||
286 | { | ||
287 | OutgoingPacket packet; | ||
288 | LocklessQueue<OutgoingPacket> queue; | ||
289 | TokenBucket bucket; | ||
290 | bool packetSent = false; | ||
291 | |||
292 | for (int i = 0; i < THROTTLE_CATEGORY_COUNT; i++) | ||
293 | { | ||
294 | bucket = throttleCategories[i]; | ||
295 | |||
296 | if (nextPackets[i] != null) | ||
297 | { | ||
298 | // This bucket was empty the last time we tried to send a packet, | ||
299 | // leaving a dequeued packet still waiting to be sent out. Try to | ||
300 | // send it again | ||
301 | if (bucket.RemoveTokens(nextPacketLengths[i])) | ||
302 | { | ||
303 | // Send the packet | ||
304 | udpServer.SendPacketFinal(nextPackets[i]); | ||
305 | nextPackets[i] = null; | ||
306 | packetSent = true; | ||
307 | } | ||
308 | } | ||
309 | else | ||
310 | { | ||
311 | // No dequeued packet waiting to be sent, try to pull one off | ||
312 | // this queue | ||
313 | queue = packetOutboxes[i]; | ||
314 | if (queue.Dequeue(out packet)) | ||
315 | { | ||
316 | // A packet was pulled off the queue. See if we have | ||
317 | // enough tokens in the bucket to send it out | ||
318 | if (bucket.RemoveTokens(packet.Buffer.DataLength)) | ||
319 | { | ||
320 | // Send the packet | ||
321 | udpServer.SendPacketFinal(packet); | ||
322 | packetSent = true; | ||
323 | } | ||
324 | else | ||
325 | { | ||
326 | // Save the dequeued packet and the length calculation for | ||
327 | // the next iteration | ||
328 | nextPackets[i] = packet; | ||
329 | nextPacketLengths[i] = packet.Buffer.DataLength; | ||
330 | } | ||
331 | } | ||
332 | else | ||
333 | { | ||
334 | // No packets in this queue. Fire the queue empty callback | ||
335 | QueueEmpty callback = OnQueueEmpty; | ||
336 | if (callback != null) | ||
337 | callback((ThrottleOutPacketType)i); | ||
338 | } | ||
339 | } | ||
340 | } | ||
341 | |||
342 | return packetSent; | ||
343 | } | ||
344 | |||
345 | public void UpdateRoundTrip(float r) | ||
346 | { | ||
347 | const float ALPHA = 0.125f; | ||
348 | const float BETA = 0.25f; | ||
349 | const float K = 4.0f; | ||
350 | |||
351 | if (RTTVAR == 0.0f) | ||
352 | { | ||
353 | // First RTT measurement | ||
354 | SRTT = r; | ||
355 | RTTVAR = r * 0.5f; | ||
356 | } | ||
357 | else | ||
358 | { | ||
359 | // Subsequence RTT measurement | ||
360 | RTTVAR = (1.0f - BETA) * RTTVAR + BETA * Math.Abs(SRTT - r); | ||
361 | SRTT = (1.0f - ALPHA) * SRTT + ALPHA * r; | ||
362 | } | ||
363 | |||
364 | // Always round retransmission timeout up to two seconds | ||
365 | RTO = Math.Max(2000, (int)(SRTT + Math.Max(G, K * RTTVAR))); | ||
366 | //Logger.Debug("Setting agent " + this.Agent.FullName + "'s RTO to " + RTO + "ms with an RTTVAR of " + | ||
367 | // RTTVAR + " based on new RTT of " + r + "ms"); | ||
368 | } | ||
369 | } | ||
370 | } | ||