Add ProxyHandshake

This commit is contained in:
Vinnie Falco
2013-07-31 14:19:12 -07:00
parent ea4c0aa8a2
commit f37edb0873
8 changed files with 537 additions and 18 deletions

View File

@@ -301,6 +301,12 @@
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\modules\ripple_app\network\ripple_ProxyHandshake.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\modules\ripple_app\network\ripple_WSHandler.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
@@ -998,12 +1004,6 @@
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\modules\ripple_net\protocol\ripple_ProxyProtocol.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\modules\ripple_net\ripple_net.cpp" />
<ClCompile Include="..\..\modules\ripple_websocket\autosocket\ripple_AutoSocket.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
@@ -1423,6 +1423,7 @@
<ClInclude Include="..\..\modules\ripple_app\misc\ripple_ProofOfWorkFactory.h" />
<ClInclude Include="..\..\modules\ripple_app\misc\ripple_SerializedLedger.h" />
<ClInclude Include="..\..\modules\ripple_app\misc\ripple_SerializedTransaction.h" />
<ClInclude Include="..\..\modules\ripple_app\network\ripple_ProxyHandshake.h" />
<ClInclude Include="..\..\modules\ripple_app\network\ripple_WSHandler.h" />
<ClInclude Include="..\..\modules\ripple_app\network\WSConnection.h" />
<ClInclude Include="..\..\modules\ripple_app\network\WSDoor.h" />
@@ -1563,7 +1564,6 @@
<ClInclude Include="..\..\modules\ripple_net\basics\ripple_HttpsClient.h" />
<ClInclude Include="..\..\modules\ripple_net\basics\ripple_RPCServer.h" />
<ClInclude Include="..\..\modules\ripple_net\basics\ripple_SNTPClient.h" />
<ClInclude Include="..\..\modules\ripple_net\protocol\ripple_ProxyProtocol.h" />
<ClInclude Include="..\..\modules\ripple_net\ripple_net.h" />
<ClInclude Include="..\..\modules\ripple_websocket\autosocket\ripple_AutoSocket.h" />
<ClInclude Include="..\..\modules\ripple_websocket\ripple_websocket.h" />

View File

@@ -160,9 +160,6 @@
<Filter Include="[2] Build">
<UniqueIdentifier>{c69b07a2-44e5-4b06-99a9-81f5d137ea15}</UniqueIdentifier>
</Filter>
<Filter Include="[1] Ripple\ripple_net\protocol">
<UniqueIdentifier>{f0308442-e7af-44d4-a76a-c91fbf685e10}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\..\Subtrees\sqlite\sqlite3.c">
@@ -885,8 +882,8 @@
<ClCompile Include="..\..\modules\ripple_app\main\ripple_FatalErrorReporter.cpp">
<Filter>[1] Ripple\ripple_app\main</Filter>
</ClCompile>
<ClCompile Include="..\..\modules\ripple_net\protocol\ripple_ProxyProtocol.cpp">
<Filter>[1] Ripple\ripple_net\protocol</Filter>
<ClCompile Include="..\..\modules\ripple_app\network\ripple_ProxyHandshake.cpp">
<Filter>[1] Ripple\ripple_app\network</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
@@ -1676,8 +1673,8 @@
<ClInclude Include="..\..\BeastConfig.h">
<Filter>[2] Build</Filter>
</ClInclude>
<ClInclude Include="..\..\modules\ripple_net\protocol\ripple_ProxyProtocol.h">
<Filter>[1] Ripple\ripple_net\protocol</Filter>
<ClInclude Include="..\..\modules\ripple_app\network\ripple_ProxyHandshake.h">
<Filter>[1] Ripple\ripple_app\network</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>

View File

@@ -7,6 +7,7 @@ REMINDER: KEEP CHANGE LOG UP TO DATE
Vinnie's List: Changes day to day, descending priority
(Items marked '*' can be handled by others.)
- Emergency implement PROXY protcol
- Get rid of boost::filesystem
- Deeply create directories specified in config settings
- Finish unit tests and code for Validators

