diff --git a/Builds/VisualStudio2012/beast.vcxproj b/Builds/VisualStudio2012/beast.vcxproj index 3894c35e84..c4b9f5ae44 100644 --- a/Builds/VisualStudio2012/beast.vcxproj +++ b/Builds/VisualStudio2012/beast.vcxproj @@ -70,6 +70,7 @@ + @@ -291,6 +292,12 @@ + + true + true + true + true + true true diff --git a/Builds/VisualStudio2012/beast.vcxproj.filters b/Builds/VisualStudio2012/beast.vcxproj.filters index c85fd8174c..33811bf7c0 100644 --- a/Builds/VisualStudio2012/beast.vcxproj.filters +++ b/Builds/VisualStudio2012/beast.vcxproj.filters @@ -152,6 +152,9 @@ {422da6a1-e57e-4a96-9fce-e5958c16026e} + + {c4a7b6bb-88ec-4ff1-bc56-7c432a467500} + @@ -809,6 +812,9 @@ beast_asio\tests + + beast_asio\protocol + @@ -1261,6 +1267,9 @@ beast_asio\tests + + beast_asio\protocol + diff --git a/modules/beast_asio/beast_asio.cpp b/modules/beast_asio/beast_asio.cpp index b74854aa53..39fcf7d105 100644 --- a/modules/beast_asio/beast_asio.cpp +++ b/modules/beast_asio/beast_asio.cpp @@ -28,6 +28,8 @@ namespace beast #include "sockets/beast_Socket.cpp" #include "sockets/beast_SslContext.cpp" +#include "protocol/beast_ProxyHandshake.cpp" + #include "tests/beast_TestPeerBasics.cpp" #include "tests/beast_TestPeerLogic.cpp" #include "tests/beast_TestPeerLogicSyncServer.cpp" diff --git a/modules/beast_asio/beast_asio.h b/modules/beast_asio/beast_asio.h index f4a42d6e13..077268e006 100644 --- a/modules/beast_asio/beast_asio.h +++ b/modules/beast_asio/beast_asio.h @@ -56,6 +56,8 @@ namespace beast #include "sockets/beast_SharedSocket.h" #include "sockets/beast_SslContext.h" +#include "protocol/beast_ProxyHandshake.h" + #include "tests/beast_TestPeerBasics.h" #include "tests/beast_TestPeer.h" #include "tests/beast_TestPeerDetails.h" diff --git a/modules/beast_asio/protocol/beast_ProxyHandshake.cpp b/modules/beast_asio/protocol/beast_ProxyHandshake.cpp new file mode 100644 index 0000000000..99dc22bebb --- /dev/null +++ b/modules/beast_asio/protocol/beast_ProxyHandshake.cpp @@ -0,0 +1,384 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +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", "beast") + { + } + + 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/beast_asio/protocol/beast_ProxyHandshake.h b/modules/beast_asio/protocol/beast_ProxyHandshake.h new file mode 100644 index 0000000000..eea2e480dd --- /dev/null +++ b/modules/beast_asio/protocol/beast_ProxyHandshake.h @@ -0,0 +1,164 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef BEAST_PROXYYHANDSHAKE_H_INCLUDED +#define BEAST_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