diff options
Diffstat (limited to '')
-rw-r--r-- | OpenSim/Region/ClientStack/Linden/UDP/OpenSimUDPBase.cs | 504 |
1 files changed, 504 insertions, 0 deletions
diff --git a/OpenSim/Region/ClientStack/Linden/UDP/OpenSimUDPBase.cs b/OpenSim/Region/ClientStack/Linden/UDP/OpenSimUDPBase.cs new file mode 100644 index 0000000..f62dc15 --- /dev/null +++ b/OpenSim/Region/ClientStack/Linden/UDP/OpenSimUDPBase.cs | |||
@@ -0,0 +1,504 @@ | |||
1 | /* | ||
2 | * Copyright (c) 2006, Clutch, Inc. | ||
3 | * Original Author: Jeff Cesnik | ||
4 | * All rights reserved. | ||
5 | * | ||
6 | * - Redistribution and use in source and binary forms, with or without | ||
7 | * modification, are permitted provided that the following conditions are met: | ||
8 | * | ||
9 | * - Redistributions of source code must retain the above copyright notice, this | ||
10 | * list of conditions and the following disclaimer. | ||
11 | * - Neither the name of the openmetaverse.org nor the names | ||
12 | * of its contributors may be used to endorse or promote products derived from | ||
13 | * this software without specific prior written permission. | ||
14 | * | ||
15 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||
16 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||
17 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | ||
18 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE | ||
19 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | ||
20 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | ||
21 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | ||
22 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | ||
23 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | ||
24 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | ||
25 | * POSSIBILITY OF SUCH DAMAGE. | ||
26 | */ | ||
27 | |||
28 | using System; | ||
29 | using System.Net; | ||
30 | using System.Net.Sockets; | ||
31 | using System.Threading; | ||
32 | using log4net; | ||
33 | using OpenSim.Framework; | ||
34 | using OpenSim.Framework.Monitoring; | ||
35 | |||
36 | namespace OpenMetaverse | ||
37 | { | ||
38 | /// <summary> | ||
39 | /// Base UDP server | ||
40 | /// </summary> | ||
41 | public abstract class OpenSimUDPBase | ||
42 | { | ||
43 | private static readonly ILog m_log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); | ||
44 | |||
45 | /// <summary> | ||
46 | /// This method is called when an incoming packet is received | ||
47 | /// </summary> | ||
48 | /// <param name="buffer">Incoming packet buffer</param> | ||
49 | public abstract void PacketReceived(UDPPacketBuffer buffer); | ||
50 | |||
51 | /// <summary>UDP port to bind to in server mode</summary> | ||
52 | protected int m_udpPort; | ||
53 | |||
54 | /// <summary>Local IP address to bind to in server mode</summary> | ||
55 | protected IPAddress m_localBindAddress; | ||
56 | |||
57 | /// <summary>UDP socket, used in either client or server mode</summary> | ||
58 | private Socket m_udpSocket; | ||
59 | |||
60 | /// <summary>Flag to process packets asynchronously or synchronously</summary> | ||
61 | private bool m_asyncPacketHandling; | ||
62 | |||
63 | /// <summary> | ||
64 | /// Are we to use object pool(s) to reduce memory churn when receiving data? | ||
65 | /// </summary> | ||
66 | public bool UsePools { get; protected set; } | ||
67 | |||
68 | /// <summary> | ||
69 | /// Pool to use for handling data. May be null if UsePools = false; | ||
70 | /// </summary> | ||
71 | protected OpenSim.Framework.Pool<UDPPacketBuffer> Pool { get; private set; } | ||
72 | |||
73 | /// <summary>Returns true if the server is currently listening for inbound packets, otherwise false</summary> | ||
74 | public bool IsRunningInbound { get; private set; } | ||
75 | |||
76 | /// <summary>Returns true if the server is currently sending outbound packets, otherwise false</summary> | ||
77 | /// <remarks>If IsRunningOut = false, then any request to send a packet is simply dropped.</remarks> | ||
78 | public bool IsRunningOutbound { get; private set; } | ||
79 | |||
80 | /// <summary> | ||
81 | /// Number of UDP receives. | ||
82 | /// </summary> | ||
83 | public int UdpReceives { get; private set; } | ||
84 | |||
85 | /// <summary> | ||
86 | /// Number of UDP sends | ||
87 | /// </summary> | ||
88 | public int UdpSends { get; private set; } | ||
89 | |||
90 | /// <summary> | ||
91 | /// Number of receives over which to establish a receive time average. | ||
92 | /// </summary> | ||
93 | private readonly static int s_receiveTimeSamples = 500; | ||
94 | |||
95 | /// <summary> | ||
96 | /// Current number of samples taken to establish a receive time average. | ||
97 | /// </summary> | ||
98 | private int m_currentReceiveTimeSamples; | ||
99 | |||
100 | /// <summary> | ||
101 | /// Cumulative receive time for the sample so far. | ||
102 | /// </summary> | ||
103 | private int m_receiveTicksInCurrentSamplePeriod; | ||
104 | |||
105 | /// <summary> | ||
106 | /// The average time taken for each require receive in the last sample. | ||
107 | /// </summary> | ||
108 | public float AverageReceiveTicksForLastSamplePeriod { get; private set; } | ||
109 | |||
110 | #region PacketDropDebugging | ||
111 | /// <summary> | ||
112 | /// For debugging purposes only... random number generator for dropping | ||
113 | /// outbound packets. | ||
114 | /// </summary> | ||
115 | private Random m_dropRandomGenerator = new Random(); | ||
116 | |||
117 | /// <summary> | ||
118 | /// For debugging purposes only... parameters for a simplified | ||
119 | /// model of packet loss with bursts, overall drop rate should | ||
120 | /// be roughly 1 - m_dropLengthProbability / (m_dropProbabiliy + m_dropLengthProbability) | ||
121 | /// which is about 1% for parameters 0.0015 and 0.15 | ||
122 | /// </summary> | ||
123 | private double m_dropProbability = 0.0030; | ||
124 | private double m_dropLengthProbability = 0.15; | ||
125 | private bool m_dropState = false; | ||
126 | |||
127 | /// <summary> | ||
128 | /// For debugging purposes only... parameters to control the time | ||
129 | /// duration over which packet loss bursts can occur, if no packets | ||
130 | /// have been sent for m_dropResetTicks milliseconds, then reset the | ||
131 | /// state of the packet dropper to its default. | ||
132 | /// </summary> | ||
133 | private int m_dropLastTick = 0; | ||
134 | private int m_dropResetTicks = 500; | ||
135 | |||
136 | /// <summary> | ||
137 | /// Debugging code used to simulate dropped packets with bursts | ||
138 | /// </summary> | ||
139 | private bool DropOutgoingPacket() | ||
140 | { | ||
141 | double rnum = m_dropRandomGenerator.NextDouble(); | ||
142 | |||
143 | // if the connection has been idle for awhile (more than m_dropResetTicks) then | ||
144 | // reset the state to the default state, don't continue a burst | ||
145 | int curtick = Util.EnvironmentTickCount(); | ||
146 | if (Util.EnvironmentTickCountSubtract(curtick, m_dropLastTick) > m_dropResetTicks) | ||
147 | m_dropState = false; | ||
148 | |||
149 | m_dropLastTick = curtick; | ||
150 | |||
151 | // if we are dropping packets, then the probability of dropping | ||
152 | // this packet is the probability that we stay in the burst | ||
153 | if (m_dropState) | ||
154 | { | ||
155 | m_dropState = (rnum < (1.0 - m_dropLengthProbability)) ? true : false; | ||
156 | } | ||
157 | else | ||
158 | { | ||
159 | m_dropState = (rnum < m_dropProbability) ? true : false; | ||
160 | } | ||
161 | |||
162 | return m_dropState; | ||
163 | } | ||
164 | #endregion PacketDropDebugging | ||
165 | |||
166 | /// <summary> | ||
167 | /// Default constructor | ||
168 | /// </summary> | ||
169 | /// <param name="bindAddress">Local IP address to bind the server to</param> | ||
170 | /// <param name="port">Port to listening for incoming UDP packets on</param> | ||
171 | /// /// <param name="usePool">Are we to use an object pool to get objects for handing inbound data?</param> | ||
172 | public OpenSimUDPBase(IPAddress bindAddress, int port) | ||
173 | { | ||
174 | m_localBindAddress = bindAddress; | ||
175 | m_udpPort = port; | ||
176 | |||
177 | // for debugging purposes only, initializes the random number generator | ||
178 | // used for simulating packet loss | ||
179 | // m_dropRandomGenerator = new Random(); | ||
180 | } | ||
181 | |||
182 | /// <summary> | ||
183 | /// Start inbound UDP packet handling. | ||
184 | /// </summary> | ||
185 | /// <param name="recvBufferSize">The size of the receive buffer for | ||
186 | /// the UDP socket. This value is passed up to the operating system | ||
187 | /// and used in the system networking stack. Use zero to leave this | ||
188 | /// value as the default</param> | ||
189 | /// <param name="asyncPacketHandling">Set this to true to start | ||
190 | /// receiving more packets while current packet handler callbacks are | ||
191 | /// still running. Setting this to false will complete each packet | ||
192 | /// callback before the next packet is processed</param> | ||
193 | /// <remarks>This method will attempt to set the SIO_UDP_CONNRESET flag | ||
194 | /// on the socket to get newer versions of Windows to behave in a sane | ||
195 | /// manner (not throwing an exception when the remote side resets the | ||
196 | /// connection). This call is ignored on Mono where the flag is not | ||
197 | /// necessary</remarks> | ||
198 | public virtual void StartInbound(int recvBufferSize, bool asyncPacketHandling) | ||
199 | { | ||
200 | m_asyncPacketHandling = asyncPacketHandling; | ||
201 | |||
202 | if (!IsRunningInbound) | ||
203 | { | ||
204 | m_log.DebugFormat("[UDPBASE]: Starting inbound UDP loop"); | ||
205 | |||
206 | const int SIO_UDP_CONNRESET = -1744830452; | ||
207 | |||
208 | IPEndPoint ipep = new IPEndPoint(m_localBindAddress, m_udpPort); | ||
209 | |||
210 | m_log.DebugFormat( | ||
211 | "[UDPBASE]: Binding UDP listener using internal IP address config {0}:{1}", | ||
212 | ipep.Address, ipep.Port); | ||
213 | |||
214 | m_udpSocket = new Socket( | ||
215 | AddressFamily.InterNetwork, | ||
216 | SocketType.Dgram, | ||
217 | ProtocolType.Udp); | ||
218 | |||
219 | try | ||
220 | { | ||
221 | if (m_udpSocket.Ttl < 128) | ||
222 | { | ||
223 | m_udpSocket.Ttl = 128; | ||
224 | } | ||
225 | } | ||
226 | catch (SocketException) | ||
227 | { | ||
228 | m_log.Debug("[UDPBASE]: Failed to increase default TTL"); | ||
229 | } | ||
230 | try | ||
231 | { | ||
232 | // This udp socket flag is not supported under mono, | ||
233 | // so we'll catch the exception and continue | ||
234 | m_udpSocket.IOControl(SIO_UDP_CONNRESET, new byte[] { 0 }, null); | ||
235 | m_log.Debug("[UDPBASE]: SIO_UDP_CONNRESET flag set"); | ||
236 | } | ||
237 | catch (SocketException) | ||
238 | { | ||
239 | m_log.Debug("[UDPBASE]: SIO_UDP_CONNRESET flag not supported on this platform, ignoring"); | ||
240 | } | ||
241 | |||
242 | // On at least Mono 3.2.8, multiple UDP sockets can bind to the same port by default. At the moment | ||
243 | // we never want two regions to listen on the same port as they cannot demultiplex each other's messages, | ||
244 | // leading to a confusing bug. | ||
245 | // By default, Windows does not allow two sockets to bind to the same port. | ||
246 | m_udpSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, false); | ||
247 | |||
248 | if (recvBufferSize != 0) | ||
249 | m_udpSocket.ReceiveBufferSize = recvBufferSize; | ||
250 | |||
251 | m_udpSocket.Bind(ipep); | ||
252 | |||
253 | IsRunningInbound = true; | ||
254 | |||
255 | // kick off an async receive. The Start() method will return, the | ||
256 | // actual receives will occur asynchronously and will be caught in | ||
257 | // AsyncEndRecieve(). | ||
258 | AsyncBeginReceive(); | ||
259 | } | ||
260 | } | ||
261 | |||
262 | /// <summary> | ||
263 | /// Start outbound UDP packet handling. | ||
264 | /// </summary> | ||
265 | public virtual void StartOutbound() | ||
266 | { | ||
267 | m_log.DebugFormat("[UDPBASE]: Starting outbound UDP loop"); | ||
268 | |||
269 | IsRunningOutbound = true; | ||
270 | } | ||
271 | |||
272 | public virtual void StopInbound() | ||
273 | { | ||
274 | if (IsRunningInbound) | ||
275 | { | ||
276 | m_log.DebugFormat("[UDPBASE]: Stopping inbound UDP loop"); | ||
277 | |||
278 | IsRunningInbound = false; | ||
279 | m_udpSocket.Close(); | ||
280 | } | ||
281 | } | ||
282 | |||
283 | public virtual void StopOutbound() | ||
284 | { | ||
285 | m_log.DebugFormat("[UDPBASE]: Stopping outbound UDP loop"); | ||
286 | |||
287 | IsRunningOutbound = false; | ||
288 | } | ||
289 | |||
290 | public virtual bool EnablePools() | ||
291 | { | ||
292 | if (!UsePools) | ||
293 | { | ||
294 | Pool = new Pool<UDPPacketBuffer>(() => new UDPPacketBuffer(), 500); | ||
295 | |||
296 | UsePools = true; | ||
297 | |||
298 | return true; | ||
299 | } | ||
300 | |||
301 | return false; | ||
302 | } | ||
303 | |||
304 | public virtual bool DisablePools() | ||
305 | { | ||
306 | if (UsePools) | ||
307 | { | ||
308 | UsePools = false; | ||
309 | |||
310 | // We won't null out the pool to avoid a race condition with code that may be in the middle of using it. | ||
311 | |||
312 | return true; | ||
313 | } | ||
314 | |||
315 | return false; | ||
316 | } | ||
317 | |||
318 | private void AsyncBeginReceive() | ||
319 | { | ||
320 | UDPPacketBuffer buf; | ||
321 | |||
322 | // FIXME: Disabled for now as this causes issues with reused packet objects interfering with each other | ||
323 | // on Windows with m_asyncPacketHandling = true, though this has not been seen on Linux. | ||
324 | // Possibly some unexpected issue with fetching UDP data concurrently with multiple threads. Requires more investigation. | ||
325 | // if (UsePools) | ||
326 | // buf = Pool.GetObject(); | ||
327 | // else | ||
328 | buf = new UDPPacketBuffer(); | ||
329 | |||
330 | if (IsRunningInbound) | ||
331 | { | ||
332 | try | ||
333 | { | ||
334 | // kick off an async read | ||
335 | m_udpSocket.BeginReceiveFrom( | ||
336 | //wrappedBuffer.Instance.Data, | ||
337 | buf.Data, | ||
338 | 0, | ||
339 | UDPPacketBuffer.BUFFER_SIZE, | ||
340 | SocketFlags.None, | ||
341 | ref buf.RemoteEndPoint, | ||
342 | AsyncEndReceive, | ||
343 | //wrappedBuffer); | ||
344 | buf); | ||
345 | } | ||
346 | catch (SocketException e) | ||
347 | { | ||
348 | if (e.SocketErrorCode == SocketError.ConnectionReset) | ||
349 | { | ||
350 | m_log.Warn("[UDPBASE]: SIO_UDP_CONNRESET was ignored, attempting to salvage the UDP listener on port " + m_udpPort); | ||
351 | bool salvaged = false; | ||
352 | while (!salvaged) | ||
353 | { | ||
354 | try | ||
355 | { | ||
356 | m_udpSocket.BeginReceiveFrom( | ||
357 | //wrappedBuffer.Instance.Data, | ||
358 | buf.Data, | ||
359 | 0, | ||
360 | UDPPacketBuffer.BUFFER_SIZE, | ||
361 | SocketFlags.None, | ||
362 | ref buf.RemoteEndPoint, | ||
363 | AsyncEndReceive, | ||
364 | //wrappedBuffer); | ||
365 | buf); | ||
366 | salvaged = true; | ||
367 | } | ||
368 | catch (SocketException) { } | ||
369 | catch (ObjectDisposedException) { return; } | ||
370 | } | ||
371 | |||
372 | m_log.Warn("[UDPBASE]: Salvaged the UDP listener on port " + m_udpPort); | ||
373 | } | ||
374 | } | ||
375 | catch (ObjectDisposedException e) | ||
376 | { | ||
377 | m_log.Error( | ||
378 | string.Format("[UDPBASE]: Error processing UDP begin receive {0}. Exception ", UdpReceives), e); | ||
379 | } | ||
380 | catch (Exception e) | ||
381 | { | ||
382 | m_log.Error( | ||
383 | string.Format("[UDPBASE]: Error processing UDP begin receive {0}. Exception ", UdpReceives), e); | ||
384 | } | ||
385 | } | ||
386 | } | ||
387 | |||
388 | private void AsyncEndReceive(IAsyncResult iar) | ||
389 | { | ||
390 | // Asynchronous receive operations will complete here through the call | ||
391 | // to AsyncBeginReceive | ||
392 | if (IsRunningInbound) | ||
393 | { | ||
394 | UdpReceives++; | ||
395 | |||
396 | // Asynchronous mode will start another receive before the | ||
397 | // callback for this packet is even fired. Very parallel :-) | ||
398 | if (m_asyncPacketHandling) | ||
399 | AsyncBeginReceive(); | ||
400 | |||
401 | try | ||
402 | { | ||
403 | // get the buffer that was created in AsyncBeginReceive | ||
404 | // this is the received data | ||
405 | UDPPacketBuffer buffer = (UDPPacketBuffer)iar.AsyncState; | ||
406 | |||
407 | int startTick = Util.EnvironmentTickCount(); | ||
408 | |||
409 | // get the length of data actually read from the socket, store it with the | ||
410 | // buffer | ||
411 | buffer.DataLength = m_udpSocket.EndReceiveFrom(iar, ref buffer.RemoteEndPoint); | ||
412 | |||
413 | // call the abstract method PacketReceived(), passing the buffer that | ||
414 | // has just been filled from the socket read. | ||
415 | PacketReceived(buffer); | ||
416 | |||
417 | // If more than one thread can be calling AsyncEndReceive() at once (e.g. if m_asyncPacketHandler) | ||
418 | // then a particular stat may be inaccurate due to a race condition. We won't worry about this | ||
419 | // since this should be rare and won't cause a runtime problem. | ||
420 | if (m_currentReceiveTimeSamples >= s_receiveTimeSamples) | ||
421 | { | ||
422 | AverageReceiveTicksForLastSamplePeriod | ||
423 | = (float)m_receiveTicksInCurrentSamplePeriod / s_receiveTimeSamples; | ||
424 | |||
425 | m_receiveTicksInCurrentSamplePeriod = 0; | ||
426 | m_currentReceiveTimeSamples = 0; | ||
427 | } | ||
428 | else | ||
429 | { | ||
430 | m_receiveTicksInCurrentSamplePeriod += Util.EnvironmentTickCountSubtract(startTick); | ||
431 | m_currentReceiveTimeSamples++; | ||
432 | } | ||
433 | } | ||
434 | catch (SocketException se) | ||
435 | { | ||
436 | m_log.Error( | ||
437 | string.Format( | ||
438 | "[UDPBASE]: Error processing UDP end receive {0}, socket error code {1}. Exception ", | ||
439 | UdpReceives, se.ErrorCode), | ||
440 | se); | ||
441 | } | ||
442 | catch (ObjectDisposedException e) | ||
443 | { | ||
444 | m_log.Error( | ||
445 | string.Format("[UDPBASE]: Error processing UDP end receive {0}. Exception ", UdpReceives), e); | ||
446 | } | ||
447 | catch (Exception e) | ||
448 | { | ||
449 | m_log.Error( | ||
450 | string.Format("[UDPBASE]: Error processing UDP end receive {0}. Exception ", UdpReceives), e); | ||
451 | } | ||
452 | finally | ||
453 | { | ||
454 | // if (UsePools) | ||
455 | // Pool.ReturnObject(buffer); | ||
456 | |||
457 | // Synchronous mode waits until the packet callback completes | ||
458 | // before starting the receive to fetch another packet | ||
459 | if (!m_asyncPacketHandling) | ||
460 | AsyncBeginReceive(); | ||
461 | } | ||
462 | } | ||
463 | } | ||
464 | |||
465 | public void AsyncBeginSend(UDPPacketBuffer buf) | ||
466 | { | ||
467 | // if (IsRunningOutbound) | ||
468 | // { | ||
469 | |||
470 | // This is strictly for debugging purposes to simulate dropped | ||
471 | // packets when testing throttles & retransmission code | ||
472 | // if (DropOutgoingPacket()) | ||
473 | // return; | ||
474 | |||
475 | try | ||
476 | { | ||
477 | m_udpSocket.BeginSendTo( | ||
478 | buf.Data, | ||
479 | 0, | ||
480 | buf.DataLength, | ||
481 | SocketFlags.None, | ||
482 | buf.RemoteEndPoint, | ||
483 | AsyncEndSend, | ||
484 | buf); | ||
485 | } | ||
486 | catch (SocketException) { } | ||
487 | catch (ObjectDisposedException) { } | ||
488 | // } | ||
489 | } | ||
490 | |||
491 | void AsyncEndSend(IAsyncResult result) | ||
492 | { | ||
493 | try | ||
494 | { | ||
495 | // UDPPacketBuffer buf = (UDPPacketBuffer)result.AsyncState; | ||
496 | m_udpSocket.EndSendTo(result); | ||
497 | |||
498 | UdpSends++; | ||
499 | } | ||
500 | catch (SocketException) { } | ||
501 | catch (ObjectDisposedException) { } | ||
502 | } | ||
503 | } | ||
504 | } \ No newline at end of file | ||