View File

@@ -0,0 +1,371 @@
//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
ProxyHandshake::ProxyHandshake (bool expectHandshake)
: m_status (expectHandshake ? statusHandshake : statusNone)
, m_gotCR (false)
{
m_buffer.preallocateBytes (maxVersion1Bytes);
}
ProxyHandshake::~ProxyHandshake ()
{
}
std::size_t ProxyHandshake::feed (void const* inputBuffer, size_t inputBytes)
{
std::size_t bytesConsumed = 0;
char const* p = static_cast <char const*> (inputBuffer);
if (m_status == statusHandshake)
{
if (! m_gotCR)
{
while (inputBytes > 0 && m_buffer.length () < maxVersion1Bytes - 1)
{
beast_wchar c = *p++;
++bytesConsumed;
--inputBytes;
m_buffer += c;
if (c == '\r')
{
m_gotCR = true;
break;
}
else if (c == '\n')
{
m_status = statusFailed;
}
}
if (m_buffer.length () > maxVersion1Bytes - 1)
{
m_status = statusFailed;
}
}
}
if (m_status == statusHandshake)
{
if (inputBytes > 0 && m_gotCR)
{
bassert (m_buffer.length () < maxVersion1Bytes);
char const lf ('\n');
if (*p == lf)
{
++bytesConsumed;
--inputBytes;
m_buffer += lf;
parseLine ();
}
else
{
m_status = statusFailed;
}
}
}
return bytesConsumed;
}
void ProxyHandshake::parseLine ()
{
Version1 p;
bool success = p.parse (m_buffer.getCharPointer (), m_buffer.length ());
if (success)
{
m_endpoints = p.endpoints;
m_status = statusOk;
}
else
{
m_status = statusFailed;
}
}
int ProxyHandshake::indexOfFirstNonNumber (String const& input)
{
bassert (input.length () > 0);
int i = 0;
for (; i < input.length (); ++i)
{
if (! CharacterFunctions::isDigit (input [i]))
break;
}
return i;
}
bool ProxyHandshake::chop (String const& what, String& input)
{
if (input.startsWith (what))
{
input = input.substring (what.length ());
return true;
}
return false;
}
bool ProxyHandshake::chopUInt (int* value, int limit, String& input)
{
if (input.length () <= 0)
return false;
String const s = input.substring (0, indexOfFirstNonNumber (input));
if (s.length () <= 0)
return false;
int const n = s.getIntValue ();
// Leading zeroes disallowed as per spec, to prevent confusion with octal
if (String (n) != s)
return false;
if (n < 0 || n > limit)
return false;
input = input.substring (s.length ());
*value = n;
return true;
}
//------------------------------------------------------------------------------
/*
steps:
Proxy protocol lets us filter attackers by learning the source ip and port
1. Determine if we should use the proxy on a connection
- Port just for proxy protocol connections
- Filter on source IPs
2. Read a line from the connection to get the proxy information
3. Parse the line (human or binary?)
4. Code Interface to retrieve proxy information (ip/port) on connection
*/
ProxyHandshake::Version1::Version1 ()
{
}
bool ProxyHandshake::IPv4::Addr::chop (String& input)
{
if (!ProxyHandshake::chopUInt (&a, 255, input))
return false;
if (!ProxyHandshake::chop (".", input))
return false;
if (!ProxyHandshake::chopUInt (&b, 255, input))
return false;
if (!ProxyHandshake::chop (".", input))
return false;
if (!ProxyHandshake::chopUInt (&c, 255, input))
return false;
if (!ProxyHandshake::chop (".", input))
return false;
if (!ProxyHandshake::chopUInt (&d, 255, input))
return false;
return true;
}
bool ProxyHandshake::Version1::parse (void const* headerData, size_t headerBytes)
{
String input (static_cast <CharPointer_UTF8::CharType const*> (headerData), headerBytes);
if (input.length () < 2)
return false;
if (! input.endsWith ("\r\n"))
return false;
input = input.dropLastCharacters (2);
if (! ProxyHandshake::chop ("PROXY ", input))
return false;
if (ProxyHandshake::chop ("UNKNOWN", input))
{
endpoints.proto = protoUnknown;
input = "";
}
else
{
if (ProxyHandshake::chop ("TCP4 ", input))
{
endpoints.proto = protoTcp4;
if (! endpoints.ipv4.sourceAddr.chop (input))
return false;
if (! ProxyHandshake::chop (" ", input))
return false;
if (! endpoints.ipv4.destAddr.chop (input))
return false;
if (! ProxyHandshake::chop (" ", input))
return false;
if (! ProxyHandshake::chopUInt (&endpoints.ipv4.sourcePort, 65535, input))
return false;
if (! ProxyHandshake::chop (" ", input))
return false;
if (! ProxyHandshake::chopUInt (&endpoints.ipv4.destPort, 65535, input))
return false;
}
else if (ProxyHandshake::chop ("TCP6 ", input))
{
endpoints.proto = protoTcp6;
//bassertfalse;
return false;
}
else
{
return false;
}
}
// Can't have anything extra between the last port number and the CRLF
if (input.length () > 0)
return false;
return true;
}
//------------------------------------------------------------------------------
class ProxyHandshakeTests : public UnitTest
{
public:
ProxyHandshakeTests () : UnitTest ("ProxyHandshake", "ripple", runManual)
{
}
static std::string goodIpv4 ()
{
return "PROXY TCP4 255.255.255.255 255.255.255.255 65535 65535\r\n"; // 56 chars
}
static std::string goodIpv6 ()
{
return "PROXY TCP6 fffffffffffffffffffffffffffffffffffffff.fffffffffffffffffffffffffffffffffffffff 65535 65535\r\n";
//1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123 4 (104 chars)
}
static std::string goodUnknown ()
{
return "PROXY UNKNOWN\r\n";
}
static std::string goodUnknownBig ()
{
return "PROXY UNKNOWN fffffffffffffffffffffffffffffffffffffff.fffffffffffffffffffffffffffffffffffffff 65535 65535\r\n";
//1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456 7 (107 chars)
}
void testHandshake (std::string const& s, bool shouldSucceed)
{
if (s.size () > 1)
{
ProxyHandshake h (true);
expect (h.getStatus () == ProxyHandshake::statusHandshake);
for (int i = 0; i < s.size () && h.getStatus () == ProxyHandshake::statusHandshake ; ++i)
{
std::size_t const bytesConsumed = h.feed (& s[i], 1);
if (i != s.size () - 1)
expect (h.getStatus () == ProxyHandshake::statusHandshake);
expect (bytesConsumed == 1);
}
if (shouldSucceed)
{
expect (h.getStatus () == ProxyHandshake::statusOk);
}
else
{
expect (h.getStatus () == ProxyHandshake::statusFailed);
}
}
else
{
bassertfalse;
}
}
void testVersion1String (std::string const& s, bool shouldSucceed)
{
ProxyHandshake::Version1 p;
if (shouldSucceed)
{
expect (p.parse (s.c_str (), s.size ()));
}
else
{
unexpected (p.parse (s.c_str (), s.size ()));
}
for (int i = 1; i < s.size () - 1; ++i)
{
String const partial = String (s).dropLastCharacters (i);
std::string ss (partial.toStdString ());
expect (! p.parse (ss.c_str (), ss.size ()));
}
testHandshake (s, shouldSucceed);
}
void testVersion1 ()
{
beginTestCase ("version1");
testVersion1String (goodIpv4 (), true);
testVersion1String (goodIpv6 (), false);
testVersion1String (goodUnknown (), true);
testVersion1String (goodUnknownBig (), true);
}
void runTest ()
{
testVersion1 ();
}
};
static ProxyHandshakeTests proxyHandshakeTests;

