* Add an initial complete frame timeout to the WebSocket processor to make it easier to write WebSocket service code that is resistant to Denial of Service attacks.

link-sitting
teravus 2013-10-04 20:37:59 -05:00
parent 083eb7679b
commit 85593d8d25
1 changed files with 65 additions and 2 deletions

View File

@ -28,8 +28,10 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Net;
using System.Security.Cryptography; using System.Security.Cryptography;
using System.Text; using System.Text;
using System.Threading;
using HttpServer; using HttpServer;
namespace OpenSim.Framework.Servers.HttpServer namespace OpenSim.Framework.Servers.HttpServer
@ -98,6 +100,8 @@ namespace OpenSim.Framework.Servers.HttpServer
/// </summary> /// </summary>
public ValidateHandshake HandshakeValidateMethodOverride = null; public ValidateHandshake HandshakeValidateMethodOverride = null;
private ManualResetEvent _receiveDone = new ManualResetEvent(false);
private OSHttpRequest _request; private OSHttpRequest _request;
private HTTPNetworkContext _networkContext; private HTTPNetworkContext _networkContext;
private IHttpClientContext _clientContext; private IHttpClientContext _clientContext;
@ -109,6 +113,8 @@ namespace OpenSim.Framework.Servers.HttpServer
private bool _closing; private bool _closing;
private bool _upgraded; private bool _upgraded;
private int _maxPayloadBytes = 41943040; private int _maxPayloadBytes = 41943040;
private int _initialMsgTimeout = 0;
private int _defaultReadTimeout = 10000;
private const string HandshakeAcceptText = private const string HandshakeAcceptText =
"HTTP/1.1 101 Switching Protocols\r\n" + "HTTP/1.1 101 Switching Protocols\r\n" +
@ -131,6 +137,7 @@ namespace OpenSim.Framework.Servers.HttpServer
{ {
_request = preq; _request = preq;
_networkContext = pContext.GiveMeTheNetworkStreamIKnowWhatImDoing(); _networkContext = pContext.GiveMeTheNetworkStreamIKnowWhatImDoing();
_networkContext.Stream.ReadTimeout = _defaultReadTimeout;
_clientContext = pContext; _clientContext = pContext;
_bufferLength = bufferlen; _bufferLength = bufferlen;
_buffer = new byte[_bufferLength]; _buffer = new byte[_bufferLength];
@ -205,6 +212,16 @@ namespace OpenSim.Framework.Servers.HttpServer
set { _maxPayloadBytes = value; } set { _maxPayloadBytes = value; }
} }
/// <summary>
/// Set this to the maximum amount of milliseconds to wait for the first complete message to be sent or received on the websocket after upgrading
/// Default, it waits forever. Use this when your Websocket consuming code opens a connection and waits for a message from the other side to avoid a Denial of Service vector.
/// </summary>
public int InitialMsgTimeout
{
get { return _initialMsgTimeout; }
set { _initialMsgTimeout = value; }
}
/// <summary> /// <summary>
/// This triggers the websocket start the upgrade process /// This triggers the websocket start the upgrade process
/// </summary> /// </summary>
@ -245,6 +262,7 @@ namespace OpenSim.Framework.Servers.HttpServer
string rawaccept = string.Format(HandshakeAcceptText, acceptKey); string rawaccept = string.Format(HandshakeAcceptText, acceptKey);
SendUpgradeSuccess(rawaccept); SendUpgradeSuccess(rawaccept);
} }
else else
{ {
@ -258,6 +276,10 @@ namespace OpenSim.Framework.Servers.HttpServer
SendUpgradeSuccess(rawaccept); SendUpgradeSuccess(rawaccept);
} }
} }
public IPEndPoint GetRemoteIPEndpoint()
{
return _request.RemoteIPEndPoint;
}
/// <summary> /// <summary>
/// Generates a handshake response key string based on the client's /// Generates a handshake response key string based on the client's
@ -290,9 +312,16 @@ namespace OpenSim.Framework.Servers.HttpServer
WebSocketState socketState = new WebSocketState() { ReceivedBytes = new List<byte>(), Header = WebsocketFrameHeader.HeaderDefault(), FrameComplete = true}; WebSocketState socketState = new WebSocketState() { ReceivedBytes = new List<byte>(), Header = WebsocketFrameHeader.HeaderDefault(), FrameComplete = true};
byte[] bhandshakeResponse = Encoding.UTF8.GetBytes(pHandshakeResponse); byte[] bhandshakeResponse = Encoding.UTF8.GetBytes(pHandshakeResponse);
try try
{ {
if (_initialMsgTimeout > 0)
{
_receiveDone.Reset();
}
// Begin reading the TCP stream before writing the Upgrade success message to the other side of the stream. // Begin reading the TCP stream before writing the Upgrade success message to the other side of the stream.
_networkContext.Stream.BeginRead(_buffer, 0, _bufferLength, OnReceive, socketState); _networkContext.Stream.BeginRead(_buffer, 0, _bufferLength, OnReceive, socketState);
@ -303,6 +332,11 @@ namespace OpenSim.Framework.Servers.HttpServer
UpgradeCompletedDelegate d = OnUpgradeCompleted; UpgradeCompletedDelegate d = OnUpgradeCompleted;
if (d != null) if (d != null)
d(this, new UpgradeCompletedEventArgs()); d(this, new UpgradeCompletedEventArgs());
if (_initialMsgTimeout > 0)
{
if (!_receiveDone.WaitOne(TimeSpan.FromMilliseconds(_initialMsgTimeout)))
Close(string.Empty);
}
} }
catch (IOException) catch (IOException)
{ {
@ -489,6 +523,11 @@ namespace OpenSim.Framework.Servers.HttpServer
/// <param name="message">the string message that is to be sent</param> /// <param name="message">the string message that is to be sent</param>
public void SendMessage(string message) public void SendMessage(string message)
{ {
if (_initialMsgTimeout > 0)
{
_receiveDone.Set();
_initialMsgTimeout = 0;
}
byte[] messagedata = Encoding.UTF8.GetBytes(message); byte[] messagedata = Encoding.UTF8.GetBytes(message);
WebSocketFrame textMessageFrame = new WebSocketFrame() { Header = WebsocketFrameHeader.HeaderDefault(), WebSocketPayload = messagedata }; WebSocketFrame textMessageFrame = new WebSocketFrame() { Header = WebsocketFrameHeader.HeaderDefault(), WebSocketPayload = messagedata };
textMessageFrame.Header.Opcode = WebSocketReader.OpCode.Text; textMessageFrame.Header.Opcode = WebSocketReader.OpCode.Text;
@ -499,6 +538,11 @@ namespace OpenSim.Framework.Servers.HttpServer
public void SendData(byte[] data) public void SendData(byte[] data)
{ {
if (_initialMsgTimeout > 0)
{
_receiveDone.Set();
_initialMsgTimeout = 0;
}
WebSocketFrame dataMessageFrame = new WebSocketFrame() { Header = WebsocketFrameHeader.HeaderDefault(), WebSocketPayload = data}; WebSocketFrame dataMessageFrame = new WebSocketFrame() { Header = WebsocketFrameHeader.HeaderDefault(), WebSocketPayload = data};
dataMessageFrame.Header.IsEnd = true; dataMessageFrame.Header.IsEnd = true;
dataMessageFrame.Header.Opcode = WebSocketReader.OpCode.Binary; dataMessageFrame.Header.Opcode = WebSocketReader.OpCode.Binary;
@ -531,6 +575,11 @@ namespace OpenSim.Framework.Servers.HttpServer
/// </summary> /// </summary>
public void SendPingCheck() public void SendPingCheck()
{ {
if (_initialMsgTimeout > 0)
{
_receiveDone.Set();
_initialMsgTimeout = 0;
}
WebSocketFrame pingFrame = new WebSocketFrame() { Header = WebsocketFrameHeader.HeaderDefault(), WebSocketPayload = new byte[0] }; WebSocketFrame pingFrame = new WebSocketFrame() { Header = WebsocketFrameHeader.HeaderDefault(), WebSocketPayload = new byte[0] };
pingFrame.Header.Opcode = WebSocketReader.OpCode.Ping; pingFrame.Header.Opcode = WebSocketReader.OpCode.Ping;
pingFrame.Header.IsEnd = true; pingFrame.Header.IsEnd = true;
@ -544,6 +593,11 @@ namespace OpenSim.Framework.Servers.HttpServer
/// <param name="message"></param> /// <param name="message"></param>
public void Close(string message) public void Close(string message)
{ {
if (_initialMsgTimeout > 0)
{
_receiveDone.Set();
_initialMsgTimeout = 0;
}
if (_networkContext == null) if (_networkContext == null)
return; return;
if (_networkContext.Stream != null) if (_networkContext.Stream != null)
@ -583,7 +637,11 @@ namespace OpenSim.Framework.Servers.HttpServer
WebSocketReader.Mask(psocketState.Header.Mask, unmask); WebSocketReader.Mask(psocketState.Header.Mask, unmask);
psocketState.ReceivedBytes = new List<byte>(unmask); psocketState.ReceivedBytes = new List<byte>(unmask);
} }
if (psocketState.Header.Opcode != WebSocketReader.OpCode.Continue && _initialMsgTimeout > 0)
{
_receiveDone.Set();
_initialMsgTimeout = 0;
}
switch (psocketState.Header.Opcode) switch (psocketState.Header.Opcode)
{ {
case WebSocketReader.OpCode.Ping: case WebSocketReader.OpCode.Ping:
@ -696,6 +754,11 @@ namespace OpenSim.Framework.Servers.HttpServer
} }
public void Dispose() public void Dispose()
{ {
if (_initialMsgTimeout > 0)
{
_receiveDone.Set();
_initialMsgTimeout = 0;
}
if (_networkContext != null && _networkContext.Stream != null) if (_networkContext != null && _networkContext.Stream != null)
{ {
if (_networkContext.Stream.CanWrite) if (_networkContext.Stream.CanWrite)