[tcp_transport] - Adds Socks4 proxy transport

- Basic implementation of Socks4 protocol.
- Introduce basic host testing.
This commit is contained in:
Euripedes Rocha 2022-10-05 15:20:39 +02:00
parent a3782377d7
commit d6db90a3cd
23 changed files with 718 additions and 36 deletions

View File

@ -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:

View File

@ -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}"

View 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)

View 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.

View File

@ -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)

View File

@ -0,0 +1,9 @@
:cmock:
:plugins:
- expect
- expect_any_args
- return_thru_ptr
- array
- ignore
- ignore_arg
- callback

View File

@ -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);

View 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)

View 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"

View File

@ -0,0 +1,4 @@
dependencies:
espressif/fmt: "^9.1.0"
idf:
version: ">=4.1.0"

View 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);
}
}
}
}

View File

@ -0,0 +1,3 @@
CONFIG_IDF_TARGET="linux"
CONFIG_COMPILER_CXX_EXCEPTIONS=y
CONFIG_UNITY_ENABLE_IDF_TEST_RUNNER=n

View 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

View 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;
}

View File

@ -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)

View File

@ -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"

View File

@ -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:

View File

@ -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

View File

@ -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());

View File

@ -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)

View File

@ -0,0 +1,5 @@
menu "ESP-TLS"
config ESP_TLS_USING_MBEDTLS
bool "mbedTLS"
default y
endmenu

View File

@ -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
View 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