Skip to content

Commit

Permalink
Merge branch 'feature/socks_transport' into 'master'
Browse files Browse the repository at this point in the history
tcp_transport: Adds SOCKS4 proxy transport

See merge request espressif/esp-idf!20479
  • Loading branch information
euripedesrocha committed Mar 28, 2023
2 parents 7c5d65b + d6db90a commit 98b7572
Show file tree
Hide file tree
Showing 23 changed files with 718 additions and 36 deletions.
7 changes: 7 additions & 0 deletions .gitlab/ci/host-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,13 @@ test_mqtt_on_host:
- idf.py build
- LSAN_OPTIONS=verbosity=1:log_threads=1 build/host_mqtt_client_test.elf

test_transport_on_host:
extends: .host_test_template
script:
- cd ${IDF_PATH}/components/tcp_transport/host_test
- idf.py build
- LSAN_OPTIONS=verbosity=1:log_threads=1 build/host_tcp_transport_test.elf

test_sockets_on_host:
extends: .host_test_template
script:
Expand Down
7 changes: 6 additions & 1 deletion components/tcp_transport/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,19 @@ set(srcs
"transport_ssl.c"
"transport_internal.c")

if(CONFIG_LWIP_IPV4)
list(APPEND srcs
"transport_socks_proxy.c")
endif()

if(CONFIG_WS_TRANSPORT)
list(APPEND srcs
"transport_ws.c")
endif()

set(req esp-tls)
if(NOT ${IDF_TARGET} STREQUAL "linux")
list(APPEND req lwip)
list(APPEND req lwip esp_timer)
endif()

