aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/OpenSim/Region/ClientStack/Linden/UDP/OpenSimUDPBase.cs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--OpenSim/Region/ClientStack/Linden/UDP/OpenSimUDPBase.cs504
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
28using System;
29using System.Net;
30using System.Net.Sockets;
31using System.Threading;
32using log4net;
33using OpenSim.Framework;
34using OpenSim.Framework.Monitoring;
35
36namespace 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