View File

@@ -0,0 +1,151 @@
//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
#ifndef RIPPLE_PROXYYHANDSHAKE_H_INCLUDED
#define RIPPLE_PROXYYHANDSHAKE_H_INCLUDED
/** PROXY protocol handshake state machine.
The PROXY Protocol:
http://haproxy.1wt.eu/download/1.5/doc/proxy-protocol.txt
*/
class ProxyHandshake
{
public:
/** Status of the handshake state machine. */
enum Status
{
statusNone, // No handshake expected
statusHandshake, // Handshake in progress
statusFailed, // Handshake failed
statusOk, // Handshake succeeded
};
enum Proto
{
protoTcp4,
protoTcp6,
protoUnknown
};
/** PROXY information for IPv4 families. */
struct IPv4
{
struct Addr
{
int a;
int b;
int c;
int d;
bool chop (String& input);
};
Addr sourceAddr;
Addr destAddr;
int sourcePort;
int destPort;
};
/** PROXY information for IPv6 families. */
struct IPv6
{
struct Addr
{
int a;
int b;
int c;
int d;
};
Addr sourceAddr;
Addr destAddr;
int sourcePort;
int destPort;
};
/** Fully decoded PROXY information. */
struct Endpoints
{
Endpoints ()
: proto (protoUnknown)
{
}
Proto proto;
IPv4 ipv4; // valid if proto == protoTcp4
IPv6 ipv6; // valid if proto == protoTcp6;
};
//--------------------------------------------------------------------------
/** Parser for PROXY version 1. */
struct Version1
{
enum
{
// Maximum input buffer size needed, including a null
// terminator, as per the PROXY protocol specification.
maxBufferBytes = 108
};
Endpoints endpoints;
Version1 ();
/** Parse the header.
@param rawHeader a pointer to the header data
@return `true` If it was parsed successfully.
*/
bool parse (void const* headerData, size_t headerBytes);
};
//--------------------------------------------------------------------------
/** Create the handshake state.
If a handshake is expected, then it is required.
@param wantHandshake `false` to skip handshaking.
*/
explicit ProxyHandshake (bool expectHandshake = false);
~ProxyHandshake ();
inline Status getStatus () const noexcept
{
return m_status;
}
inline Endpoints const& getEndpoints () const noexcept
{
return m_endpoints;
};
/** Feed the handshaking state engine.
@return The number of bytes consumed in the input buffer.
*/
std::size_t feed (void const* inputBuffer, std::size_t inputBytes);
// Utility functions used by parsers
static int indexOfFirstNonNumber (String const& input);
static bool chop (String const& what, String& input);
static bool chopUInt (int* value, int limit, String& input);
private:
void parseLine ();
private:
enum
{
maxVersion1Bytes = 107 // including crlf, not including null term
};
Status m_status;
String m_buffer;
bool m_gotCR;
Endpoints m_endpoints;
};
#endif

View File

@@ -239,6 +239,8 @@ static const uint64 tenTo17m1 = tenTo17 - 1;
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
#include "network/ripple_ProxyHandshake.h" // private?
//------------------------------------------------------------------------------
#if ! defined (RIPPLE_MAIN_PART) || RIPPLE_MAIN_PART == 1
@@ -326,6 +328,7 @@ static DH* handleTmpDh (SSL* ssl, int is_export, int iKeyLength)
#include "tx/Transactor.cpp"
#include "network/WSConnection.cpp"
#include "network/WSDoor.cpp"
#include "network/ripple_ProxyHandshake.cpp"
#endif

View File

@@ -25,6 +25,4 @@ namespace ripple
#include "basics/ripple_RPCServer.cpp"
#include "basics/ripple_SNTPClient.cpp"
#include "protocol/ripple_ProxyProtocol.cpp"
}

View File

@@ -32,8 +32,6 @@ namespace ripple
#include "basics/ripple_RPCServer.h"
#include "basics/ripple_SNTPClient.h"
#include "protocol/ripple_ProxyProtocol.h"
}
#endif