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