From f37edb0873ef8cc69434cf9ed8a49f69bc968eee Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Wed, 31 Jul 2013 14:19:12 -0700 Subject: [PATCH] Add ProxyHandshake --- Builds/VisualStudio2012/RippleD.vcxproj | 14 +- .../VisualStudio2012/RippleD.vcxproj.filters | 11 +- Notes/VFALCO_TODO.txt | 1 + .../network/ripple_ProxyHandshake.cpp | 371 ++++++++++++++++++ .../network/ripple_ProxyHandshake.h | 151 +++++++ modules/ripple_app/ripple_app.cpp | 3 + modules/ripple_net/ripple_net.cpp | 2 - modules/ripple_net/ripple_net.h | 2 - 8 files changed, 537 insertions(+), 18 deletions(-) create mode 100644 modules/ripple_app/network/ripple_ProxyHandshake.cpp create mode 100644 modules/ripple_app/network/ripple_ProxyHandshake.h diff --git a/Builds/VisualStudio2012/RippleD.vcxproj b/Builds/VisualStudio2012/RippleD.vcxproj index e646e1b68b..de1f84bf82 100644 --- a/Builds/VisualStudio2012/RippleD.vcxproj +++ b/Builds/VisualStudio2012/RippleD.vcxproj @@ -301,6 +301,12 @@ true true + + true + true + true + true + true true @@ -998,12 +1004,6 @@ true true - - true - true - true - true - true @@ -1423,6 +1423,7 @@ + @@ -1563,7 +1564,6 @@ - diff --git a/Builds/VisualStudio2012/RippleD.vcxproj.filters b/Builds/VisualStudio2012/RippleD.vcxproj.filters index 71ad15054e..91219499c1 100644 --- a/Builds/VisualStudio2012/RippleD.vcxproj.filters +++ b/Builds/VisualStudio2012/RippleD.vcxproj.filters @@ -160,9 +160,6 @@ {c69b07a2-44e5-4b06-99a9-81f5d137ea15} - - {f0308442-e7af-44d4-a76a-c91fbf685e10} - @@ -885,8 +882,8 @@ [1] Ripple\ripple_app\main - - [1] Ripple\ripple_net\protocol + + [1] Ripple\ripple_app\network @@ -1676,8 +1673,8 @@ [2] Build - - [1] Ripple\ripple_net\protocol + + [1] Ripple\ripple_app\network diff --git a/Notes/VFALCO_TODO.txt b/Notes/VFALCO_TODO.txt index 7acb0598a8..331207b8b3 100644 --- a/Notes/VFALCO_TODO.txt +++ b/Notes/VFALCO_TODO.txt @@ -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 diff --git a/modules/ripple_app/network/ripple_ProxyHandshake.cpp b/modules/ripple_app/network/ripple_ProxyHandshake.cpp new file mode 100644 index 0000000000..c3dba646ee --- /dev/null +++ b/modules/ripple_app/network/ripple_ProxyHandshake.cpp @@ -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 (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 (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; diff --git a/modules/ripple_app/network/ripple_ProxyHandshake.h b/modules/ripple_app/network/ripple_ProxyHandshake.h new file mode 100644 index 0000000000..d9bd6efa3c --- /dev/null +++ b/modules/ripple_app/network/ripple_ProxyHandshake.h @@ -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 diff --git a/modules/ripple_app/ripple_app.cpp b/modules/ripple_app/ripple_app.cpp index 9e24a0e63b..469bccd3bd 100644 --- a/modules/ripple_app/ripple_app.cpp +++ b/modules/ripple_app/ripple_app.cpp @@ -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 diff --git a/modules/ripple_net/ripple_net.cpp b/modules/ripple_net/ripple_net.cpp index f8694664b6..a2ed5d8037 100644 --- a/modules/ripple_net/ripple_net.cpp +++ b/modules/ripple_net/ripple_net.cpp @@ -25,6 +25,4 @@ namespace ripple #include "basics/ripple_RPCServer.cpp" #include "basics/ripple_SNTPClient.cpp" -#include "protocol/ripple_ProxyProtocol.cpp" - } diff --git a/modules/ripple_net/ripple_net.h b/modules/ripple_net/ripple_net.h index d5bfdb6723..2452be7f9d 100644 --- a/modules/ripple_net/ripple_net.h +++ b/modules/ripple_net/ripple_net.h @@ -32,8 +32,6 @@ namespace ripple #include "basics/ripple_RPCServer.h" #include "basics/ripple_SNTPClient.h" -#include "protocol/ripple_ProxyProtocol.h" - } #endif