From 4615297b00abe88e8349a1a8b9dbda04b4922c04 Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Fri, 16 Aug 2013 13:42:20 -0700 Subject: [PATCH] Refactor handshake parsing and logic classes --- Builds/VisualStudio2012/beast.vcxproj | 5 +- Builds/VisualStudio2012/beast.vcxproj.filters | 11 +- .../basics/beast_FixedInputBuffer.h | 167 ++++++-- modules/beast_asio/beast_asio.cpp | 2 +- modules/beast_asio/beast_asio.h | 5 +- .../handshake/beast_HandshakeDetectLogic.h | 18 +- .../beast_HandshakeDetectLogicPROXY.cpp | 20 + .../beast_HandshakeDetectLogicPROXY.h | 159 ++++++++ .../beast_HandshakeDetectLogicSSL2.h | 7 +- .../beast_HandshakeDetectLogicSSL3.h | 7 +- .../handshake/beast_HandshakeDetectStream.h | 24 +- .../beast_asio/handshake/beast_InputParser.h | 379 +++++++++++++++++ .../handshake/beast_ProxyHandshake.cpp | 384 ------------------ .../handshake/beast_ProxyHandshake.h | 164 -------- .../tests/beast_TestPeerLogicProxyClient.cpp | 2 +- 15 files changed, 747 insertions(+), 607 deletions(-) create mode 100644 modules/beast_asio/handshake/beast_HandshakeDetectLogicPROXY.cpp create mode 100644 modules/beast_asio/handshake/beast_HandshakeDetectLogicPROXY.h create mode 100644 modules/beast_asio/handshake/beast_InputParser.h delete mode 100644 modules/beast_asio/handshake/beast_ProxyHandshake.cpp delete mode 100644 modules/beast_asio/handshake/beast_ProxyHandshake.h diff --git a/Builds/VisualStudio2012/beast.vcxproj b/Builds/VisualStudio2012/beast.vcxproj index 6b28e276a..9017ba1d7 100644 --- a/Builds/VisualStudio2012/beast.vcxproj +++ b/Builds/VisualStudio2012/beast.vcxproj @@ -77,10 +77,11 @@ + - + @@ -309,7 +310,7 @@ true - + true true true diff --git a/Builds/VisualStudio2012/beast.vcxproj.filters b/Builds/VisualStudio2012/beast.vcxproj.filters index c9094c3bb..b57b3da3d 100644 --- a/Builds/VisualStudio2012/beast.vcxproj.filters +++ b/Builds/VisualStudio2012/beast.vcxproj.filters @@ -836,9 +836,6 @@ beast_asio\streams - - beast_asio\handshake - beast_asio\handshake @@ -854,6 +851,12 @@ beast_asio\handshake + + beast_asio\handshake + + + beast_asio\handshake + @@ -1312,7 +1315,7 @@ beast_asio\basics - + beast_asio\handshake diff --git a/modules/beast_asio/basics/beast_FixedInputBuffer.h b/modules/beast_asio/basics/beast_FixedInputBuffer.h index ce0a96805..a7c06d6e7 100644 --- a/modules/beast_asio/basics/beast_FixedInputBuffer.h +++ b/modules/beast_asio/basics/beast_FixedInputBuffer.h @@ -24,68 +24,88 @@ This provides a convenient interface for doing a bytewise verification/reject test on a handshake protocol. */ -template -struct FixedInputBuffer +/** @{ */ +class FixedInputBuffer { - template - explicit FixedInputBuffer (ConstBufferSequence const& buffer) - : m_buffer (boost::asio::buffer (m_storage)) - , m_size (boost::asio::buffer_copy (m_buffer, buffer)) - , m_data (boost::asio::buffer_cast (m_buffer)) +protected: + struct CtorParams + { + CtorParams (uint8 const* begin_, std::size_t bytes_) + : begin (begin_) + , bytes (bytes_) + { + } + + uint8 const* begin; + std::size_t bytes; + }; + + FixedInputBuffer (CtorParams const& params) + : m_begin (params.begin) + , m_iter (m_begin) + , m_end (m_begin + params.bytes) { } - uint8 operator[] (std::size_t index) const noexcept +public: + FixedInputBuffer (FixedInputBuffer const& other) + : m_begin (other.m_begin) + , m_iter (other.m_iter) + , m_end (other.m_end) { - bassert (index >= 0 && index < m_size); - return m_data [index]; } - bool peek (std::size_t bytes) const noexcept + FixedInputBuffer& operator= (FixedInputBuffer const& other) { - if (m_size >= bytes) - return true; - return false; + m_begin = other.m_begin; + m_iter = other.m_iter; + m_end = other.m_end; + return *this; + } + + // Returns the number of bytes consumed + std::size_t used () const noexcept + { + return m_iter - m_begin; + } + + // Returns the size of what's remaining + std::size_t size () const noexcept + { + return m_end - m_iter; + } + + void const* peek (std::size_t bytes) + { + return peek_impl (bytes, nullptr); } template - bool peek (T* t) noexcept + bool peek (T* t) const noexcept { - std::size_t const bytes = sizeof (T); - if (m_size >= bytes) - { - std::copy (m_data, m_data + bytes, t); - return true; - } - return false; + return peek_impl (sizeof (T), t) != nullptr; } bool consume (std::size_t bytes) noexcept { - if (m_size >= bytes) - { - m_data += bytes; - m_size -= bytes; - return true; - } - return false; + return read_impl (bytes, nullptr) != nullptr; + } + + bool read (std::size_t bytes) noexcept + { + return read_impl (bytes, nullptr) != nullptr; } template bool read (T* t) noexcept { - std::size_t const bytes = sizeof (T); - if (m_size >= bytes) - { - //this causes a stack corruption. - //std::copy (m_data, m_data + bytes, t); + return read_impl (sizeof (T), t) != nullptr; + } - memcpy (t, m_data, bytes); - m_data += bytes; - m_size -= bytes; - return true; - } - return false; + uint8 operator[] (std::size_t index) const noexcept + { + bassert (index >= 0 && index < size ()); + return m_iter [index]; } // Reads an integraltype in network byte order @@ -97,16 +117,77 @@ struct FixedInputBuffer //static_bassert (std::is_integral ::value); IntegerType networkValue; if (! read (&networkValue)) - return; + return false; *value = fromNetworkByteOrder (networkValue); return true; } +protected: + void const* peek_impl (std::size_t bytes, void* buffer) const noexcept + { + if (size () >= bytes) + { + if (buffer != nullptr) + memcpy (buffer, m_iter, bytes); + return m_iter; + } + return nullptr; + } + + void const* read_impl (std::size_t bytes, void* buffer) noexcept + { + if (size () >= bytes) + { + if (buffer != nullptr) + memcpy (buffer, m_iter, bytes); + void const* data = m_iter; + m_iter += bytes; + return data; + } + return nullptr; + } + +private: + uint8 const* m_begin; + uint8 const* m_iter; + uint8 const* m_end; +}; + +//------------------------------------------------------------------------------ + +template +class FixedInputBufferSize : public FixedInputBuffer +{ +protected: + struct SizedCtorParams + { + template + SizedCtorParams (ConstBufferSequence const& buffers, Storage& storage) + { + MutableBuffer buffer (boost::asio::buffer (storage)); + data = boost::asio::buffer_cast (buffer); + bytes = boost::asio::buffer_copy (buffer, buffers); + } + + operator CtorParams () const noexcept + { + return CtorParams (data, bytes); + } + + uint8 const* data; + std::size_t bytes; + }; + +public: + template + explicit FixedInputBufferSize (ConstBufferSequence const& buffers) + : FixedInputBuffer (SizedCtorParams (buffers, m_storage)) + { + } + private: boost::array m_storage; MutableBuffer m_buffer; - std::size_t m_size; - uint8 const* m_data; }; #endif diff --git a/modules/beast_asio/beast_asio.cpp b/modules/beast_asio/beast_asio.cpp index 64d811048..c22b3b6dc 100644 --- a/modules/beast_asio/beast_asio.cpp +++ b/modules/beast_asio/beast_asio.cpp @@ -30,7 +30,7 @@ namespace beast #include "sockets/beast_Socket.cpp" #include "sockets/beast_SslContext.cpp" -#include "handshake/beast_ProxyHandshake.cpp" +#include "handshake/beast_HandshakeDetectLogicPROXY.cpp" #include "tests/beast_TestPeerBasics.cpp" #include "tests/beast_TestPeerLogic.cpp" diff --git a/modules/beast_asio/beast_asio.h b/modules/beast_asio/beast_asio.h index acba94323..3ab9fc29b 100644 --- a/modules/beast_asio/beast_asio.h +++ b/modules/beast_asio/beast_asio.h @@ -60,8 +60,9 @@ namespace beast #include "sockets/beast_SocketWrapper.h" #include "sockets/beast_SslContext.h" -#include "handshake/beast_ProxyHandshake.h" - #include "handshake/beast_HandshakeDetectLogic.h" + #include "handshake/beast_InputParser.h" + #include "handshake/beast_HandshakeDetectLogic.h" +#include "handshake/beast_HandshakeDetectLogicPROXY.h" #include "handshake/beast_HandshakeDetectLogicSSL2.h" #include "handshake/beast_HandshakeDetectLogicSSL3.h" #include "handshake/beast_HandshakeDetectStream.h" diff --git a/modules/beast_asio/handshake/beast_HandshakeDetectLogic.h b/modules/beast_asio/handshake/beast_HandshakeDetectLogic.h index 62eae110d..8855928e2 100644 --- a/modules/beast_asio/handshake/beast_HandshakeDetectLogic.h +++ b/modules/beast_asio/handshake/beast_HandshakeDetectLogic.h @@ -42,6 +42,13 @@ public: */ virtual std::size_t max_needed () = 0; + /** How many bytes the handshake consumes. + If the detector processes the entire handshake this will + be non zero. The SSL detector would return 0, since we + want all the existing bytes to be passed on. + */ + virtual std::size_t bytes_consumed () = 0; + /** Return true if we have enough data to form a conclusion. */ bool finished () const noexcept @@ -94,19 +101,24 @@ public: return m_logic; } - std::size_t max_needed () noexcept + std::size_t max_needed () { return m_logic.max_needed (); } - bool finished () noexcept + std::size_t bytes_consumed () + { + return m_logic.bytes_consumed (); + } + + bool finished () { return m_logic.finished (); } /** If finished is true, this tells us if the handshake was detected. */ - bool success () noexcept + bool success () { return m_logic.success (); } diff --git a/modules/beast_asio/handshake/beast_HandshakeDetectLogicPROXY.cpp b/modules/beast_asio/handshake/beast_HandshakeDetectLogicPROXY.cpp new file mode 100644 index 000000000..b95e38c58 --- /dev/null +++ b/modules/beast_asio/handshake/beast_HandshakeDetectLogicPROXY.cpp @@ -0,0 +1,20 @@ +//------------------------------------------------------------------------------ +/* + 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. +*/ +//============================================================================== + + diff --git a/modules/beast_asio/handshake/beast_HandshakeDetectLogicPROXY.h b/modules/beast_asio/handshake/beast_HandshakeDetectLogicPROXY.h new file mode 100644 index 000000000..4b65e9bb9 --- /dev/null +++ b/modules/beast_asio/handshake/beast_HandshakeDetectLogicPROXY.h @@ -0,0 +1,159 @@ +//------------------------------------------------------------------------------ +/* + 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_HANDSHAKEDETECTLOGICPROXY_H_INCLUDED +#define BEAST_HANDSHAKEDETECTLOGICPROXY_H_INCLUDED + +/** Handshake detector for the PROXY protcol + + http://haproxy.1wt.eu/download/1.5/doc/proxy-protocol.txt +*/ +class HandshakeDetectLogicPROXY : public HandshakeDetectLogic +{ +public: + typedef int arg_type; + + enum + { + // This is for version 1. The largest number of bytes + // that we could possibly need to parse a valid handshake. + // We will reject it much sooner if there's an illegal value. + // + maxBytesNeeded = 107 // including CRLF, no null term + }; + + struct ProxyInfo + { + typedef InputParser::IPv4Address IPv4Address; + + String protocol; // "TCP4", "TCP6", "UNKNOWN" + + IPv4Address sourceAddress; + IPv4Address destAddress; + + uint16 sourcePort; + uint16 destPort; + }; + + explicit HandshakeDetectLogicPROXY (arg_type const&) + : m_consumed (0) + { + } + + ProxyInfo const& getInfo () const noexcept + { + return m_info; + } + + std::size_t max_needed () + { + return maxBytesNeeded; + } + + std::size_t bytes_consumed () + { + return m_consumed; + } + + template + void analyze (ConstBufferSequence const& buffer) + { + FixedInputBufferSize in (buffer); + + InputParser::State state; + + analyze_input (in, state); + + if (state.passed ()) + { + m_consumed = in.used (); + conclude (true); + } + else if (state.failed ()) + { + conclude (false); + } + } + + void analyze_input (FixedInputBuffer& in, InputParser::State& state) + { + using namespace InputParser; + + if (! match (in, "PROXY "), state) + return; + + if (match (in, "TCP4 ")) + { + m_info.protocol = "TCP4"; + + if (! read (in, m_info.sourceAddress, state)) + return; + + if (! match (in, " ", state)) + return; + + if (! read (in, m_info.destAddress, state)) + return; + + if (! match (in, " ", state)) + return; + + UInt16Str sourcePort; + if (! read (in, sourcePort, state)) + return; + m_info.sourcePort = sourcePort.value; + + if (! match (in, " ", state)) + return; + + UInt16Str destPort; + if (! read (in, destPort, state)) + return; + m_info.destPort = destPort.value; + + if (! match (in, "\r\n", state)) + return; + + state = State::pass; + return; + } + else if (match (in, "TCP6 ")) + { + m_info.protocol = "TCP6"; + + state = State::fail; + return; + } + else if (match (in, "UNKNOWN ")) + { + m_info.protocol = "UNKNOWN"; + + state = State::fail; + return; + } + + state = State::fail; + } + +private: + std::size_t m_consumed; + ProxyInfo m_info; +}; + +#endif diff --git a/modules/beast_asio/handshake/beast_HandshakeDetectLogicSSL2.h b/modules/beast_asio/handshake/beast_HandshakeDetectLogicSSL2.h index 49f63eeb9..47a6d68df 100644 --- a/modules/beast_asio/handshake/beast_HandshakeDetectLogicSSL2.h +++ b/modules/beast_asio/handshake/beast_HandshakeDetectLogicSSL2.h @@ -53,10 +53,15 @@ public: return bytesNeeded; } + std::size_t bytes_consumed () + { + return 0; + } + template void analyze (ConstBufferSequence const& buffer) { - FixedInputBuffer in (buffer); + FixedInputBufferSize in (buffer); { uint8 byte; diff --git a/modules/beast_asio/handshake/beast_HandshakeDetectLogicSSL3.h b/modules/beast_asio/handshake/beast_HandshakeDetectLogicSSL3.h index c2314f04a..839c053c2 100644 --- a/modules/beast_asio/handshake/beast_HandshakeDetectLogicSSL3.h +++ b/modules/beast_asio/handshake/beast_HandshakeDetectLogicSSL3.h @@ -45,11 +45,16 @@ public: return bytesNeeded; } + std::size_t bytes_consumed () + { + return 0; + } + template void analyze (ConstBufferSequence const& buffer) { uint16 version; - FixedInputBuffer in (buffer); + FixedInputBufferSize in (buffer); uint8 msg_type; if (! in.read (&msg_type)) diff --git a/modules/beast_asio/handshake/beast_HandshakeDetectStream.h b/modules/beast_asio/handshake/beast_HandshakeDetectStream.h index 82c59c45a..34c1e3aca 100644 --- a/modules/beast_asio/handshake/beast_HandshakeDetectStream.h +++ b/modules/beast_asio/handshake/beast_HandshakeDetectStream.h @@ -97,6 +97,18 @@ public: { } + // This puts bytes that you already have into the detector buffer + // Any leftovers will be given to the callback. + // A copy of the data is made. + // + template + void fill (ConstBufferSequence const& buffers) + { + m_buffer.commit (boost::asio::buffer_copy ( + m_buffer.prepare (boost::asio::buffer_size (buffers)), + buffers)); + } + // basic_io_object boost::asio::io_service& get_io_service () @@ -203,6 +215,10 @@ public: if (m_logic.finished ()) { + // consume what we used (for SSL its 0) + std::size_t const consumed = m_logic.bytes_consumed (); + bassert (consumed <= m_buffer.size ()); + m_buffer.consume (consumed); m_callback->on_detect (m_logic.get (), ec, ConstBuffers (m_buffer.data ())); break; @@ -240,10 +256,16 @@ public: std::size_t const available = m_buffer.size (); std::size_t const needed = m_logic.max_needed (); - m_logic.analyze (m_buffer.data ()); + if (bytes_transferred > 0) + m_logic.analyze (m_buffer.data ()); if (m_logic.finished ()) { + // consume what we used (for SSL its 0) + std::size_t const consumed = m_logic.bytes_consumed (); + bassert (consumed <= m_buffer.size ()); + m_buffer.consume (consumed); + #if BEAST_ASIO_HAS_BUFFEREDHANDSHAKE if (! m_origBufferedHandler.isNull ()) { diff --git a/modules/beast_asio/handshake/beast_InputParser.h b/modules/beast_asio/handshake/beast_InputParser.h new file mode 100644 index 000000000..f79f23afc --- /dev/null +++ b/modules/beast_asio/handshake/beast_InputParser.h @@ -0,0 +1,379 @@ +//------------------------------------------------------------------------------ +/* + 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_INPUTPARSER_H_INCLUDED +#define BEAST_INPUTPARSER_H_INCLUDED + +namespace InputParser +{ + +/** Tri-valued parsing state. + This is convertible to bool which means continue. + Or you can use stop() to decide if you should return. + After a stop you can use failed () to determine if parsing failed. +*/ +struct State : SafeBool +{ + enum State_t + { + pass, // passed the parse + fail, // failed the parse + more // didn't fail but need more bytes + }; + + State () : m_state (more) { } + + State (State_t state) : m_state (state) { } + + /** Implicit construction from bool. + If condition is true then the parse passes, else need more. + */ + State (bool condition) : m_state (condition ? pass : more) { } + + State& operator= (State_t state) { m_state = state; return *this; } + + bool eof () const noexcept { return m_state == more; } + bool stop () const noexcept { return m_state != pass; } + bool passed () const noexcept { return m_state == pass; } + bool failed () const noexcept { return m_state == fail; } + bool asBoolean () const noexcept { return m_state == pass; } // for SafeBool<> + +private: + State_t m_state; +}; + +//------------------------------------------------------------------------------ + +// Shortcut to save typing. +typedef FixedInputBuffer& Input; + +/** Specializations implement the get() function. */ +template +struct Get; + +/** Specializations implement the match() function. + Default implementation of match tries to read it into a local. +*/ +template +struct Match +{ + static State func (Input in, T other) + { + T t; + State state = Get ::func (in, t); + if (state.passed ()) + { + if (t == other) + return State::passed; + return State::failed; + } + return state; + } +}; + +/** Specializations implement the peek() function. + Default implementation of peek reads and rewinds. +*/ +template +struct Peek +{ + static State func (Input in, T& t) + { + Input dup (in); + return Get ::func (dup, t); + } +}; + +//------------------------------------------------------------------------------ +// +// Free Functions +// +//------------------------------------------------------------------------------ + +// match a block of data in memory +// +static State match_buffer (Input in, void const* buffer, std::size_t bytes) +{ + bassert (bytes > 0); + if (in.size () <= 0) + return State::more; + + std::size_t const have = std::min (in.size (), bytes); + void const* data = in.peek (have); + bassert (data != nullptr); + + int const compare = memcmp (data, buffer, have); + if (compare != 0) + return State::fail; + in.consume (have); + + return have == bytes; +} + +//------------------------------------------------------------------------------ +// +// match +// + +// Returns the state +template +State match (Input in, T t) +{ + return Match ::func (in, t); +} + +// Stores the state in the argument and returns true if its a pass +template +bool match (Input in, T t, State& state) +{ + return (state = match (in, t)).passed (); +} + +//------------------------------------------------------------------------------ +// +// peek +// + +// Returns the state +template +State peek (Input in, T& t) +{ + return Peek ::func (in, t); +} + +// Stores the state in the argument and returns true if its a pass +template +bool peek (Input in, T& t, State& state) +{ + return (state = peek (in, t)).passed (); +} + +//------------------------------------------------------------------------------ +// +// read +// + +// Returns the state +template +State read (Input in, T& t) +{ + return Get ::func (in, t); +} + +// Stores the state in the argument and returns true if its a pass +template +bool read (Input in, T& t, State& state) +{ + return (state = read (in, t)).passed (); +} + +//------------------------------------------------------------------------------ +// +// Specializations for basic types +// + +template <> +struct Match +{ + static State func (Input in, char const* text) + { + return InputParser::match_buffer (in, text, strlen (text)); + } +}; + +//------------------------------------------------------------------------------ +// +// Special types and their specializations +// + +struct Digit +{ + int value; +}; + +template <> +struct Get +{ + static State func (Input in, Digit& t) + { + char c; + if (! in.peek (&c)) + return State::more; + if (! std::isdigit (c)) + return State::fail; + in.consume (1); + t.value = c - '0'; + return State::pass; + } +}; + +//------------------------------------------------------------------------------ + +// An unsigned 32 bit number expressed as a string +struct UInt32Str +{ + uint32 value; +}; + +template <> +struct Get +{ + static State func (Input in, UInt32Str& t) + { + State state; + uint32 value (0); + + Digit digit; + // have to have at least one digit + if (! read (in, digit, state)) + return state; + value = digit.value; + + for (;;) + { + state = peek (in, digit); + + if (state.failed ()) + { + t.value = value; + return State::pass; + } + else if (state.eof ()) + { + t.value = value; + return state; + } + + // can't have a digit following a zero + if (value == 0) + return State::fail; + + uint32 newValue = (value * 10) + digit.value; + + // overflow + if (newValue < value) + return State::fail; + + value = newValue; + } + + return State::fail; + } +}; + +//------------------------------------------------------------------------------ + +// An unsigned 16 bit number expressed as a string +struct UInt16Str +{ + uint16 value; +}; + +template <> +struct Get +{ + static State func (Input in, UInt16Str& t) + { + UInt32Str v; + State state = read (in, v); + if (state.passed ()) + { + if (v.value <= 65535) + { + t.value = uint16(v.value); + return State::pass; + } + return State::fail; + } + return state; + } +}; + +//------------------------------------------------------------------------------ + +// An unsigned 8 bit number expressed as a string +struct UInt8Str +{ + uint8 value; +}; + +template <> +struct Get +{ + static State func (Input in, UInt8Str& t) + { + UInt32Str v; + State state = read (in, v); + if (state.passed ()) + { + if (v.value <= 255) + { + t.value = uint8(v.value); + return State::pass; + } + return State::fail; + } + return state; + } +}; + +//------------------------------------------------------------------------------ + +// An dotted IPv4 address +struct IPv4Address +{ + uint8 value [4]; +}; + +template <> +struct Get +{ + static State func (Input in, IPv4Address& t) + { + State state; + UInt8Str digit [4]; + if (! read (in, digit [0], state)) + return state; + if (! match (in, ".", state)) + return state; + if (! read (in, digit [1], state)) + return state; + if (! match (in, ".", state)) + return state; + if (! read (in, digit [2], state)) + return state; + if (! match (in, ".", state)) + return state; + if (! read (in, digit [3], state)) + return state; + + t.value [0] = digit [0].value; + t.value [1] = digit [1].value; + t.value [2] = digit [2].value; + t.value [3] = digit [3].value; + + return State::pass; + } +}; + +//------------------------------------------------------------------------------ + +} + +#endif diff --git a/modules/beast_asio/handshake/beast_ProxyHandshake.cpp b/modules/beast_asio/handshake/beast_ProxyHandshake.cpp deleted file mode 100644 index 94856fcbc..000000000 --- a/modules/beast_asio/handshake/beast_ProxyHandshake.cpp +++ /dev/null @@ -1,384 +0,0 @@ -//------------------------------------------------------------------------------ -/* - 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 (std::size_t 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 (std::size_t 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/handshake/beast_ProxyHandshake.h b/modules/beast_asio/handshake/beast_ProxyHandshake.h deleted file mode 100644 index eea2e480d..000000000 --- a/modules/beast_asio/handshake/beast_ProxyHandshake.h +++ /dev/null @@ -1,164 +0,0 @@ -//------------------------------------------------------------------------------ -/* - 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 diff --git a/modules/beast_asio/tests/beast_TestPeerLogicProxyClient.cpp b/modules/beast_asio/tests/beast_TestPeerLogicProxyClient.cpp index 1f9a5ae81..e7a438d1d 100644 --- a/modules/beast_asio/tests/beast_TestPeerLogicProxyClient.cpp +++ b/modules/beast_asio/tests/beast_TestPeerLogicProxyClient.cpp @@ -24,7 +24,7 @@ TestPeerLogicProxyClient::TestPeerLogicProxyClient (Socket& socket) void TestPeerLogicProxyClient::on_pre_handshake () { - ProxyHandshake h; + //ProxyHandshakeParser h; static std::string line ( "PROXY TCP4 255.255.255.255 255.255.255.255 65535 65535\r\n"