idf_component_register(SRCS "${srcs}"
Expand Down
14 changes: 14 additions & 0 deletions components/tcp_transport/host_test/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
cmake_minimum_required(VERSION 3.16)

set(COMPONENTS esp_timer lwip tcp_transport main)

list(APPEND EXTRA_COMPONENT_DIRS
"$ENV{IDF_PATH}/tools/mocks/lwip/"
"$ENV{IDF_PATH}/tools/mocks/freertos/"
"$ENV{IDF_PATH}/tools/mocks/esp_timer/"
"$ENV{IDF_PATH}/tools/mocks/esp-tls/"
)

include($ENV{IDF_PATH}/tools/cmake/project.cmake)

project(host_tcp_transport_test)
29 changes: 29 additions & 0 deletions components/tcp_transport/host_test/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
| Supported Targets | Linux |
| ----------------- | ----- |

# Description

This directory contains test code for `tcp_transport` that runs on host.

Tests are written using [Catch2](https://github.com/catchorg/Catch2) test framework

# Build

Tests build regularly like an idf project.

```
idf.py build
```

# Run

The build produces an executable in the build folder.

Just run:

```
./build/host_tcp_transport_test.elf
```

The test executable have some options provided by the test framework.

Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# NOTE: This kind of mocking currently works on Linux targets only.
# On Espressif chips, too many dependencies are missing at the moment.

idf_component_mock(INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}
REQUIRES tcp_transport
MOCK_HEADER_FILES ${CMAKE_CURRENT_SOURCE_DIR}/mock_transport.h)
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
:cmock:
:plugins:
- expect
- expect_any_args
- return_thru_ptr
- array
- ignore
- ignore_arg
- callback
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once

#include "esp_transport.h"

int mock_connect(esp_transport_handle_t t, const char *host, int port, int timeout_ms);

int mock_close(esp_transport_handle_t t);

int mock_write(esp_transport_handle_t t, const char *buffer, int len, int timeout_ms);

int mock_read(esp_transport_handle_t t, char *buffer, int len, int timeout_ms);

int mock_poll_read(esp_transport_handle_t t, int timeout_ms);

int mock_poll_write(esp_transport_handle_t t, int timeout_ms);

esp_err_t mock_destroy(esp_transport_handle_t t);
12 changes: 12 additions & 0 deletions components/tcp_transport/host_test/main/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
idf_component_register(SRCS "test_socks_transport.cpp" "catch_main.cpp"
REQUIRES tcp_transport mocked_transport
INCLUDE_DIRS "$ENV{IDF_PATH}/tools"
WHOLE_ARCHIVE)

idf_component_get_property(lwip_component lwip COMPONENT_LIB)
idf_component_get_property(esp_timer_component esp_timer COMPONENT_LIB)
idf_component_get_property(tcp_transport_component tcp_transport COMPONENT_LIB)
target_link_libraries(${tcp_transport_component} PUBLIC ${lwip_component} ${esp_timer_component})
target_compile_options(${COMPONENT_LIB} PUBLIC -fsanitize=address -fconcepts)
target_link_options(${COMPONENT_LIB} PUBLIC -fsanitize=address)
set_property(TARGET ${COMPONENT_LIB} PROPERTY CXX_STANDARD 20)
7 changes: 7 additions & 0 deletions components/tcp_transport/host_test/main/catch_main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#define CATCH_CONFIG_MAIN
#include "catch/catch.hpp"
4 changes: 4 additions & 0 deletions components/tcp_transport/host_test/main/idf_component.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
dependencies:
espressif/fmt: "^9.1.0"
idf:
version: ">=4.1.0"
185 changes: 185 additions & 0 deletions components/tcp_transport/host_test/main/test_socks_transport.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
/*
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <cstdint>
#include <cstdlib>
#include <cstddef>
#include <memory>
#include <string>
#include <type_traits>
#include <array>
#include <vector>
#include <netinet/in.h>
#include <sys/socket.h>
#include <netdb.h>
#include "fmt/core.h"
#include "fmt/ranges.h"
#include "catch/catch.hpp"
#include "esp_transport.h"
#include "esp_transport_socks_proxy.h"

extern "C" {
#include "Mockmock_transport.h"
#include "Mocknetdb.h"
#include "Mockesp_timer.h"

uint16_t lwip_htons(uint16_t n)
{
return __builtin_bswap16(n);
}
}

using unique_transport = std::unique_ptr<std::remove_pointer_t<esp_transport_handle_t>, decltype(&esp_transport_destroy)>;
using namespace std::literals;

namespace {

/*
* Makes possible to pass a capturing lambda as a callback
*/
decltype(auto) capture_lambda(auto callable)
{
// make a static copy of the lambda to extend it's lifetime and avoid the capture.
[[maybe_unused]]static auto call = callable;
return []<typename... Args>(Args... args) {
return call(args...);
};
}

auto make_response(socks_transport_error_t response)
{
return std::array<char, 8>({0x00, static_cast<char>(response), 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 });
}
}

TEST_CASE("Initial", "[Initialization]")
{
esp_transport_socks_proxy_config_t config{ .version = SOCKS4,
.address = "test_socks4_proxy",
.port = 1080};

mock_destroy_IgnoreAndReturn(ESP_OK);
unique_transport test_parent{esp_transport_init(), esp_transport_destroy};
esp_transport_set_func(test_parent.get(), mock_connect, mock_read, mock_write, mock_close, mock_poll_read, mock_poll_write, mock_destroy);

SECTION("Initialize with invalid parent transport") {
esp_transport_handle_t parent_handle = nullptr;
unique_transport socks_transport{esp_transport_socks_proxy_init(parent_handle, &config), esp_transport_destroy};
REQUIRE(socks_transport == nullptr);
}

SECTION("Initialize with NULL config") {
auto *socks_transport = esp_transport_socks_proxy_init(test_parent.get(), nullptr);
REQUIRE(socks_transport == nullptr);
}

SECTION("Initialize with NULL address config") {
config.address = nullptr;
auto *socks_transport = esp_transport_socks_proxy_init(test_parent.get(), &config);
REQUIRE(socks_transport == nullptr);
}

SECTION("Successful Initialization") {
auto *socks_transport = esp_transport_socks_proxy_init(test_parent.get(), &config);
REQUIRE(socks_transport != nullptr);
esp_transport_destroy(socks_transport);
}
}

