mirror of
https://github.com/espressif/esp-idf.git
synced 2024-10-05 20:47:46 -04:00
[tcp_transport] - Adds Socks4 proxy transport
- Basic implementation of Socks4 protocol. - Introduce basic host testing.
This commit is contained in:
parent
a3782377d7
commit
d6db90a3cd
@ -296,6 +296,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:
|
||||
|
@ -3,6 +3,11 @@ 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")
|
||||
@ -10,7 +15,7 @@ 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}"
|
||||
|
14
components/tcp_transport/host_test/CMakeLists.txt
Normal file
14
components/tcp_transport/host_test/CMakeLists.txt
Normal file
@ -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
components/tcp_transport/host_test/README.md
Normal file
29
components/tcp_transport/host_test/README.md
Normal file
@ -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.
|
||||
|
@ -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)
|
@ -0,0 +1,9 @@
|
||||
:cmock:
|
||||
:plugins:
|
||||
- expect
|
||||
- expect_any_args
|
||||
- return_thru_ptr
|
||||
- array
|
||||
- ignore
|
||||
- ignore_arg
|
||||
- callback
|
@ -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
components/tcp_transport/host_test/main/CMakeLists.txt
Normal file
12
components/tcp_transport/host_test/main/CMakeLists.txt
Normal file
@ -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
components/tcp_transport/host_test/main/catch_main.cpp
Normal file
7
components/tcp_transport/host_test/main/catch_main.cpp
Normal file
@ -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"
|
@ -0,0 +1,4 @@
|
||||
dependencies:
|
||||
espressif/fmt: "^9.1.0"
|
||||
idf:
|
||||
version: ">=4.1.0"
|
185
components/tcp_transport/host_test/main/test_socks_transport.cpp
Normal file
185
components/tcp_transport/host_test/main/test_socks_transport.cpp
Normal file
@ -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
components/tcp_transport/host_test/sdkconfig.defaults
Normal file
3
components/tcp_transport/host_test/sdkconfig.defaults
Normal file
@ -0,0 +1,3 @@
|
||||
CONFIG_IDF_TARGET="linux"
|
||||
CONFIG_COMPILER_CXX_EXCEPTIONS=y
|
||||
CONFIG_UNITY_ENABLE_IDF_TEST_RUNNER=n
|
61
components/tcp_transport/include/esp_transport_socks_proxy.h
Normal file
61
components/tcp_transport/include/esp_transport_socks_proxy.h
Normal file
@ -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
|
224
components/tcp_transport/transport_socks_proxy.c
Normal file
224
components/tcp_transport/transport_socks_proxy.c
Normal file
@ -0,0 +1,224 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#include <netinet/in.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <errno.h>
|
||||
#include "esp_err.h"
|
||||
#include "esp_timer.h"
|
||||
#include "esp_check.h"
|
||||
#include "esp_transport_socks_proxy.h"
|
||||
#include "netdb.h"
|
||||
#include "string.h"
|
||||
|
||||
#include "esp_log.h"
|
||||
#include "include/esp_transport.h"
|
||||
|
||||
static const char *TAG = "transport_proxy";
|
||||
static const uint32_t SOCKS4_FIX_MESSAGE_SIZE = 8;
|
||||
#define SOCKS4_RESPONSE_SIZE 8
|
||||
|
||||
#define SOCKS_ERROR_IF(cond, err_code, format, ...) do {\
|
||||
ESP_GOTO_ON_FALSE(!(cond), err_code, Error, TAG, format , ##__VA_ARGS__);\
|
||||
}while(0)
|
||||
|
||||
typedef enum {CONNECT = 0x01, BIND = 0x02} command_socks_t;
|
||||
|
||||
typedef struct {
|
||||
union {
|
||||
char *user_id;
|
||||
};
|
||||
} socks_authentication_data_t;
|
||||
|
||||
typedef struct transport_socks_t {
|
||||
socks_version_t version;
|
||||
char *proxy_address;
|
||||
uint16_t proxy_port;
|
||||
socks_authentication_data_t authentication;
|
||||
esp_transport_handle_t parent;
|
||||
} transport_socks_t;
|
||||
|
||||
typedef struct __attribute((packed)) socks_request {
|
||||
uint8_t version;
|
||||
uint8_t command;
|
||||
uint16_t destination_port;
|
||||
uint32_t destination_ip;
|
||||
} socks_request_t;
|
||||
|
||||
typedef struct __attribute((packed)) socks_response {
|
||||
uint8_t version;
|
||||
uint8_t code;
|
||||
uint16_t destination_port;
|
||||
uint32_t destination_ip;
|
||||
} socks_response_t;
|
||||
|
||||
static uint32_t get_IP(const char *const host)
|
||||
{
|
||||
struct addrinfo *address_info;
|
||||
struct addrinfo hints;
|
||||
hints.ai_family = AF_INET;
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
int res = getaddrinfo(host, NULL, &hints, &address_info);
|
||||
if (res) {
|
||||
// IP_ADDR_ANY equivalent returned as error here because it doesn't make
|
||||
// sense to connect to ANY
|
||||
return 0;
|
||||
}
|
||||
uint32_t ip = ((struct sockaddr_in *)address_info->ai_addr)->sin_addr.s_addr;
|
||||
freeaddrinfo(address_info);
|
||||
return ip;
|
||||
}
|
||||
|
||||
static int64_t get_tick(void)
|
||||
{
|
||||
return esp_timer_get_time() / (int64_t)1000;
|
||||
}
|
||||
|
||||
static int socks_connect(esp_transport_handle_t transport, const char *const host, int port, int timeout_ms)
|
||||
{
|
||||
transport_socks_t *socks_transport = esp_transport_get_context_data(transport);
|
||||
int64_t initial_tick = get_tick();
|
||||
int64_t remaining_time = timeout_ms;
|
||||
int ret = 0;
|
||||
uint32_t request_message_len = SOCKS4_FIX_MESSAGE_SIZE + 1;
|
||||
char *request_message = NULL;
|
||||
|
||||
int proxy_connected = esp_transport_connect(socks_transport->parent, socks_transport->proxy_address, socks_transport->proxy_port, remaining_time);
|
||||
SOCKS_ERROR_IF(proxy_connected == -1, esp_transport_get_errno(socks_transport->parent), "Error connecting to proxy");
|
||||
|
||||
if (remaining_time > -1) {
|
||||
remaining_time = (int64_t)timeout_ms - (get_tick() - initial_tick);
|
||||
SOCKS_ERROR_IF(remaining_time < 0, SOCKS_TIMEOUT, "Connection Timeout");
|
||||
}
|
||||
|
||||
socks_request_t request = {};
|
||||
request.version = socks_transport->version;
|
||||
request.command = CONNECT;
|
||||
request.destination_port = htons(port);
|
||||
request.destination_ip = get_IP(host);
|
||||
SOCKS_ERROR_IF(request.destination_ip == 0, SOCKS_RESPONSE_TARGET_NOT_FOUND, "Failed to resolve target address");
|
||||
|
||||
if (socks_transport->authentication.user_id) {
|
||||
request_message_len += strlen(socks_transport->authentication.user_id);
|
||||
request_message = calloc(request_message_len, sizeof(char));
|
||||
if (request_message) {
|
||||
strcpy(request_message + sizeof(socks_request_t) +1, socks_transport->authentication.user_id);
|
||||
}
|
||||
} else {
|
||||
request_message = calloc(request_message_len, sizeof(char));
|
||||
}
|
||||
SOCKS_ERROR_IF(request_message == NULL, ESP_ERR_NO_MEM, "Failed to allocate request message");
|
||||
memcpy(request_message, (char *)&request, sizeof(socks_request_t));
|
||||
|
||||
SOCKS_ERROR_IF(esp_transport_write(socks_transport->parent, request_message, request_message_len, remaining_time) < 0, esp_transport_get_errno(socks_transport->parent), "Failed to write the request message");
|
||||
|
||||
if (remaining_time > -1) {
|
||||
remaining_time = (int64_t)timeout_ms - (get_tick() - initial_tick);
|
||||
SOCKS_ERROR_IF(remaining_time < 0, SOCKS_TIMEOUT, "Connection Timeout");
|
||||
}
|
||||
|
||||
char proxy_response[SOCKS4_RESPONSE_SIZE];
|
||||
SOCKS_ERROR_IF(esp_transport_read(socks_transport->parent, proxy_response, SOCKS4_RESPONSE_SIZE, remaining_time) < 0, esp_transport_get_errno(socks_transport->parent), "Failed to get a response");
|
||||
|
||||
socks_response_t *response = (socks_response_t *)proxy_response;
|
||||
SOCKS_ERROR_IF(response->code != SOCKS_RESPONSE_SUCCESS, response->code, "Request Rejected with : %02x", response->code);
|
||||
free(request_message);
|
||||
return 0;
|
||||
Error:
|
||||
free(request_message);
|
||||
errno = ret;
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int socks_close(esp_transport_handle_t transport)
|
||||
{
|
||||
transport_socks_t *socks_transport = esp_transport_get_context_data(transport);
|
||||
return esp_transport_close(socks_transport->parent);
|
||||
|
||||
}
|
||||
|
||||
static int socks_write(esp_transport_handle_t transport, const char *buffer, int len, int timeout_ms)
|
||||
{
|
||||
transport_socks_t *socks_transport = esp_transport_get_context_data(transport);
|
||||
return esp_transport_write(socks_transport->parent, buffer, len, timeout_ms);
|
||||
}
|
||||
|
||||
static int socks_read(esp_transport_handle_t transport, char *buffer, int len, int timeout_ms)
|
||||
{
|
||||
transport_socks_t *socks_transport = esp_transport_get_context_data(transport);
|
||||
return esp_transport_read(socks_transport->parent, buffer, len, timeout_ms);
|
||||
}
|
||||
|
||||
static int socks_poll_read(esp_transport_handle_t transport, int timeout_ms)
|
||||
{
|
||||
transport_socks_t *socks_transport = esp_transport_get_context_data(transport);
|
||||
return esp_transport_poll_read(socks_transport->parent, timeout_ms);
|
||||
}
|
||||
|
||||
static int socks_poll_write(esp_transport_handle_t transport, int timeout_ms)
|
||||
{
|
||||
transport_socks_t *socks_transport = esp_transport_get_context_data(transport);
|
||||
return esp_transport_poll_write(socks_transport->parent, timeout_ms);
|
||||
}
|
||||
|
||||
static esp_err_t socks_destroy(esp_transport_handle_t transport)
|
||||
{
|
||||
if (transport == NULL) {
|
||||
return ESP_OK;
|
||||
}
|
||||
transport_socks_t *socks_transport = esp_transport_get_context_data(transport);
|
||||
if (socks_transport == NULL) {
|
||||
return ESP_FAIL;
|
||||
}
|
||||
free(socks_transport->proxy_address);
|
||||
free(socks_transport);
|
||||
|
||||
return ESP_OK;
|
||||
|
||||
}
|
||||
static esp_err_t check_parameters(esp_transport_handle_t parent_handle, const esp_transport_socks_proxy_config_t *config)
|
||||
{
|
||||
if (parent_handle == NULL) {
|
||||
ESP_LOGE(TAG, "Parent transport is invalid");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
if (config == NULL || config->address == NULL) {
|
||||
ESP_LOGE(TAG, "Invalid Configuration");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_transport_handle_t esp_transport_socks_proxy_init(esp_transport_handle_t parent_handle, const esp_transport_socks_proxy_config_t *config)
|
||||
{
|
||||
if (check_parameters(parent_handle, config) == ESP_FAIL) {
|
||||
return NULL;
|
||||
};
|
||||
|
||||
int ret = 0;
|
||||
|
||||
esp_transport_handle_t transport = esp_transport_init();
|
||||
SOCKS_ERROR_IF(transport == NULL, ESP_ERR_NO_MEM, "Failed to allocate transport");
|
||||
|
||||
transport_socks_t *socks_context = calloc(1, sizeof(transport_socks_t));
|
||||
SOCKS_ERROR_IF(socks_context == NULL, ESP_ERR_NO_MEM, "Failed to allocate transport context");
|
||||
esp_transport_set_context_data(transport, socks_context);
|
||||
esp_transport_set_func(transport, socks_connect, socks_read, socks_write, socks_close, socks_poll_read, socks_poll_write, socks_destroy);
|
||||
|
||||
socks_context->parent = parent_handle;
|
||||
socks_context->proxy_address = strdup(config->address);
|
||||
SOCKS_ERROR_IF(socks_context->proxy_address == NULL, ESP_ERR_NO_MEM, "Failed to copy proxy address");
|
||||
socks_context->proxy_port = config->port;
|
||||
socks_context->version = config->version;
|
||||
|
||||
|
||||
return transport;
|
||||
Error:
|
||||
esp_transport_destroy(transport);
|
||||
errno = ret;
|
||||
return NULL;
|
||||
}
|
@ -6,6 +6,7 @@
|
||||
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "esp_tls.h"
|
||||
@ -14,7 +15,6 @@
|
||||
#include "esp_transport.h"
|
||||
#include "esp_transport_ssl.h"
|
||||
#include "esp_transport_internal.h"
|
||||
#include "errno.h"
|
||||
|
||||
#define INVALID_SOCKET (-1)
|
||||
|
||||
|
@ -10,6 +10,7 @@
|
||||
#include <ctype.h>
|
||||
#include <sys/random.h>
|
||||
#include <sys/socket.h>
|
||||
#include <arpa/inet.h>
|
||||
#include "esp_log.h"
|
||||
#include "esp_transport.h"
|
||||
#include "esp_transport_tcp.h"
|
||||
|
@ -6,21 +6,33 @@
|
||||
|
||||
(See the README.md file in the upper level 'examples' directory for more information about examples.)
|
||||
|
||||
The application creates a TCP transport connection and tries to connect to the server with predefined IP address and port number. When a connection is successfully established, the application sends message and waits for the answer. After the server's reply, application prints received reply as ASCII text, waits for 2 seconds and sends another message.
|
||||
The application creates a TCP connection using `tcp_transport` component and tries to connect to the server with predefined IP address and port number. When a connection is successfully established, the application sends message and waits for the answer. After the server's reply, application prints received reply as ASCII text, waits for 2 seconds and sends another message.
|
||||
|
||||
It's possible to enable SOCKS proxy support to make the connection to go through a proxy server.
|
||||
|
||||
## How to use example
|
||||
|
||||
In order to create TCP server that communicates with TCP TRANSPORT Client example, choose one of the following options.
|
||||
In order to create TCP server that communicates with TCP TRANSPORT Client example, it's possible to choose some host-side tool or one of the scripts available in the example parent directory.
|
||||
|
||||
There are many host-side tools which can be used to interact with the UDP/TCP server/client.
|
||||
One command line tool is [netcat](http://netcat.sourceforge.net) which can send and receive many kinds of packets.
|
||||
Note: please replace `192.168.0.167 3333` with desired IPV4/IPV6 address (displayed in monitor console) and port number in the following command.
|
||||
|
||||
In addition to those tools, simple Python scripts can be found under sockets/scripts directory. Every script is designed to interact with one of the examples.
|
||||
In addition to those tools, simple Python scripts can be found under the example parent directory. Every script is designed to interact with one of the examples.
|
||||
|
||||
### TCP server using netcat
|
||||
```
|
||||
nc -l 192.168.0.167 3333
|
||||
nc -l 0.0.0.0 -p 3333
|
||||
```
|
||||
|
||||
In this scenario netcat doesn't send any data back. If the python scripts are used to test the monitor shows the message sent from the server.
|
||||
|
||||
### Using a proxy server
|
||||
|
||||
A simple way of testing the usage of the proxy client on Linux systems is
|
||||
to use ssh as a proxy:
|
||||
|
||||
```
|
||||
ssh -N -v -D 0.0.0.0:1080 localhost
|
||||
```
|
||||
|
||||
## Hardware Required
|
||||
@ -35,12 +47,18 @@ idf.py menuconfig
|
||||
|
||||
Set following parameters under Example Configuration Options:
|
||||
|
||||
* Set `IPV4 Address` that represents remote host the example will connect to.
|
||||
* Set `Target server Address` that represents remote host the example will connect to.
|
||||
|
||||
* Set `Port` number that represents remote port the example will connect to.
|
||||
* Set `Target Port` number that represents remote port the example will connect to.
|
||||
|
||||
Configure Wi-Fi or Ethernet under "Example Connection Configuration" menu. See "Establishing Wi-Fi or Ethernet Connection" section in [examples/protocols/README.md](../../README.md) for more details.
|
||||
|
||||
### Enabling proxy client
|
||||
|
||||
Set `Enable proxy client`
|
||||
Set `Proxy server address` that represents the proxy server the example will connect to.
|
||||
Set `Proxy server port` number that represents the proxy server port the example will connect to.
|
||||
|
||||
## Build and Flash
|
||||
|
||||
Build the project and flash it to the board, then run monitor tool to view serial output:
|
||||
|
@ -1,16 +1,37 @@
|
||||
menu "Example Configuration"
|
||||
|
||||
config EXAMPLE_IPV4_ADDR
|
||||
string "IPV4 Address"
|
||||
config EXAMPLE_TARGET_ADDR
|
||||
string "Target server Address"
|
||||
default "192.168.0.165"
|
||||
help
|
||||
The example will connect to this IPV4 address.
|
||||
|
||||
config EXAMPLE_PORT
|
||||
int "Port"
|
||||
config EXAMPLE_TARGET_PORT
|
||||
int "Target server Port"
|
||||
range 0 65535
|
||||
default 3333
|
||||
default 8080
|
||||
help
|
||||
The remote port to which the client example will connect to.
|
||||
|
||||
config EXAMPLE_ENABLE_PROXY
|
||||
bool "Enable Proxy client"
|
||||
default n
|
||||
help
|
||||
Use a SOCKS proxy to connect
|
||||
|
||||
config EXAMPLE_PROXY_ADDR
|
||||
string "Proxy server Address"
|
||||
depends on ENABLE_PROXY
|
||||
default "192.168.0.1"
|
||||
help
|
||||
The example will connect to this proxy address to request connection to target.
|
||||
|
||||
config EXAMPLE_PROXY_PORT
|
||||
int "Proxy server Port"
|
||||
depends on ENABLE_PROXY
|
||||
range 0 65535
|
||||
default 1080
|
||||
help
|
||||
The proxy port to which the client example will request connection.
|
||||
|
||||
endmenu
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
@ -16,26 +16,44 @@
|
||||
#include "protocol_examples_common.h"
|
||||
#include "esp_transport.h"
|
||||
#include "esp_transport_tcp.h"
|
||||
#include "esp_transport_socks_proxy.h"
|
||||
|
||||
#define TARGET_ADDR CONFIG_EXAMPLE_TARGET_ADDR
|
||||
#define TARGET_PORT CONFIG_EXAMPLE_TARGET_PORT
|
||||
|
||||
#define HOST_IP_ADDR CONFIG_EXAMPLE_IPV4_ADDR
|
||||
|
||||
#define PORT CONFIG_EXAMPLE_PORT
|
||||
#ifdef CONFIG_EXAMPLE_ENABLE_PROXY
|
||||
#define PROXY_ADDR CONFIG_EXAMPLE_PROXY_ADDR
|
||||
#define PROXY_PORT CONFIG_EXAMPLE_PROXY_PORT
|
||||
#endif
|
||||
|
||||
static const char *TAG = "tcp_transport_client";
|
||||
static const char *payload = "Message from ESP32";
|
||||
static const char *payload = "Message from ESP32\n";
|
||||
|
||||
static void tcp_transport_client_task(void *pvParameters)
|
||||
{
|
||||
char rx_buffer[128];
|
||||
char host_ip[] = HOST_IP_ADDR;
|
||||
esp_transport_handle_t tcp = esp_transport_tcp_init();
|
||||
while (1) {
|
||||
if (tcp == NULL) {
|
||||
ESP_LOGE(TAG, "Error occurred during esp_transport_tcp_init()");
|
||||
char host_ip[] = TARGET_ADDR;
|
||||
esp_transport_handle_t transport = esp_transport_tcp_init();
|
||||
|
||||
#ifdef CONFIG_EXAMPLE_ENABLE_PROXY
|
||||
/*
|
||||
* The socks transport is a composed transport, so we save the previously created
|
||||
* handler to use it as a parent transport, so our transport is now socks over tcp.
|
||||
* We could have used the ssl transport as parent and we can use a socks transport as a
|
||||
* parent to websocket transport.
|
||||
*
|
||||
*/
|
||||
esp_transport_handle_t parent = transport;
|
||||
esp_transport_socks_proxy_config_t proxy_config = {.port = PROXY_PORT, .address = PROXY_ADDR, .version = SOCKS4};
|
||||
transport = esp_transport_socks_proxy_init(parent, &proxy_config);
|
||||
#endif
|
||||
|
||||
while (1) {
|
||||
if (transport == NULL) {
|
||||
ESP_LOGE(TAG, "Error occurred during esp_transport_proxy_init()");
|
||||
break;
|
||||
}
|
||||
int err = esp_transport_connect(tcp, HOST_IP_ADDR, PORT, -1);
|
||||
int err = esp_transport_connect(transport, TARGET_ADDR, TARGET_PORT, -1);
|
||||
if (err != 0) {
|
||||
ESP_LOGE(TAG, "Client unable to connect: errno %d", errno);
|
||||
break;
|
||||
@ -43,34 +61,43 @@ static void tcp_transport_client_task(void *pvParameters)
|
||||
ESP_LOGI(TAG, "Successfully connected");
|
||||
|
||||
while (1) {
|
||||
int bytes_written = esp_transport_write(tcp, payload, strlen(payload), 0);
|
||||
int bytes_written = esp_transport_write(transport, payload, strlen(payload), 0);
|
||||
if (bytes_written < 0) {
|
||||
ESP_LOGE(TAG, "Error occurred during sending: esp_transport_write() returned %d, errno %d", bytes_written, errno);
|
||||
break;
|
||||
}
|
||||
int len = esp_transport_read(tcp, rx_buffer, sizeof(rx_buffer) - 1, 0);
|
||||
int len = esp_transport_read(transport, rx_buffer, sizeof(rx_buffer) - 1, 0);
|
||||
// Error occurred during receiving
|
||||
if (len < 0) {
|
||||
ESP_LOGE(TAG, "recv failed: esp_transport_read() returned %d, errno %d", len, errno);
|
||||
break;
|
||||
}
|
||||
// Data received
|
||||
rx_buffer[len] = 0; // Null-terminate whatever we received and treat like a string
|
||||
ESP_LOGI(TAG, "Received %d bytes from %s:", len, host_ip);
|
||||
ESP_LOGI(TAG, "%s", rx_buffer);
|
||||
rx_buffer[len] = 0; // Null-terminate whatever we received and treat like a string
|
||||
ESP_LOGI(TAG, "Received %d bytes from %s:", len, host_ip);
|
||||
ESP_LOGI(TAG, "Received data : %s", rx_buffer);
|
||||
|
||||
vTaskDelay(2000 / portTICK_PERIOD_MS);
|
||||
}
|
||||
|
||||
ESP_LOGE(TAG, "Shutting down TCP and restarting...");
|
||||
esp_transport_close(tcp);
|
||||
ESP_LOGE(TAG, "Shutting down transport and restarting...");
|
||||
esp_transport_close(transport);
|
||||
}
|
||||
esp_transport_destroy(tcp);
|
||||
vTaskDelete(NULL);
|
||||
esp_transport_destroy(transport);
|
||||
|
||||
#ifdef CONFIG_EXAMPLE_ENABLE_PROXY
|
||||
esp_transport_destroy(parent);
|
||||
#endif
|
||||
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
esp_log_level_set("transport", ESP_LOG_VERBOSE);
|
||||
esp_log_level_set("transport_base", ESP_LOG_VERBOSE);
|
||||
esp_log_level_set("transport_proxy", ESP_LOG_VERBOSE);
|
||||
|
||||
ESP_ERROR_CHECK(nvs_flash_init());
|
||||
ESP_ERROR_CHECK(esp_netif_init());
|
||||
ESP_ERROR_CHECK(esp_event_loop_create_default());
|
||||
|
@ -8,5 +8,6 @@ idf_component_mock(INCLUDE_DIRS "${original_esp_tls_dir}"
|
||||
"${original_esp_tls_dir}/esp-tls-crypto"
|
||||
MOCK_HEADER_FILES ${original_esp_tls_dir}/esp_tls.h
|
||||
${original_esp_tls_dir}/esp-tls-crypto/esp_tls_crypto.h
|
||||
REQUIRES mbedtls
|
||||
)
|
||||
target_compile_options(${COMPONENT_LIB} PRIVATE -Wno-array-parameter)
|
||||
|
5
tools/mocks/esp-tls/Kconfig
Normal file
5
tools/mocks/esp-tls/Kconfig
Normal file
@ -0,0 +1,5 @@
|
||||
menu "ESP-TLS"
|
||||
config ESP_TLS_USING_MBEDTLS
|
||||
bool "mbedTLS"
|
||||
default y
|
||||
endmenu
|
@ -4,7 +4,12 @@ message(STATUS "building LWIP MOCKS (only netdb)")
|
||||
|
||||
idf_component_get_property(original_lwip_dir lwip COMPONENT_OVERRIDEN_DIR)
|
||||
|
||||
idf_component_mock(INCLUDE_DIRS "${original_lwip_dir}/port"
|
||||
idf_component_mock(INCLUDE_DIRS "${original_lwip_dir}/include"
|
||||
"${original_lwip_dir}/include/lwip"
|
||||
"${original_lwip_dir}/lwip/src/include"
|
||||
MOCK_HEADER_FILES ${original_lwip_dir}/lwip/src/include/lwip/netdb.h )
|
||||
"${original_lwip_dir}/lwip/src/include/compat/posix"
|
||||
"${original_lwip_dir}/port/include"
|
||||
"${original_lwip_dir}/port/linux/include"
|
||||
"${original_lwip_dir}/port/freertos/include"
|
||||
"${original_lwip_dir}/port/linux/include/arch"
|
||||
MOCK_HEADER_FILES ${original_lwip_dir}/lwip/src/include/lwip/netdb.h)
|
||||
|
16
tools/mocks/lwip/Kconfig
Normal file
16
tools/mocks/lwip/Kconfig
Normal file
@ -0,0 +1,16 @@
|
||||
menu "LWIP"
|
||||
config LWIP_TCP_OVERSIZE_MSS
|
||||
bool "MSS"
|
||||
default y
|
||||
|
||||
config LWIP_DHCP_COARSE_TIMER_SECS
|
||||
int "DHCP coarse timer interval(s)"
|
||||
default 1
|
||||
range 1 10
|
||||
|
||||
config LWIP_IPV4
|
||||
bool "Enable IPv4"
|
||||
default y
|
||||
help
|
||||
Enable IPv4 stack. If you want to use IPv6 only TCP/IP stack, disable this.
|
||||
endmenu
|
Loading…
Reference in New Issue
Block a user