OpenSim
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros
OpenSimUDPBase.cs
Go to the documentation of this file.
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 {
41  public abstract class OpenSimUDPBase
42  {
43  private static readonly ILog m_log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
44 
49  public abstract void PacketReceived(UDPPacketBuffer buffer);
50 
52  protected int m_udpPort;
53 
55  protected IPAddress m_localBindAddress;
56 
58  private Socket m_udpSocket;
59 
61  private bool m_asyncPacketHandling;
62 
66  public bool UsePools { get; protected set; }
67 
71  protected OpenSim.Framework.Pool<UDPPacketBuffer> Pool { get; private set; }
72 
74  public bool IsRunningInbound { get; private set; }
75 
78  public bool IsRunningOutbound { get; private set; }
79 
83  public int UdpReceives { get; private set; }
84 
88  public int UdpSends { get; private set; }
89 
93  private readonly static int s_receiveTimeSamples = 500;
94 
98  private int m_currentReceiveTimeSamples;
99 
103  private int m_receiveTicksInCurrentSamplePeriod;
104 
108  public float AverageReceiveTicksForLastSamplePeriod { get; private set; }
109 
110  #region PacketDropDebugging
111  private Random m_dropRandomGenerator = new Random();
116 
123  private double m_dropProbability = 0.0030;
124  private double m_dropLengthProbability = 0.15;
125  private bool m_dropState = false;
126 
133  private int m_dropLastTick = 0;
134  private int m_dropResetTicks = 500;
135 
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 
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 
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_udpSocket = new Socket(
211  AddressFamily.InterNetwork,
212  SocketType.Dgram,
213  ProtocolType.Udp);
214 
215  // OpenSim may need this but in AVN, this messes up automated
216  // sim restarts badly
217  //m_udpSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, false);
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 
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  {
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 }
virtual void StartOutbound()
Start outbound UDP packet handling.
bool UsePools
Are we to use object pool(s) to reduce memory churn when receiving data?
float AverageReceiveTicksForLastSamplePeriod
The average time taken for each require receive in the last sample.
int UdpReceives
Number of UDP receives.
abstract void PacketReceived(UDPPacketBuffer buffer)
This method is called when an incoming packet is received
OpenSim.Framework.Pool< UDPPacketBuffer > Pool
Pool to use for handling data. May be null if UsePools = false;
void AsyncBeginSend(UDPPacketBuffer buf)
IPAddress m_localBindAddress
Local IP address to bind to in server mode
OpenSimUDPBase(IPAddress bindAddress, int port)
Default constructor
virtual void StartInbound(int recvBufferSize, bool asyncPacketHandling)
Start inbound UDP packet handling.
int UdpSends
Number of UDP sends
bool IsRunningOutbound
Returns true if the server is currently sending outbound packets, otherwise false
int m_udpPort
UDP port to bind to in server mode
bool IsRunningInbound
Returns true if the server is currently listening for inbound packets, otherwise false