TEST_CASE("Requests to Proxy", "[Requests]")
{
constexpr auto timeout = 50;
esp_transport_socks_proxy_config_t config{ .version = SOCKS4,
.address = "test_socks4_proxy",
.port = 1080};

auto test_target = "test_target"sv;
auto target_port = 80;
unique_transport test_parent{esp_transport_init(), esp_transport_destroy};
REQUIRE(test_parent);
esp_transport_set_func(test_parent.get(), mock_connect, mock_read, mock_write, mock_close, mock_poll_read, mock_poll_write, mock_destroy);
unique_transport socks_transport{esp_transport_socks_proxy_init(test_parent.get(), &config), esp_transport_destroy};

mock_destroy_IgnoreAndReturn(ESP_OK);
esp_timer_get_time_IgnoreAndReturn(0);

SECTION("Failure to connect to proxy") {
mock_connect_ExpectAndReturn(test_parent.get(), config.address, config.port, timeout, -1);
REQUIRE(esp_transport_connect(socks_transport.get(), test_target.data(), target_port, timeout) == -1);
}

GIVEN("Proxy accepted the connection") {

mock_connect_ExpectAndReturn(test_parent.get(), config.address, config.port, timeout, 0);
auto expect_addr_info = [](std::string_view test_target, int return_value) {
lwip_getaddrinfo_ExpectAndReturn(test_target.data(), nullptr, nullptr, nullptr, return_value);
lwip_getaddrinfo_IgnoreArg_hints();
lwip_getaddrinfo_IgnoreArg_res();
struct addrinfo addr_info = {};
struct sockaddr_in sockaddr = {};
sockaddr.sin_addr.s_addr = 0x5a5a5a5a;
lwip_freeaddrinfo_Ignore();
return std::tuple{addr_info, sockaddr};
};

SECTION("Failure to resolve target") {
auto [addr_info, sockaddr] = expect_addr_info(test_target, EAI_NONAME);
addr_info.ai_addr = reinterpret_cast<struct sockaddr *>(&sockaddr);
auto *p_addr_info = &addr_info;
lwip_getaddrinfo_ReturnThruPtr_res(&p_addr_info);
REQUIRE(esp_transport_connect(socks_transport.get(), "test_target", 8080, timeout) == -1);
REQUIRE(errno == SOCKS_RESPONSE_TARGET_NOT_FOUND);
}

GIVEN("Success on target resolution") {
auto [addr_info, sockaddr] = expect_addr_info(test_target, 0);
addr_info.ai_addr = reinterpret_cast<struct sockaddr *>(&sockaddr);
auto *p_addr_info = &addr_info;
lwip_getaddrinfo_ReturnThruPtr_res(&p_addr_info);
auto expected_request = std::array<char,9>{0x04, 0x01, 0x00, 0x50, 0x5a, 0x5a, 0x5a, 0x5a, 0x00 };
mock_write_Stub(capture_lambda([&test_parent, expected_request, &timeout](esp_transport_handle_t transport, const char *request_sent, int len, int timeout_ms, [[maybe_unused]]int num_call) {
using namespace Catch::Matchers;
REQUIRE(transport == test_parent.get());
REQUIRE(len == expected_request.size());
REQUIRE(timeout_ms == timeout);
REQUIRE(std::equal(request_sent,request_sent+len, std::begin(expected_request), std::end(expected_request)));
return len;
}));

SECTION("Successful connection request") {

auto proxy_response = make_response(SOCKS_RESPONSE_SUCCESS);


mock_read_ExpectAndReturn(test_parent.get(), proxy_response.data(), proxy_response.size(), timeout, proxy_response.size());
mock_read_IgnoreArg_buffer();
mock_read_ReturnArrayThruPtr_buffer(proxy_response.data(), proxy_response.size());
REQUIRE(esp_transport_connect(socks_transport.get(), test_target.data(), target_port, timeout) == 0);
};

SECTION("Proxy rejected request") {
auto proxy_response = make_response(SOCKS_RESPONSE_REQUEST_REJECTED);

mock_read_ExpectAndReturn(test_parent.get(), proxy_response.data(), proxy_response.size(), timeout, proxy_response.size());
mock_read_IgnoreArg_buffer();
mock_read_ReturnArrayThruPtr_buffer(proxy_response.data(), proxy_response.size());
REQUIRE(esp_transport_connect(socks_transport.get(), test_target.data(), target_port, timeout) == -1);
REQUIRE(errno == SOCKS_RESPONSE_REQUEST_REJECTED);
}

SECTION("Client not running identification protocol") {
auto proxy_response = make_response(SOCKS_RESPONSE_NOT_RUNNING_IDENTD);

mock_read_ExpectAndReturn(test_parent.get(), proxy_response.data(), proxy_response.size(), timeout, proxy_response.size());
mock_read_IgnoreArg_buffer();
mock_read_ReturnArrayThruPtr_buffer(proxy_response.data(), proxy_response.size());
REQUIRE(esp_transport_connect(socks_transport.get(), test_target.data(), target_port, timeout) == -1);
REQUIRE(errno == SOCKS_RESPONSE_NOT_RUNNING_IDENTD);
}

}

}
}
3 changes: 3 additions & 0 deletions components/tcp_transport/host_test/sdkconfig.defaults
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
CONFIG_IDF_TARGET="linux"
CONFIG_COMPILER_CXX_EXCEPTIONS=y
CONFIG_UNITY_ENABLE_IDF_TEST_RUNNER=n
61 changes: 61 additions & 0 deletions components/tcp_transport/include/esp_transport_socks_proxy.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once

