From 74dfd4803b84a91fc07cb06580b4c5cfff969b5f Mon Sep 17 00:00:00 2001 From: oneiric Date: Wed, 18 Jul 2018 00:02:09 +0000 Subject: [PATCH 1/4] SocksProxy: make handler functions testable Changes access restriction on SOCKS handler members to make them testable. --- src/client/proxy/socks.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/client/proxy/socks.h b/src/client/proxy/socks.h index 47c0aab6..d41e86f0 100644 --- a/src/client/proxy/socks.h +++ b/src/client/proxy/socks.h @@ -81,7 +81,7 @@ struct SOCKSDNSAddress { class SOCKSHandler : public kovri::client::I2PServiceHandler, public std::enable_shared_from_this { - private: + protected: /// @enum State /// @brief Enumerators used for parsing data to /// fetch the corresponding variable @@ -304,6 +304,7 @@ class SOCKSHandler void HandleStreamRequestComplete( std::shared_ptr stream); + private: std::uint8_t m_SocketBuffer[MAX_SOCKS_BUFFER_SIZE]; std::shared_ptr m_Socket; std::shared_ptr m_Stream; From 418ea8f347e8d3d23aa7feef771a2900e601ed0c Mon Sep 17 00:00:00 2001 From: oneiric Date: Wed, 18 Jul 2018 00:53:55 +0000 Subject: [PATCH 2/4] SocksProxy: check for null socket in handler Performs checks before dereferencing the handler's socket pointer. --- src/client/proxy/socks.cc | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/client/proxy/socks.cc b/src/client/proxy/socks.cc index 30b47981..797e9ec6 100644 --- a/src/client/proxy/socks.cc +++ b/src/client/proxy/socks.cc @@ -62,6 +62,12 @@ SOCKSHandler::SOCKSHandler( m_AddressType(IPv4), m_SOCKSVersion(SOCKS5), m_Command(Connect) { + if (!parent) + throw std::invalid_argument( + __func__ + std::string(": null server")); + if (!socket) + throw std::invalid_argument( + __func__ + std::string(": null socket")); m_Address.ip = 0; EnterState(GetSOCKSVersion); } From d4fd487a0641b031e98e2319bc2076ba8ea22f82 Mon Sep 17 00:00:00 2001 From: oneiric Date: Wed, 18 Jul 2018 06:01:48 +0000 Subject: [PATCH 3/4] SocksProxy: inline one-line definitions Merge one-line definitions with declarations in the header file. --- src/client/proxy/socks.cc | 10 ---------- src/client/proxy/socks.h | 12 +++++++++--- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/src/client/proxy/socks.cc b/src/client/proxy/socks.cc index 797e9ec6..2183284f 100644 --- a/src/client/proxy/socks.cc +++ b/src/client/proxy/socks.cc @@ -610,15 +610,5 @@ void SOCKSDNSAddress::FromString(std::string str) memcpy(value, str.c_str(), size); } -std::string SOCKSDNSAddress::ToString() -{ - return std::string(value, size); -} - -void SOCKSDNSAddress::PushBack(char c) -{ - value[size++] = c; -} - } // namespace client } // namespace kovri diff --git a/src/client/proxy/socks.h b/src/client/proxy/socks.h index d41e86f0..4b519563 100644 --- a/src/client/proxy/socks.h +++ b/src/client/proxy/socks.h @@ -73,9 +73,15 @@ struct SOCKSDNSAddress { void FromString(std::string str); - std::string ToString(); - - void PushBack(char c); + std::string ToString() + { + return std::string(value, size); + } + + void PushBack(char c) + { + value[size++] = c; + } }; class SOCKSHandler From 60f0e5b6633693fde322798422910c33998bd846 Mon Sep 17 00:00:00 2001 From: oneiric Date: Tue, 17 Jul 2018 06:03:01 +0000 Subject: [PATCH 4/4] Tests: SOCKS: Socks proxy unit-tests --- tests/unit_tests/CMakeLists.txt | 2 + tests/unit_tests/client/proxy/socks.cc | 269 +++++++++++++++++++++++++ tests/unit_tests/client/proxy/socks.h | 178 ++++++++++++++++ 3 files changed, 449 insertions(+) create mode 100644 tests/unit_tests/client/proxy/socks.cc create mode 100644 tests/unit_tests/client/proxy/socks.h diff --git a/tests/unit_tests/CMakeLists.txt b/tests/unit_tests/CMakeLists.txt index 4ed5d4fe..55a0a2c3 100644 --- a/tests/unit_tests/CMakeLists.txt +++ b/tests/unit_tests/CMakeLists.txt @@ -8,6 +8,8 @@ add_executable(kovri-tests client/api/i2p_control/parser.cc client/api/streaming.cc client/proxy/http.cc + client/proxy/socks.cc + client/proxy/socks.h client/reseed.cc client/util/http.cc client/util/parse.cc diff --git a/tests/unit_tests/client/proxy/socks.cc b/tests/unit_tests/client/proxy/socks.cc new file mode 100644 index 00000000..9fd21d52 --- /dev/null +++ b/tests/unit_tests/client/proxy/socks.cc @@ -0,0 +1,269 @@ +/** // + * Copyright (c) 2013-2018, The Kovri I2P Router Project // + * // + * All rights reserved. // + * // + * Redistribution and use in source and binary forms, with or without modification, are // + * permitted provided that the following conditions are met: // + * // + * 1. Redistributions of source code must retain the above copyright notice, this list of // + * conditions and the following disclaimer. // + * // + * 2. Redistributions in binary form must reproduce the above copyright notice, this list // + * of conditions and the following disclaimer in the documentation and/or other // + * materials provided with the distribution. // + * // + * 3. Neither the name of the copyright holder nor the names of its contributors may be // + * used to endorse or promote products derived from this software without specific // + * prior written permission. // + * // + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY // + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF // + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL // + * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, // + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, // + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF // + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // + * // + * Parts of the project are originally copyright (c) 2013-2015 The PurpleI2P Project // + */ + +#include "tests/unit_tests/client/proxy/socks.h" + +SOCKSProxyFixture::SOCKSProxyFixture() +{ + dest = std::make_shared( + core::PrivateKeys::CreateRandomKeys( + core::DEFAULT_CLIENT_SIGNING_KEY_TYPE), + false, + nullptr); + server = std::make_unique("127.0.0.1", 0, dest); + socket = std::make_shared(dest->GetService()); + handler = std::make_shared(server.get(), socket); +} + +auto SOCKSProxyFixture::StubHandler::GenerateResponse( + SOCKSVersions version, + ErrorTypes error, + AddressTypes type, + const std::uint32_t ip, + const std::array ipv6, + const std::string& dns, + const std::uint16_t port) +{ + StubHandler::Address address; + address.ip = ip; + std::copy(ipv6.begin(), ipv6.end(), address.ipv6); + address.dns.FromString(dns); + + return version == SOCKSVersions::SOCKS4 + ? client::SOCKSHandler::GenerateSOCKS4Response( + error, ip, port) + : client::SOCKSHandler::GenerateSOCKS5Response( + error, type, address, port); +} + +bool SOCKSProxyFixture::StubHandler::HandleData(std::uint8_t* buf, const std::size_t len) +{ + return client::SOCKSHandler::HandleData(buf, len); +} + +void SOCKSProxyFixture::StubHandler::CheckResponse( + const StubHandler::SOCKSVersions version, + const StubHandler::ErrorTypes error) +{ + auto response = GenerateResponse(version, error); + const auto buf = boost::asio::buffer_cast(response); + const std::size_t size = boost::asio::buffer_size(response); + + BOOST_CHECK(buf && size); + + auto res = + version == StubHandler::SOCKSVersions::SOCKS4 + ? std::vector(v4_res.begin(), v4_res.end()) + : std::vector(v5_dns_res.begin(), v5_dns_res.end()); + + // Set error code in response + res[1] = error; + + BOOST_CHECK_EQUAL_COLLECTIONS(buf, buf + size, res.begin(), res.end()); +} + +bool SOCKSProxyFixture::StubHandler::CheckHandleData( + const SOCKSVersions version, + const AddressTypes type, + const CommandTypes cmd) +{ + std::vector req; + + if (version == SOCKSVersions::SOCKS4) + { + if (type == AddressTypes::IPv4) + { + req = std::vector(v4_req.begin(), v4_req.end()); + } + else + { + req = std::vector(v4a_req.begin(), v4a_req.end()); + } + // Set state machine to process connect request + client::SOCKSHandler::EnterState(StubHandler::State::GetSOCKSVersion); + } + else + { + switch (type) + { + case StubHandler::AddressTypes::IPv4: + req = std::vector( + v5_ipv4_req.begin(), v5_ipv4_req.end()); + break; + case StubHandler::AddressTypes::DNS: + req = + std::vector(v5_dns_req.begin(), v5_dns_req.end()); + break; + case StubHandler::AddressTypes::IPv6: + req = std::vector( + v5_ipv6_req.begin(), v5_ipv6_req.end()); + break; + default: + break; + } + // Set state machine to process connect request + client::SOCKSHandler::EnterState( + StubHandler::State::GetSOCKS5RequestVersion); + } + + // Change default command + req[1] = cmd; + + bool success; + BOOST_CHECK_NO_THROW( + success = client::SOCKSHandler::HandleData(req.data(), req.size())); + return success; +} + +bool SOCKSProxyFixture::StubHandler::CheckSOCKS5Auth(const AuthMethods auth) +{ + bool success; + v5_greet_req[2] = auth; + + // Set state machine to process auth request + client::SOCKSHandler::EnterState(State::GetSOCKSVersion); + + BOOST_CHECK_NO_THROW( + success = client::SOCKSHandler::HandleData( + v5_greet_req.data(), v5_greet_req.size())); + + return success; +} + +BOOST_FIXTURE_TEST_SUITE(SOCKSProxyTests, SOCKSProxyFixture) + +BOOST_AUTO_TEST_CASE(GoodSOCKS4Response) +{ + handler->CheckResponse(v4, StubHandler::ErrorTypes::SOCKS4Success); +} + +BOOST_AUTO_TEST_CASE(FailSOCKS4Response) +{ + handler->CheckResponse(v4, StubHandler::ErrorTypes::SOCKS4Fail); + handler->CheckResponse(v4, StubHandler::ErrorTypes::SOCKS4MissingIdent); + handler->CheckResponse(v4, StubHandler::ErrorTypes::SOCKS4InvalidIdent); +} + +BOOST_AUTO_TEST_CASE(GoodSOCKS5Response) +{ + handler->CheckResponse(v5, StubHandler::ErrorTypes::SOCKS5Success); +} + +BOOST_AUTO_TEST_CASE(FailSOCKS5Response) +{ + handler->CheckResponse(v5, StubHandler::ErrorTypes::SOCKS5Fail); + handler->CheckResponse(v5, StubHandler::ErrorTypes::SOCKS5RuleDenied); + handler->CheckResponse(v5, StubHandler::ErrorTypes::SOCKS5NetworkUnreachable); + handler->CheckResponse(v5, StubHandler::ErrorTypes::SOCKS5HostUnreachable); + handler->CheckResponse(v5, StubHandler::ErrorTypes::SOCKS5ConnectionRefused); + handler->CheckResponse(v5, StubHandler::ErrorTypes::SOCKS5Expired); + handler->CheckResponse(v5, StubHandler::ErrorTypes::SOCKS5UnsupportedCommand); + handler->CheckResponse(v5, StubHandler::ErrorTypes::SOCKS5UnsupportedAddress); +} + +BOOST_AUTO_TEST_CASE(GoodSOCKS4aRequest) +{ + BOOST_CHECK( + handler->CheckHandleData(v4, dns, StubHandler::CommandTypes::Connect)); +} + +BOOST_AUTO_TEST_CASE(GoodSOCKS5Request) +{ + BOOST_CHECK( + handler->CheckHandleData(v5, dns, StubHandler::CommandTypes::Connect)); +} + +BOOST_AUTO_TEST_CASE(UnimplementedSOCKS4a) +{ + // TODO(oneiric): implement BIND command + BOOST_CHECK( + !handler->CheckHandleData(v4, dns, StubHandler::CommandTypes::Bind)); +} + +BOOST_AUTO_TEST_CASE(UnimplementedSOCKS5) +{ + // TODO(oneiric): implement BIND and UDP commands + BOOST_CHECK( + !handler->CheckHandleData(v5, dns, StubHandler::CommandTypes::Bind)); + BOOST_CHECK( + !handler->CheckHandleData(v5, dns, StubHandler::CommandTypes::UDP)); +} + +BOOST_AUTO_TEST_CASE(UnsupportedSOCKS4) +{ + // SOCKS4 unsupported, no DNS option + // IPv4 unsupported, cannot connect to raw IP in-net + BOOST_CHECK( + !handler->CheckHandleData(v4, ipv4, StubHandler::CommandTypes::Connect)); + BOOST_CHECK( + !handler->CheckHandleData(v4, ipv4, StubHandler::CommandTypes::Bind)); +} + +BOOST_AUTO_TEST_CASE(UnsupportedSOCKS5) +{ + // IPv4 unsupported, cannot connect to raw IP in-net + BOOST_CHECK( + !handler->CheckHandleData(v5, ipv4, StubHandler::CommandTypes::Connect)); + BOOST_CHECK( + !handler->CheckHandleData(v5, ipv4, StubHandler::CommandTypes::Bind)); + BOOST_CHECK( + !handler->CheckHandleData(v5, ipv4, StubHandler::CommandTypes::UDP)); + + // IPv6 unsupported, cannot connect to raw IP in-net + BOOST_CHECK( + !handler->CheckHandleData(v5, ipv6, StubHandler::CommandTypes::Connect)); + BOOST_CHECK( + !handler->CheckHandleData(v5, ipv6, StubHandler::CommandTypes::Bind)); + BOOST_CHECK( + !handler->CheckHandleData(v5, ipv6, StubHandler::CommandTypes::UDP)); +} + +BOOST_AUTO_TEST_CASE(GoodSOCKS5Auth) +{ + BOOST_CHECK(handler->CheckSOCKS5Auth(StubHandler::AuthMethods::None)); +} + +BOOST_AUTO_TEST_CASE(UnimplementedSOCKS5Auth) +{ + // TODO(oneiric): implement GSSAPI authentication + BOOST_CHECK(!handler->CheckSOCKS5Auth(StubHandler::AuthMethods::GSSAPI)); + // TODO(oneiric): implement user-password authentication + BOOST_CHECK( + !handler->CheckSOCKS5Auth(StubHandler::AuthMethods::UserPassword)); +} + +BOOST_AUTO_TEST_CASE(InvalidSOCKS5Auth) +{ + BOOST_CHECK(!handler->CheckSOCKS5Auth(StubHandler::AuthMethods::Invalid)); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/unit_tests/client/proxy/socks.h b/tests/unit_tests/client/proxy/socks.h new file mode 100644 index 00000000..7c7d8b61 --- /dev/null +++ b/tests/unit_tests/client/proxy/socks.h @@ -0,0 +1,178 @@ +/** // + * Copyright (c) 2013-2018, The Kovri I2P Router Project // + * // + * All rights reserved. // + * // + * Redistribution and use in source and binary forms, with or without modification, are // + * permitted provided that the following conditions are met: // + * // + * 1. Redistributions of source code must retain the above copyright notice, this list of // + * conditions and the following disclaimer. // + * // + * 2. Redistributions in binary form must reproduce the above copyright notice, this list // + * of conditions and the following disclaimer in the documentation and/or other // + * materials provided with the distribution. // + * // + * 3. Neither the name of the copyright holder nor the names of its contributors may be // + * used to endorse or promote products derived from this software without specific // + * prior written permission. // + * // + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY // + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF // + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL // + * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, // + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, // + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF // + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // + * // + * Parts of the project are originally copyright (c) 2013-2015 The PurpleI2P Project // + */ + +#ifndef TESTS_UNIT_TESTS_CLIENT_PROXY_SOCKS_H_ +#define TESTS_UNIT_TESTS_CLIENT_PROXY_SOCKS_H_ + +#include "tests/unit_tests/main.h" + +#include "client/proxy/socks.h" + +struct SOCKSProxyFixture +{ + struct StubHandler : public client::SOCKSHandler + { + StubHandler( + client::SOCKSServer* server, + std::shared_ptr socket) + : client::SOCKSHandler(server, socket) + { + } + + // Type aliases to expose protected handler enums + using State = client::SOCKSHandler::State; + using AuthMethods = client::SOCKSHandler::AuthMethods; + using AddressTypes = client::SOCKSHandler::AddressTypes; + using ErrorTypes = client::SOCKSHandler::ErrorTypes; + using CommandTypes = client::SOCKSHandler::CommandTypes; + using SOCKSVersions = client::SOCKSHandler::SOCKSVersions; + using Address = client::SOCKSHandler::Address; + + auto GenerateResponse( + SOCKSVersions version = SOCKSVersions::SOCKS5, + ErrorTypes error = ErrorTypes::SOCKS5Success, + AddressTypes type = AddressTypes::DNS, + const std::uint32_t ip = 0, + const std::array ipv6 = {}, + const std::string& dns = "kovri.i2p", + const std::uint16_t port = 0); + + bool HandleData(std::uint8_t* buf, const std::size_t len); + + void CheckResponse(const SOCKSVersions version, const ErrorTypes error); + + bool CheckHandleData( + const SOCKSVersions version, + const AddressTypes type, + const CommandTypes cmd); + + bool CheckSOCKS5Auth(const AuthMethods auth); + + std::array v4_res {{ + // version, error + 0x00, 0x00, + // port + 0x00, 0x00, + // ip + 0x00, 0x00, 0x00, 0x00 + }}; + + std::array v5_dns_res {{ + // version, error, reserved, address type + 0x05, 0x00, 0x00, 0x03, + // address length + 0x09, + // "kovri.i2p" in hex + 0x6b, 0x6f, 0x76, 0x72, 0x69, 0x2e, 0x69, 0x32, 0x70, + // port + 0x00, 0x00 + }}; + + std::array v4_req {{ + // version, command + 0x04, 0x01, + // port + 0x00, 0x00, + // IP + 0x00, 0x00, 0x00, 0x00, + // user ID: "kovri" in hex, null-terminated + 0x6b, 0x6f, 0x76, 0x72, 0x69, 0x00 + }}; + + std::array v4a_req {{ + // version, command + 0x04, 0x01, + // port + 0x00, 0x00, + // IP: intentionally invalid, see spec + 0x00, 0x00, 0x00, 0x01, + // user ID: "kovri" in hex, null-terminated + 0x6b, 0x6f, 0x76, 0x72, 0x69, 0x00, + // domain name: "kovri.i2p" in hex, null-terminated + 0x6b, 0x6f, 0x76, 0x72, 0x69, 0x2e, 0x69, 0x32, 0x70, 0x00 + }}; + + std::array v5_greet_req {{ + // version, number methods, method(s) + 0x05, 0x01, 0x00 + }}; + + std::array v5_ipv4_req {{ + // version, command, reserved + 0x05, 0x01, 0x00, + // address type + 0x01, + // IP + 0x00, 0x00, 0x00, 0x00, + // port + 0x00, 0x00 + }}; + + std::array v5_dns_req {{ + // version, command, reserved + 0x05, 0x01, 0x00, + // address type + 0x03, + // domain name: name size + "kovri.i2p" in hex + 0x09, 0x6b, 0x6f, 0x76, 0x72, 0x69, 0x2e, 0x69, 0x32, 0x70, + // port + 0x00, 0x00 + }}; + + std::array v5_ipv6_req {{ + // version, command, reserved + 0x05, 0x01, 0x00, + // address type + 0x04, + // IPv6 address + 0xfe, 0x80, 0x6f, 0x76, 0x72, 0x69, 0x2e, 0x69, + 0x32, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // port + 0x00, 0x00 + }}; + }; + + SOCKSProxyFixture(); + + std::shared_ptr dest; + std::unique_ptr server; + std::shared_ptr handler; + std::shared_ptr socket; + + const StubHandler::SOCKSVersions v4 = StubHandler::SOCKSVersions::SOCKS4; + const StubHandler::SOCKSVersions v5 = StubHandler::SOCKSVersions::SOCKS5; + const StubHandler::AddressTypes ipv4 = StubHandler::AddressTypes::IPv4; + const StubHandler::AddressTypes dns = StubHandler::AddressTypes::DNS; + const StubHandler::AddressTypes ipv6 = StubHandler::AddressTypes::IPv6; +}; + +#endif // TESTS_UNIT_TESTS_CLIENT_PROXY_SOCKS_H_