#include <stdint.h>
#include "esp_transport.h"

#ifdef __cplusplus
extern "C" {
#endif

typedef enum socks_version_t {SOCKS4 = 4} socks_version_t;

typedef enum socks_transport_response_t {
// The following values correspond to transport operation
SOCKS_RESPONSE_TARGET_NOT_FOUND = 0xF0,
SOCKS_RESPONSE_PROXY_UNREACHABLE = 0xF1,
SOCKS_TIMEOUT = 0xF2,
// The following values are defined by the SOCKS4 protocol
SOCKS_RESPONSE_SUCCESS = 0x5a,
SOCKS_RESPONSE_REQUEST_REJECTED = 0x5B,
SOCKS_RESPONSE_NOT_RUNNING_IDENTD = 0x5c,
SOCKS_RESPONSE_COULD_NOT_CONFIRM_ID = 0x5d,
} socks_transport_error_t;

/*
* Socks configuration structure
*/
typedef struct esp_transport_socks_proxy_config_t {
const socks_version_t version; /*!< Socks protocol version.*/
const char *address;/*!< Proxy address*/
const int port; /*< Proxy port*/
} esp_transport_socks_proxy_config_t;

/**
* @brief Create a proxy transport
* @param parent_handle Handle for the parent transport
* @param config Pointer to the configuration structure to use
*
* @return
* - transport Handler for the created transport.
* - NULL in case of failure
*/
esp_transport_handle_t esp_transport_socks_proxy_init(esp_transport_handle_t parent_handle, const esp_transport_socks_proxy_config_t *config);

/**
* @brief Changes the configuration of the proxy
* @param socks_transport Handle for the transport
* @param config Pointer to the configuration structure to use
*
* @return
* - ESP_OK on success
*/
esp_err_t esp_transport_socks_proxy_set_config(esp_transport_handle_t socks_transport, const esp_transport_socks_proxy_config_t *config);

#ifdef __cplusplus
}
#endif
Loading

0 comments on commit 98b7572

Please sign in to comment.