Compare commits

..

28 Commits

Author SHA1 Message Date
a363beea6f Merge pull request #462 from david-cermak/feat/eppp_link
New component ESP-PPP-Link
2024-02-23 12:22:43 +01:00
a8035c21a2 bump(eppp): Published ESP-PPP-Link as eppp-v0.0.1 2024-02-23 12:05:21 +01:00
7eefcf084e feat(eppp): Added CI job to build examples and tests 2024-02-23 12:05:02 +01:00
18f845275f feat(eppp): Added support for SPI transport 2024-02-23 11:47:22 +01:00
ad27414a64 feat(eppp): Added support for UART transport 2024-02-23 11:47:15 +01:00
a7610395ef feat(eppp): Introduced ESP-PPP-Link component 2024-02-23 11:11:49 +01:00
9b7c8755e9 Merge pull request #506 from gabsuren/websocket_ssl_example
feat(websocket): Expanded example to demonstrate the transfer over TLS (IDFGH-11681)
2024-02-23 12:39:05 +04:00
0d0630ed76 feat(websocket): Expanded example to demonstrate the transfer over TLS 2024-02-23 12:08:41 +04:00
38a3631a27 Merge pull request #495 from eht-datarespons/master
fix(websocket): Continue waiting for TCP connection to be closed (IDFGH-11965)
2024-02-08 14:37:44 +01:00
96f4ebd994 Merge pull request #504 from gabsuren/mdns_1.2.4
bump(mdns): 1.2.3 -> 1.2.4
2024-02-08 16:39:33 +04:00
247ca41bb7 bump(mdns): 1.2.3 -> 1.2.4
1.2.4
Bug Fixes
- Correction on 6d2c475 MDNS_PREDEF_NETIF_ETH fix (fc59f87c4e)
- fix the logic of creating pcb for networking socket (5000a9a20a)
- fix compiling issue when disabling IPv4 (2646dcd23a)
- Fix compile error when MDNS_PREDEF_NETIF_ETH is defined, but ETH_ENABLED is not (#459) (6d2c475c20)
2024-02-06 11:51:38 +04:00
891384cc53 Merge pull request #414 from wqx6/fix/mdns_disable_ipv4
fix(mdns): fix compiling issue when disabling IPv4 (IDFGH-11453)
2024-02-06 11:45:45 +04:00
38ef603296 Merge pull request #500 from gabsuren/websocket_linux
fix(websocket): added dependency checks, sdkconfig.defaults and refined README.md
2024-02-05 14:05:00 +04:00
312982e4aa fix(websocket): added dependency checks, sdkconfig.defaults and refined README.md 2024-02-05 13:22:39 +04:00
d9d377133e Merge pull request #501 from gabsuren/update_examples
docs(common): Enhance documentation clarity in the examples directory
2024-02-05 13:16:47 +04:00
110536ebb2 docs(common): Enhance documentation clarity in the examples directory 2024-02-05 13:15:25 +04:00
d85311880d fix(websocket): Close websocket and dispatch event if server does not close within a reasonable amount of time. 2024-01-29 08:01:39 +01:00
5ba7cfab8e Merge pull request #496 from gabsuren/readme_websocket_linux
docs(websocket): Added README for websocket host example (IDF-9115)
2024-01-26 20:31:15 +04:00
2f7c58259d docs(websocket): Added README for websocket host example 2024-01-26 20:29:28 +04:00
2b092e0db4 fix(websocket): Continue waiting for TCP connection to be closed
Prevents an issue where WEBSOCKET_EVENT_CLOSED is not sent after
websocket is closed.
2024-01-23 14:58:27 +01:00
f42c0adfc0 Merge pull request #494 from david-cermak/bump/modem_v1.1
bump(modem): 1.0.5 -> 1.1.0
2024-01-22 17:08:55 +01:00
2782277f3f bump(modem): 1.0.5 -> 1.1.0
1.1.0
Features
- Added support for at_raw() command (ae38110, #471)
- Added iperf test for PPP netifs (976e98d)
- Added test that performs OTA to exercise modem layers (f2223dd)
Bug Fixes
- Fixed OTA test to gracefully fail with no verification (1dc4299)
- Added C-API to configure APN (ce7dadd, #485)
- Fixed AT commands to copy strings to prevent overrides (741d166, #463)
- Fixed incorrect dial command format (0998f3d, #433)
- Fixed documentation and example on creating custom device (577de67, #452)
- Removed CI jobs for IDF v4.2 (d88cd61)
- Fixed OAT test to verify server cert and CN (edc3e72)
- Fixed set_pdp_context() command timeout (1d80cbc, #455)
Updated
- docs(modem): Added description of manual test procedure (68ce794)
2024-01-22 16:33:07 +01:00
3225f40c22 Merge pull request #491 from david-cermak/feat/modem_set_apn
fix(modem): Added C-API to configure APN
2024-01-22 16:05:51 +01:00
68ce794098 docs(modem): Added description of manual test procedure 2024-01-19 11:16:18 +01:00
1dc4299eb0 fix(modem): Fixed OTA test to gracefully fail with no verification 2024-01-17 17:43:47 +01:00
ce7daddc77 fix(modem): Added C-API to configure APN
Closes https://github.com/espressif/esp-protocols/issues/485
2024-01-17 14:30:05 +01:00
5000a9a20a fix(mdns): fix the logic of creating pcb for networking socket 2024-01-17 10:48:40 +08:00
2646dcd23a fix(mdns): fix compiling issue when disabling IPv4 2024-01-17 10:48:40 +08:00
79 changed files with 3111 additions and 175 deletions

28
.github/workflows/eppp__build.yml vendored Normal file
View File

@ -0,0 +1,28 @@
name: "eppp_link: build-tests"
on:
push:
branches:
- master
pull_request:
types: [opened, synchronize, reopened, labeled]
jobs:
build_eppp:
if: contains(github.event.pull_request.labels.*.name, 'eppp') || github.event_name == 'push'
name: Build
strategy:
matrix:
idf_ver: ["latest"]
test: [ { app: host, path: "examples/host" }, { app: slave, path: "examples/slave" }, { app: test_app, path: "test/test_app" }]
runs-on: ubuntu-20.04
container: espressif/idf:${{ matrix.idf_ver }}
steps:
- name: Checkout esp-protocols
uses: actions/checkout@v3
- name: Build ${{ matrix.test.app }} with IDF-${{ matrix.idf_ver }}
shell: bash
run: |
${IDF_PATH}/install.sh --enable-pytest
. ${IDF_PATH}/export.sh
python ./ci/build_apps.py ./components/eppp_link/${{matrix.test.path}} -vv --preserve-all

View File

@ -92,6 +92,7 @@ jobs:
components/esp_modem;
components/esp_mqtt_cxx;
components/esp_websocket_client;
components/eppp_link;
components/mdns;
components/console_simple_init;
components/console_cmd_ping;

View File

@ -61,8 +61,8 @@ repos:
- repo: local
hooks:
- id: commit message scopes
name: "commit message must be scoped with: mdns, modem, websocket, asio, mqtt_cxx, console, common"
entry: '\A(?!(feat|fix|ci|bump|test|docs)\((mdns|modem|common|console|websocket|asio|mqtt_cxx|examples)\)\:)'
name: "commit message must be scoped with: mdns, modem, websocket, asio, mqtt_cxx, console, common, eppp"
entry: '\A(?!(feat|fix|ci|bump|test|docs)\((mdns|modem|common|console|websocket|asio|mqtt_cxx|examples|eppp)\)\:)'
language: pygrep
args: [--multiline]
stages: [commit-msg]

View File

@ -49,3 +49,7 @@ Please refer to instructions in [ESP-IDF](https://github.com/espressif/esp-idf)
### console_cmd_wifi
* Brief introduction [README](components/console_cmd_wifi/README.md)
### ESP PPP Link (eppp)
* Brief introduction [README](components/eppp_link/README.md)

View File

@ -0,0 +1,8 @@
---
commitizen:
bump_message: 'bump(eppp): $current_version -> $new_version'
pre_bump_hooks: python ../../ci/changelog.py eppp_link
tag_format: eppp-v$version
version: 0.0.1
version_files:
- idf_component.yml

View File

@ -0,0 +1,10 @@
# Changelog
## [0.0.1](https://github.com/espressif/esp-protocols/commits/eppp-v0.0.1)
### Features
- Added CI job to build examples and tests ([8686977](https://github.com/espressif/esp-protocols/commit/8686977))
- Added support for SPI transport ([18f8452](https://github.com/espressif/esp-protocols/commit/18f8452))
- Added support for UART transport ([ad27414](https://github.com/espressif/esp-protocols/commit/ad27414))
- Introduced ESP-PPP-Link component ([a761039](https://github.com/espressif/esp-protocols/commit/a761039))

View File

@ -0,0 +1,3 @@
idf_component_register(SRCS "eppp_link.c"
INCLUDE_DIRS "include"
PRIV_REQUIRES esp_netif esp_driver_spi esp_driver_gpio esp_timer driver)

View File

@ -0,0 +1,35 @@
menu "eppp_link"
choice EPPP_LINK_DEVICE
prompt "Choose PPP device"
default EPPP_LINK_DEVICE_UART
help
Select which peripheral to use for PPP link
config EPPP_LINK_DEVICE_UART
bool "UART"
help
Use UART.
config EPPP_LINK_DEVICE_SPI
bool "SPI"
help
Use SPI.
endchoice
config EPPP_LINK_CONN_MAX_RETRY
int "Maximum retry"
default 6
help
Set the Maximum retry to infinitely avoid reconnecting
This is used only with the simplified API (eppp_connect()
and eppp_listen())
config EPPP_LINK_PACKET_QUEUE_SIZE
int "Packet queue size"
default 64
help
Size of the Tx packet queue.
You can decrease the number for slower bit rates.
endmenu

View File

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -0,0 +1,52 @@
# ESP PPP Link component (eppp_link)
The component provides a general purpose connectivity engine between two microcontrollers, one acting as PPP server (slave), the other one as PPP client (host).
This component could be used for extending network using physical serial connection. Applications could vary from providing PRC engine for multiprocessor solutions to serial connection to POSIX machine. This uses a standard PPP protocol to negotiate IP addresses and networking, so standard PPP toolset could be used, e.g. a `pppd` service on linux. Typical application is a WiFi connectivity provider for chips that do not have WiFi
## Typical application
Using this component we can construct a WiFi connectivity gateway on PPP channel. The below diagram depicts an application where
PPP server is running on a WiFi capable chip with NAPT module translating packets between WiFi and PPPoS interface.
We usually call this node a SLAVE microcontroller. The "HOST" microcontroller runs PPP client and connects only to the serial line,
brings in the WiFi connectivity from the "SLAVE" microcontroller.
```
SLAVE micro HOST micro
\|/ +----------------+ +----------------+
| | | serial line | |
+---+ WiFi NAT PPPoS |======== UART / SPI =======| PPPoS client |
| (server)| | |
+----------------+ +----------------+
```
## API
### Client
* `eppp_connect()` -- Simplified API. Provides the initialization, starts the task and blocks until we're connected
### Server
* `eppp_listen()` -- Simplified API. Provides the initialization, starts the task and blocks until the client connects
### Manual actions
* `eppp_init()` -- Initializes one endpoint (client/server).
* `eppp_deinit()` -- Destroys the endpoint
* `eppp_netif_start()` -- Starts the network, could be called after startup or whenever a connection is lost
* `eppp_netif_stop()` -- Stops the network
* `eppp_perform()` -- Perform one iteration of the PPP task (need to be called regularly in task-less configuration)
## Throughput
Tested with WiFi-NAPT example, no IRAM optimizations
### UART @ 3Mbauds
* TCP - 2Mbits/s
* UDP - 2Mbits/s
### SPI @ 20MHz
* TCP - 6Mbits/s
* UDP - 10Mbits/s

View File

@ -0,0 +1,820 @@
/*
* SPDX-FileCopyrightText: 2019-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#include <stdint.h>
#include "sdkconfig.h"
#include "esp_log.h"
#include "esp_netif.h"
#include "esp_check.h"
#include "esp_event.h"
#include "esp_netif_ppp.h"
#include "eppp_link.h"
#if CONFIG_EPPP_LINK_DEVICE_SPI
#include "driver/spi_master.h"
#include "driver/spi_slave.h"
#include "driver/gpio.h"
#include "esp_timer.h"
#include "esp_rom_crc.h"
#elif CONFIG_EPPP_LINK_DEVICE_UART
#include "driver/uart.h"
#endif
static const int GOT_IPV4 = BIT0;
static const int CONNECTION_FAILED = BIT1;
#define CONNECT_BITS (GOT_IPV4|CONNECTION_FAILED)
static EventGroupHandle_t s_event_group = NULL;
static const char *TAG = "eppp_link";
static int s_retry_num = 0;
static int s_eppp_netif_count = 0; // used as a suffix for the netif key
struct packet {
size_t len;
uint8_t *data;
};
#if CONFIG_EPPP_LINK_DEVICE_SPI
#define MAX_PAYLOAD 1500
#define MIN_TRIGGER_US 20
#define SPI_HEADER_MAGIC 0x1234
static void timer_callback(void *arg);
struct header {
uint16_t magic;
uint16_t size;
uint16_t next_size;
uint16_t check;
} __attribute__((packed));
enum blocked_status {
NONE,
MASTER_BLOCKED,
MASTER_WANTS_READ,
SLAVE_BLOCKED,
SLAVE_WANTS_WRITE,
};
#endif // CONFIG_EPPP_LINK_DEVICE_SPI
struct eppp_handle {
#if CONFIG_EPPP_LINK_DEVICE_SPI
QueueHandle_t out_queue;
QueueHandle_t ready_semaphore;
spi_device_handle_t spi_device;
spi_host_device_t spi_host;
int gpio_intr;
uint16_t next_size;
uint16_t transaction_size;
struct packet outbound;
enum blocked_status blocked;
uint32_t slave_last_edge;
esp_timer_handle_t timer;
#elif CONFIG_EPPP_LINK_DEVICE_UART
QueueHandle_t uart_event_queue;
uart_port_t uart_port;
#endif
esp_netif_t *netif;
eppp_type_t role;
bool stop;
bool exited;
bool netif_stop;
};
static esp_err_t transmit(void *h, void *buffer, size_t len)
{
struct eppp_handle *handle = h;
#if CONFIG_EPPP_LINK_DEVICE_SPI
struct packet buf = { };
uint8_t *current_buffer = buffer;
size_t remaining = len;
do { // TODO(IDF-9194): Refactor this loop to allocate only once and perform
// fragmentation after receiving from the queue (applicable only if MTU > MAX_PAYLOAD)
size_t batch = remaining > MAX_PAYLOAD ? MAX_PAYLOAD : remaining;
buf.data = malloc(batch);
if (buf.data == NULL) {
ESP_LOGE(TAG, "Failed to allocate packet");
return ESP_ERR_NO_MEM;
}
buf.len = batch;
remaining -= batch;
memcpy(buf.data, current_buffer, batch);
current_buffer += batch;
BaseType_t ret = xQueueSend(handle->out_queue, &buf, 0);
if (ret != pdTRUE) {
ESP_LOGE(TAG, "Failed to queue packet to slave!");
return ESP_ERR_NO_MEM;
}
} while (remaining > 0);
if (handle->role == EPPP_SERVER && handle->blocked == SLAVE_BLOCKED) {
uint32_t now = esp_timer_get_time();
uint32_t diff = now - handle->slave_last_edge;
if (diff < MIN_TRIGGER_US) {
esp_rom_delay_us(MIN_TRIGGER_US - diff);
}
gpio_set_level(handle->gpio_intr, 0);
}
#elif CONFIG_EPPP_LINK_DEVICE_UART
ESP_LOG_BUFFER_HEXDUMP("ppp_uart_send", buffer, len, ESP_LOG_VERBOSE);
uart_write_bytes(handle->uart_port, buffer, len);
#endif
return ESP_OK;
}
static void netif_deinit(esp_netif_t *netif)
{
if (netif == NULL) {
return;
}
struct eppp_handle *h = esp_netif_get_io_driver(netif);
if (h == NULL) {
return;
}
#if CONFIG_EPPP_LINK_DEVICE_SPI
struct packet buf = { };
while (xQueueReceive(h->out_queue, &buf, 0) == pdTRUE) {
if (buf.len > 0) {
free(buf.data);
}
}
vQueueDelete(h->out_queue);
if (h->role == EPPP_CLIENT) {
vSemaphoreDelete(h->ready_semaphore);
}
#endif
free(h);
esp_netif_destroy(netif);
if (s_eppp_netif_count > 0) {
s_eppp_netif_count--;
}
}
static esp_netif_t *netif_init(eppp_type_t role)
{
if (s_eppp_netif_count > 9) { // Limit to max 10 netifs, since we use "EPPPx" as the unique key (where x is 0-9)
ESP_LOGE(TAG, "Cannot create more than 10 instances");
return NULL;
}
// Create the object first
struct eppp_handle *h = calloc(1, sizeof(struct eppp_handle));
if (!h) {
ESP_LOGE(TAG, "Failed to allocate eppp_handle");
return NULL;
}
h->role = role;
#if CONFIG_EPPP_LINK_DEVICE_SPI
h->out_queue = xQueueCreate(CONFIG_EPPP_LINK_PACKET_QUEUE_SIZE, sizeof(struct packet));
if (!h->out_queue) {
ESP_LOGE(TAG, "Failed to create the packet queue");
free(h);
return NULL;
}
if (role == EPPP_CLIENT) {
h->ready_semaphore = xSemaphoreCreateBinary();
if (!h->ready_semaphore) {
ESP_LOGE(TAG, "Failed to create the packet queue");
vQueueDelete(h->out_queue);
free(h);
return NULL;
}
}
h->transaction_size = 0;
h->outbound.data = NULL;
h->outbound.len = 0;
if (role == EPPP_SERVER) {
esp_timer_create_args_t args = {
.callback = &timer_callback,
.arg = h,
.name = "timer"
};
if (esp_timer_create(&args, &h->timer) != ESP_OK) {
ESP_LOGE(TAG, "Failed to create the packet queue");
vQueueDelete(h->out_queue);
vSemaphoreDelete(h->ready_semaphore);
free(h);
return NULL;
}
}
#endif
esp_netif_driver_ifconfig_t driver_cfg = {
.handle = h,
.transmit = transmit,
};
const esp_netif_driver_ifconfig_t *ppp_driver_cfg = &driver_cfg;
esp_netif_inherent_config_t base_netif_cfg = ESP_NETIF_INHERENT_DEFAULT_PPP();
char if_key[] = "EPPP0"; // netif key needs to be unique
if_key[sizeof(if_key) - 2 /* 2 = two chars before the terminator */ ] += s_eppp_netif_count++;
base_netif_cfg.if_key = if_key;
if (role == EPPP_CLIENT) {
base_netif_cfg.if_desc = "pppos_client";
} else {
base_netif_cfg.if_desc = "pppos_server";
}
esp_netif_config_t netif_ppp_config = { .base = &base_netif_cfg,
.driver = ppp_driver_cfg,
.stack = ESP_NETIF_NETSTACK_DEFAULT_PPP
};
esp_netif_t *netif = esp_netif_new(&netif_ppp_config);
if (!netif) {
ESP_LOGE(TAG, "Failed to create esp_netif");
#if CONFIG_EPPP_LINK_DEVICE_SPI
vQueueDelete(h->out_queue);
if (h->ready_semaphore) {
vSemaphoreDelete(h->ready_semaphore);
}
#endif
free(h);
return NULL;
}
return netif;
}
esp_err_t eppp_netif_stop(esp_netif_t *netif, int stop_timeout_ms)
{
esp_netif_action_disconnected(netif, 0, 0, 0);
esp_netif_action_stop(netif, 0, 0, 0);
struct eppp_handle *h = esp_netif_get_io_driver(netif);
for (int wait = 0; wait < 100; wait++) {
vTaskDelay(pdMS_TO_TICKS(stop_timeout_ms) / 100);
if (h->netif_stop) {
break;
}
}
if (!h->netif_stop) {
return ESP_FAIL;
}
return ESP_OK;
}
esp_err_t eppp_netif_start(esp_netif_t *netif)
{
esp_netif_action_start(netif, 0, 0, 0);
esp_netif_action_connected(netif, 0, 0, 0);
return ESP_OK;
}
static int get_netif_num(esp_netif_t *netif)
{
if (netif == NULL) {
return -1;
}
const char *ifkey = esp_netif_get_ifkey(netif);
if (strstr(ifkey, "EPPP") == NULL) {
return -1; // not our netif
}
int netif_cnt = ifkey[4] - '0';
if (netif_cnt < 0 || netif_cnt > 9) {
ESP_LOGE(TAG, "Unexpected netif key %s", ifkey);
return -1;
}
return netif_cnt;
}
static void on_ppp_event(void *arg, esp_event_base_t base, int32_t event_id, void *data)
{
esp_netif_t **netif = data;
ESP_LOGD(TAG, "PPP status event: %" PRId32, event_id);
if (base == NETIF_PPP_STATUS && event_id == NETIF_PPP_ERRORUSER) {
ESP_LOGI(TAG, "Disconnected %d", get_netif_num(*netif));
struct eppp_handle *h = esp_netif_get_io_driver(*netif);
h->netif_stop = true;
}
}
static void on_ip_event(void *arg, esp_event_base_t base, int32_t event_id, void *data)
{
ip_event_got_ip_t *event = (ip_event_got_ip_t *)data;
esp_netif_t *netif = event->esp_netif;
int netif_cnt = get_netif_num(netif);
if (netif_cnt < 0) {
return;
}
if (event_id == IP_EVENT_PPP_GOT_IP) {
ESP_LOGI(TAG, "Got IPv4 event: Interface \"%s(%s)\" address: " IPSTR, esp_netif_get_desc(netif),
esp_netif_get_ifkey(netif), IP2STR(&event->ip_info.ip));
xEventGroupSetBits(s_event_group, GOT_IPV4 << (netif_cnt * 2));
} else if (event_id == IP_EVENT_PPP_LOST_IP) {
ESP_LOGI(TAG, "Disconnected");
s_retry_num++;
if (s_retry_num > CONFIG_EPPP_LINK_CONN_MAX_RETRY) {
ESP_LOGE(TAG, "PPP Connection failed %d times, stop reconnecting.", s_retry_num);
xEventGroupSetBits(s_event_group, CONNECTION_FAILED << (netif_cnt * 2));
} else {
ESP_LOGI(TAG, "PPP Connection failed %d times, try to reconnect.", s_retry_num);
eppp_netif_start(netif);
}
}
}
#if CONFIG_EPPP_LINK_DEVICE_SPI
#define SPI_ALIGN(size) (((size) + 3U) & ~(3U))
#define TRANSFER_SIZE SPI_ALIGN((MAX_PAYLOAD + 6))
#define NEXT_TRANSACTION_SIZE(a,b) (((a)>(b))?(a):(b)) /* next transaction: whichever is bigger */
static void IRAM_ATTR timer_callback(void *arg)
{
struct eppp_handle *h = arg;
if (h->blocked == SLAVE_WANTS_WRITE) {
gpio_set_level(h->gpio_intr, 0);
}
}
static void IRAM_ATTR gpio_isr_handler(void *arg)
{
static uint32_t s_last_time;
uint32_t now = esp_timer_get_time();
uint32_t diff = now - s_last_time;
if (diff < MIN_TRIGGER_US) { // debounce
return;
}
s_last_time = now;
struct eppp_handle *h = arg;
BaseType_t yield = false;
// Positive edge means SPI slave prepared the data
if (gpio_get_level(h->gpio_intr) == 1) {
xSemaphoreGiveFromISR(h->ready_semaphore, &yield);
if (yield) {
portYIELD_FROM_ISR();
}
return;
}
// Negative edge (when master blocked) means that slave wants to transmit
if (h->blocked == MASTER_BLOCKED) {
struct packet buf = { .data = NULL, .len = -1 };
xQueueSendFromISR(h->out_queue, &buf, &yield);
if (yield) {
portYIELD_FROM_ISR();
}
}
}
static esp_err_t deinit_master(esp_netif_t *netif)
{
struct eppp_handle *h = esp_netif_get_io_driver(netif);
ESP_RETURN_ON_ERROR(spi_bus_remove_device(h->spi_device), TAG, "Failed to remove SPI bus");
ESP_RETURN_ON_ERROR(spi_bus_free(h->spi_host), TAG, "Failed to free SPI bus");
return ESP_OK;
}
static esp_err_t init_master(struct eppp_config_spi_s *config, esp_netif_t *netif)
{
struct eppp_handle *h = esp_netif_get_io_driver(netif);
h->spi_host = config->host;
h->gpio_intr = config->intr;
spi_bus_config_t bus_cfg = {};
bus_cfg.mosi_io_num = config->mosi;
bus_cfg.miso_io_num = config->miso;
bus_cfg.sclk_io_num = config->sclk;
bus_cfg.quadwp_io_num = -1;
bus_cfg.quadhd_io_num = -1;
bus_cfg.max_transfer_sz = TRANSFER_SIZE;
bus_cfg.flags = 0;
bus_cfg.intr_flags = 0;
// TODO: Init and deinit SPI bus separately (per Kconfig?)
if (spi_bus_initialize(config->host, &bus_cfg, SPI_DMA_CH_AUTO) != ESP_OK) {
return ESP_FAIL;
}
spi_device_interface_config_t dev_cfg = {};
dev_cfg.clock_speed_hz = config->freq;
dev_cfg.mode = 0;
dev_cfg.spics_io_num = config->cs;
dev_cfg.cs_ena_pretrans = config->cs_ena_pretrans;
dev_cfg.cs_ena_posttrans = config->cs_ena_posttrans;
dev_cfg.duty_cycle_pos = 128;
dev_cfg.input_delay_ns = config->input_delay_ns;
dev_cfg.pre_cb = NULL;
dev_cfg.post_cb = NULL;
dev_cfg.queue_size = 3;
if (spi_bus_add_device(config->host, &dev_cfg, &h->spi_device) != ESP_OK) {
return ESP_FAIL;
}
//GPIO config for the handshake line.
gpio_config_t io_conf = {
.intr_type = GPIO_INTR_ANYEDGE,
.mode = GPIO_MODE_INPUT,
.pull_up_en = 1,
.pin_bit_mask = BIT64(config->intr),
};
gpio_config(&io_conf);
gpio_install_isr_service(0);
gpio_set_intr_type(config->intr, GPIO_INTR_ANYEDGE);
gpio_isr_handler_add(config->intr, gpio_isr_handler, esp_netif_get_io_driver(netif));
return ESP_OK;
}
static void post_setup(spi_slave_transaction_t *trans)
{
struct eppp_handle *h = trans->user;
h->slave_last_edge = esp_timer_get_time();
gpio_set_level(h->gpio_intr, 1);
if (h->transaction_size == 0) { // If no transaction planned:
if (h->outbound.len == 0) { // we're blocked if we don't have any data
h->blocked = SLAVE_BLOCKED;
} else {
h->blocked = SLAVE_WANTS_WRITE; // we notify the master that we want to write
esp_timer_start_once(h->timer, MIN_TRIGGER_US);
}
}
}
static void post_trans(spi_slave_transaction_t *trans)
{
struct eppp_handle *h = trans->user;
h->blocked = NONE;
gpio_set_level(h->gpio_intr, 0);
}
static esp_err_t deinit_slave(esp_netif_t *netif)
{
struct eppp_handle *h = esp_netif_get_io_driver(netif);
ESP_RETURN_ON_ERROR(spi_slave_free(h->spi_host), TAG, "Failed to free SPI slave host");
ESP_RETURN_ON_ERROR(spi_bus_remove_device(h->spi_device), TAG, "Failed to remove SPI device");
ESP_RETURN_ON_ERROR(spi_bus_free(h->spi_host), TAG, "Failed to free SPI bus");
return ESP_OK;
}
static esp_err_t init_slave(struct eppp_config_spi_s *config, esp_netif_t *netif)
{
struct eppp_handle *h = esp_netif_get_io_driver(netif);
h->spi_host = config->host;
h->gpio_intr = config->intr;
spi_bus_config_t bus_cfg = {};
bus_cfg.mosi_io_num = config->mosi;
bus_cfg.miso_io_num = config->miso;
bus_cfg.sclk_io_num = config->sclk;
bus_cfg.quadwp_io_num = -1;
bus_cfg.quadhd_io_num = -1;
bus_cfg.flags = 0;
bus_cfg.intr_flags = 0;
//Configuration for the SPI slave interface
spi_slave_interface_config_t slvcfg = {
.mode = 0,
.spics_io_num = config->cs,
.queue_size = 3,
.flags = 0,
.post_setup_cb = post_setup,
.post_trans_cb = post_trans,
};
//Configuration for the handshake line
gpio_config_t io_conf = {
.intr_type = GPIO_INTR_DISABLE,
.mode = GPIO_MODE_OUTPUT,
.pin_bit_mask = BIT64(config->intr),
};
gpio_config(&io_conf);
gpio_set_pull_mode(config->mosi, GPIO_PULLUP_ONLY);
gpio_set_pull_mode(config->sclk, GPIO_PULLUP_ONLY);
gpio_set_pull_mode(config->cs, GPIO_PULLUP_ONLY);
//Initialize SPI slave interface
if (spi_slave_initialize(config->host, &bus_cfg, &slvcfg, SPI_DMA_CH_AUTO) != ESP_OK) {
return ESP_FAIL;
}
return ESP_OK;
}
typedef esp_err_t (*perform_transaction_t)(struct eppp_handle *h, size_t len, const void *tx_buffer, void *rx_buffer);
static esp_err_t perform_transaction_master(struct eppp_handle *h, size_t len, const void *tx_buffer, void *rx_buffer)
{
spi_transaction_t t = {};
t.length = len * 8;
t.tx_buffer = tx_buffer;
t.rx_buffer = rx_buffer;
return spi_device_transmit(h->spi_device, &t);
}
static esp_err_t perform_transaction_slave(struct eppp_handle *h, size_t len, const void *tx_buffer, void *rx_buffer)
{
spi_slave_transaction_t t = {};
t.user = h;
t.length = len * 8;
t.tx_buffer = tx_buffer;
t.rx_buffer = rx_buffer;
return spi_slave_transmit(h->spi_host, &t, portMAX_DELAY);
}
esp_err_t eppp_perform(esp_netif_t *netif)
{
static WORD_ALIGNED_ATTR uint8_t out_buf[TRANSFER_SIZE] = {};
static WORD_ALIGNED_ATTR uint8_t in_buf[TRANSFER_SIZE] = {};
struct eppp_handle *h = esp_netif_get_io_driver(netif);
// Perform transaction for master and slave
const perform_transaction_t perform_transaction = h->role == EPPP_CLIENT ? perform_transaction_master : perform_transaction_slave;
if (h->stop) {
return ESP_ERR_TIMEOUT;
}
BaseType_t tx_queue_stat;
bool allow_test_tx = false;
uint16_t next_tx_size = 0;
if (h->role == EPPP_CLIENT) {
// SPI MASTER only code
if (xSemaphoreTake(h->ready_semaphore, pdMS_TO_TICKS(1000)) != pdTRUE) {
// slave might not be ready, but maybe we just missed an interrupt
allow_test_tx = true;
}
if (h->outbound.len == 0 && h->transaction_size == 0 && h->blocked == NONE) {
h->blocked = MASTER_BLOCKED;
xQueueReceive(h->out_queue, &h->outbound, portMAX_DELAY);
h->blocked = NONE;
if (h->outbound.len == -1) {
h->outbound.len = 0;
h->blocked = MASTER_WANTS_READ;
}
} else if (h->blocked == MASTER_WANTS_READ) {
h->blocked = NONE;
}
}
struct header *head = (void *)out_buf;
if (h->outbound.len <= h->transaction_size && allow_test_tx == false) {
// sending outbound
head->size = h->outbound.len;
if (h->outbound.len > 0) {
memcpy(out_buf + sizeof(struct header), h->outbound.data, h->outbound.len);
free(h->outbound.data);
ESP_LOG_BUFFER_HEXDUMP(TAG, out_buf + sizeof(struct header), head->size, ESP_LOG_VERBOSE);
h->outbound.data = NULL;
h->outbound.len = 0;
}
do {
tx_queue_stat = xQueueReceive(h->out_queue, &h->outbound, 0);
} while (tx_queue_stat == pdTRUE && h->outbound.len == -1);
if (h->outbound.len == -1) { // used as a signal only, no actual data
h->outbound.len = 0;
}
} else {
// outbound is bigger, need to transmit in another transaction (keep this empty)
head->size = 0;
}
next_tx_size = head->next_size = h->outbound.len;
head->magic = SPI_HEADER_MAGIC;
head->check = esp_rom_crc16_le(0, out_buf, sizeof(struct header) - sizeof(uint16_t));
esp_err_t ret = perform_transaction(h, sizeof(struct header) + h->transaction_size, out_buf, in_buf);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "spi_device_transmit failed");
h->transaction_size = 0; // need to start with HEADER only transaction
return ESP_FAIL;
}
head = (void *)in_buf;
uint16_t check = esp_rom_crc16_le(0, in_buf, sizeof(struct header) - sizeof(uint16_t));
if (check != head->check || head->magic != SPI_HEADER_MAGIC) {
h->transaction_size = 0; // need to start with HEADER only transaction
if (allow_test_tx) {
return ESP_OK;
}
ESP_LOGE(TAG, "Wrong checksum or magic");
return ESP_FAIL;
}
if (head->size > 0) {
ESP_LOG_BUFFER_HEXDUMP(TAG, in_buf + sizeof(struct header), head->size, ESP_LOG_VERBOSE);
esp_netif_receive(netif, in_buf + sizeof(struct header), head->size, NULL);
}
h->transaction_size = NEXT_TRANSACTION_SIZE(next_tx_size, head->next_size);
return ESP_OK;
}
#elif CONFIG_EPPP_LINK_DEVICE_UART
#define BUF_SIZE (1024)
static esp_err_t init_uart(struct eppp_handle *h, eppp_config_t *config)
{
h->uart_port = config->uart.port;
uart_config_t uart_config = {};
uart_config.baud_rate = config->uart.baud;
uart_config.data_bits = UART_DATA_8_BITS;
uart_config.parity = UART_PARITY_DISABLE;
uart_config.stop_bits = UART_STOP_BITS_1;
uart_config.flow_ctrl = UART_HW_FLOWCTRL_DISABLE;
uart_config.source_clk = UART_SCLK_DEFAULT;
ESP_RETURN_ON_ERROR(uart_driver_install(h->uart_port, config->uart.rx_buffer_size, 0, config->uart.queue_size, &h->uart_event_queue, 0), TAG, "Failed to install UART");
ESP_RETURN_ON_ERROR(uart_param_config(h->uart_port, &uart_config), TAG, "Failed to set params");
ESP_RETURN_ON_ERROR(uart_set_pin(h->uart_port, config->uart.tx_io, config->uart.rx_io, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE), TAG, "Failed to set UART pins");
ESP_RETURN_ON_ERROR(uart_set_rx_timeout(h->uart_port, 1), TAG, "Failed to set UART Rx timeout");
return ESP_OK;
}
static void deinit_uart(struct eppp_handle *h)
{
uart_driver_delete(h->uart_port);
}
esp_err_t eppp_perform(esp_netif_t *netif)
{
static uint8_t buffer[BUF_SIZE] = {};
struct eppp_handle *h = esp_netif_get_io_driver(netif);
uart_event_t event = {};
if (h->stop) {
return ESP_ERR_TIMEOUT;
}
if (xQueueReceive(h->uart_event_queue, &event, pdMS_TO_TICKS(100)) != pdTRUE) {
return ESP_OK;
}
if (event.type == UART_DATA) {
size_t len;
uart_get_buffered_data_len(h->uart_port, &len);
if (len) {
len = uart_read_bytes(h->uart_port, buffer, BUF_SIZE, 0);
ESP_LOG_BUFFER_HEXDUMP("ppp_uart_recv", buffer, len, ESP_LOG_VERBOSE);
esp_netif_receive(netif, buffer, len, NULL);
}
} else {
ESP_LOGW(TAG, "Received UART event: %d", event.type);
}
return ESP_OK;
}
#endif // CONFIG_EPPP_LINK_DEVICE_SPI / UART
static void ppp_task(void *args)
{
esp_netif_t *netif = args;
while (eppp_perform(netif) != ESP_ERR_TIMEOUT) {}
struct eppp_handle *h = esp_netif_get_io_driver(netif);
h->exited = true;
vTaskDelete(NULL);
}
static bool have_some_eppp_netif(esp_netif_t *netif, void *ctx)
{
return get_netif_num(netif) > 0;
}
static void remove_handlers(void)
{
esp_netif_t *netif = esp_netif_find_if(have_some_eppp_netif, NULL);
if (netif == NULL) {
// if EPPP netif in the system, we cleanup the statics
vEventGroupDelete(s_event_group);
s_event_group = NULL;
esp_event_handler_unregister(IP_EVENT, ESP_EVENT_ANY_ID, on_ip_event);
esp_event_handler_unregister(NETIF_PPP_STATUS, ESP_EVENT_ANY_ID, on_ppp_event);
}
}
void eppp_deinit(esp_netif_t *netif)
{
if (netif == NULL) {
return;
}
#if CONFIG_EPPP_LINK_DEVICE_SPI
struct eppp_handle *h = esp_netif_get_io_driver(netif);
if (h->role == EPPP_CLIENT) {
deinit_master(netif);
} else {
deinit_slave(netif);
}
#elif CONFIG_EPPP_LINK_DEVICE_UART
deinit_uart(esp_netif_get_io_driver(netif));
#endif
netif_deinit(netif);
}
esp_netif_t *eppp_init(eppp_type_t role, eppp_config_t *config)
{
esp_netif_t *netif = netif_init(role);
if (!netif) {
ESP_LOGE(TAG, "Failed to initialize PPP netif");
remove_handlers();
return NULL;
}
esp_netif_ppp_config_t netif_params;
ESP_ERROR_CHECK(esp_netif_ppp_get_params(netif, &netif_params));
netif_params.ppp_our_ip4_addr = config->ppp.our_ip4_addr;
netif_params.ppp_their_ip4_addr = config->ppp.their_ip4_addr;
netif_params.ppp_error_event_enabled = true;
ESP_ERROR_CHECK(esp_netif_ppp_set_params(netif, &netif_params));
#if CONFIG_EPPP_LINK_DEVICE_SPI
if (role == EPPP_CLIENT) {
init_master(&config->spi, netif);
} else {
init_slave(&config->spi, netif);
}
#elif CONFIG_EPPP_LINK_DEVICE_UART
init_uart(esp_netif_get_io_driver(netif), config);
#endif
return netif;
}
esp_netif_t *eppp_open(eppp_type_t role, eppp_config_t *config, int connect_timeout_ms)
{
#if CONFIG_EPPP_LINK_DEVICE_UART
if (config->transport != EPPP_TRANSPORT_UART) {
ESP_LOGE(TAG, "Invalid transport: UART device must be enabled in Kconfig");
return NULL;
}
#endif
#if CONFIG_EPPP_LINK_DEVICE_SPI
if (config->transport != EPPP_TRANSPORT_SPI) {
ESP_LOGE(TAG, "Invalid transport: SPI device must be enabled in Kconfig");
return NULL;
}
#endif
if (config->task.run_task == false) {
ESP_LOGE(TAG, "task.run_task == false is invalid in this API. Please use eppp_init()");
return NULL;
}
if (s_event_group == NULL) {
s_event_group = xEventGroupCreate();
if (esp_event_handler_register(IP_EVENT, ESP_EVENT_ANY_ID, on_ip_event, NULL) != ESP_OK) {
ESP_LOGE(TAG, "Failed to register IP event handler");
remove_handlers();
return NULL;
}
if (esp_event_handler_register(NETIF_PPP_STATUS, ESP_EVENT_ANY_ID, on_ppp_event, NULL) != ESP_OK) {
ESP_LOGE(TAG, "Failed to register PPP status handler");
remove_handlers();
return NULL;
}
}
esp_netif_t *netif = eppp_init(role, config);
if (!netif) {
remove_handlers();
return NULL;
}
eppp_netif_start(netif);
if (xTaskCreate(ppp_task, "ppp connect", config->task.stack_size, netif, config->task.priority, NULL) != pdTRUE) {
ESP_LOGE(TAG, "Failed to create a ppp connection task");
eppp_deinit(netif);
return NULL;
}
int netif_cnt = get_netif_num(netif);
if (netif_cnt < 0) {
eppp_close(netif);
return NULL;
}
ESP_LOGI(TAG, "Waiting for IP address %d", netif_cnt);
EventBits_t bits = xEventGroupWaitBits(s_event_group, CONNECT_BITS << (netif_cnt * 2), pdFALSE, pdFALSE, pdMS_TO_TICKS(connect_timeout_ms));
if (bits & (CONNECTION_FAILED << (netif_cnt * 2))) {
ESP_LOGE(TAG, "Connection failed!");
eppp_close(netif);
return NULL;
}
ESP_LOGI(TAG, "Connected! %d", netif_cnt);
return netif;
}
esp_netif_t *eppp_connect(eppp_config_t *config)
{
return eppp_open(EPPP_CLIENT, config, portMAX_DELAY);
}
esp_netif_t *eppp_listen(eppp_config_t *config)
{
return eppp_open(EPPP_SERVER, config, portMAX_DELAY);
}
void eppp_close(esp_netif_t *netif)
{
struct eppp_handle *h = esp_netif_get_io_driver(netif);
if (eppp_netif_stop(netif, 60000) != ESP_OK) {
ESP_LOGE(TAG, "Network didn't exit cleanly");
}
h->stop = true;
for (int wait = 0; wait < 100; wait++) {
vTaskDelay(pdMS_TO_TICKS(10));
if (h->exited) {
break;
}
}
if (!h->exited) {
ESP_LOGE(TAG, "Cannot stop ppp_task");
}
eppp_deinit(netif);
remove_handlers();
}

View File

@ -0,0 +1,8 @@
# The following four lines of boilerplate have to be in your project's CMakeLists
# in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.16)
set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/common_components/iperf)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(pppos_host)

View File

@ -0,0 +1,9 @@
# Client side demo of ESP-PPP-Link
This is a basic demo of using esp-mqtt library, but connects to the internet using a PPPoS client. To run this example, you would need a PPP server that provides connectivity to the MQTT broker used in this example (by default a public broker accessible on the internet).
If configured, this example could also run a ping session and an iperf console.
The PPP server could be a Linux computer with `pppd` service or an ESP32 acting like a connection gateway with PPPoS server (see the "slave" project).

View File

@ -0,0 +1,2 @@
idf_component_register(SRCS app_main.c register_iperf.c
INCLUDE_DIRS ".")

View File

@ -0,0 +1,53 @@
menu "Example Configuration"
config EXAMPLE_GLOBAL_DNS
hex "Set global DNS server"
range 0 0xFFFFFFFF
default 0x08080808
help
Global DNS server address.
config EXAMPLE_MQTT
bool "Run mqtt example"
default y
help
Run MQTT client after startup.
config EXAMPLE_BROKER_URL
string "Broker URL"
depends on EXAMPLE_MQTT
default "mqtt://mqtt.eclipseprojects.io"
help
URL of the broker to connect to.
config EXAMPLE_IPERF
bool "Run iperf"
default y
help
Init and run iperf console.
config EXAMPLE_UART_TX_PIN
int "TXD Pin Number"
depends on EPPP_LINK_DEVICE_UART
default 10
range 0 31
help
Pin number of UART TX.
config EXAMPLE_UART_RX_PIN
int "RXD Pin Number"
depends on EPPP_LINK_DEVICE_UART
default 11
range 0 31
help
Pin number of UART RX.
config EXAMPLE_UART_BAUDRATE
int "Baudrate"
depends on EPPP_LINK_DEVICE_UART
default 2000000
range 0 4000000
help
Baudrate used by the PPP over UART
endmenu

View File

@ -0,0 +1,149 @@
/*
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <stdio.h>
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#include "esp_system.h"
#include "nvs_flash.h"
#include "esp_event.h"
#include "esp_netif.h"
#include "eppp_link.h"
#include "esp_log.h"
#include "mqtt_client.h"
#include "console_ping.h"
void register_iperf(void);
static const char *TAG = "eppp_host_example";
#if CONFIG_EXAMPLE_MQTT
static void mqtt_event_handler(void *args, esp_event_base_t base, int32_t event_id, void *event_data)
{
ESP_LOGD(TAG, "Event dispatched from event loop base=%s, event_id=%" PRIi32 "", base, event_id);
esp_mqtt_event_handle_t event = event_data;
esp_mqtt_client_handle_t client = event->client;
int msg_id;
switch ((esp_mqtt_event_id_t)event_id) {
case MQTT_EVENT_CONNECTED:
ESP_LOGI(TAG, "MQTT_EVENT_CONNECTED");
msg_id = esp_mqtt_client_publish(client, "/topic/qos1", "data_3", 0, 1, 0);
ESP_LOGI(TAG, "sent publish successful, msg_id=%d", msg_id);
msg_id = esp_mqtt_client_subscribe(client, "/topic/qos0", 0);
ESP_LOGI(TAG, "sent subscribe successful, msg_id=%d", msg_id);
msg_id = esp_mqtt_client_subscribe(client, "/topic/qos1", 1);
ESP_LOGI(TAG, "sent subscribe successful, msg_id=%d", msg_id);
msg_id = esp_mqtt_client_unsubscribe(client, "/topic/qos1");
ESP_LOGI(TAG, "sent unsubscribe successful, msg_id=%d", msg_id);
break;
case MQTT_EVENT_DISCONNECTED:
ESP_LOGI(TAG, "MQTT_EVENT_DISCONNECTED");
break;
case MQTT_EVENT_SUBSCRIBED:
ESP_LOGI(TAG, "MQTT_EVENT_SUBSCRIBED, msg_id=%d", event->msg_id);
msg_id = esp_mqtt_client_publish(client, "/topic/qos0", "data", 0, 0, 0);
ESP_LOGI(TAG, "sent publish successful, msg_id=%d", msg_id);
break;
case MQTT_EVENT_UNSUBSCRIBED:
ESP_LOGI(TAG, "MQTT_EVENT_UNSUBSCRIBED, msg_id=%d", event->msg_id);
break;
case MQTT_EVENT_PUBLISHED:
ESP_LOGI(TAG, "MQTT_EVENT_PUBLISHED, msg_id=%d", event->msg_id);
break;
case MQTT_EVENT_DATA:
ESP_LOGI(TAG, "MQTT_EVENT_DATA");
printf("TOPIC=%.*s\r\n", event->topic_len, event->topic);
printf("DATA=%.*s\r\n", event->data_len, event->data);
break;
case MQTT_EVENT_ERROR:
ESP_LOGI(TAG, "MQTT_EVENT_ERROR");
if (event->error_handle->error_type == MQTT_ERROR_TYPE_TCP_TRANSPORT) {
ESP_LOGI(TAG, "Last errno string (%s)", strerror(event->error_handle->esp_transport_sock_errno));
}
break;
default:
ESP_LOGI(TAG, "Other event id:%d", event->event_id);
break;
}
}
static void mqtt_app_start(void)
{
esp_mqtt_client_config_t mqtt_cfg = {
.broker.address.uri = "mqtt://mqtt.eclipseprojects.io",
};
esp_mqtt_client_handle_t client = esp_mqtt_client_init(&mqtt_cfg);
/* The last argument may be used to pass data to the event handler, in this example mqtt_event_handler */
esp_mqtt_client_register_event(client, ESP_EVENT_ANY_ID, mqtt_event_handler, NULL);
esp_mqtt_client_start(client);
}
#endif // MQTT
void app_main(void)
{
ESP_LOGI(TAG, "[APP] Startup..");
ESP_LOGI(TAG, "[APP] Free memory: %" PRIu32 " bytes", esp_get_free_heap_size());
ESP_LOGI(TAG, "[APP] IDF version: %s", esp_get_idf_version());
ESP_ERROR_CHECK(nvs_flash_init());
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
/* Sets up the default EPPP-connection
*/
eppp_config_t config = EPPP_DEFAULT_CLIENT_CONFIG();
#if CONFIG_EPPP_LINK_DEVICE_SPI
config.transport = EPPP_TRANSPORT_SPI;
#else
config.transport = EPPP_TRANSPORT_UART;
config.uart.tx_io = CONFIG_EXAMPLE_UART_TX_PIN;
config.uart.rx_io = CONFIG_EXAMPLE_UART_RX_PIN;
config.uart.baud = CONFIG_EXAMPLE_UART_BAUDRATE;
#endif
esp_netif_t *eppp_netif = eppp_connect(&config);
if (eppp_netif == NULL) {
ESP_LOGE(TAG, "Failed to connect");
return ;
}
// Setup global DNS
esp_netif_dns_info_t dns;
dns.ip.u_addr.ip4.addr = esp_netif_htonl(CONFIG_EXAMPLE_GLOBAL_DNS);
dns.ip.type = ESP_IPADDR_TYPE_V4;
ESP_ERROR_CHECK(esp_netif_set_dns_info(eppp_netif, ESP_NETIF_DNS_MAIN, &dns));
// Initialize console REPL
ESP_ERROR_CHECK(console_cmd_init());
#if CONFIG_EXAMPLE_IPERF
register_iperf();
printf("\n =======================================================\n");
printf(" | Steps to Test EPPP-host bandwidth |\n");
printf(" | |\n");
printf(" | 1. Wait for the ESP32 to get an IP |\n");
printf(" | 2. Server: 'iperf -u -s -i 3' (on host) |\n");
printf(" | 3. Client: 'iperf -u -c SERVER_IP -t 60 -i 3' |\n");
printf(" | |\n");
printf(" =======================================================\n\n");
#endif // CONFIG_EXAMPLE_IPERF
// Register the ping command
ESP_ERROR_CHECK(console_cmd_ping_register());
// start console REPL
ESP_ERROR_CHECK(console_cmd_start());
#if CONFIG_EXAMPLE_MQTT
mqtt_app_start();
#endif
}

View File

@ -0,0 +1,6 @@
dependencies:
espressif/eppp_link:
version: "*"
override_path: "../../.."
console_cmd_ping:
version: "*"

View File

@ -0,0 +1,179 @@
/*
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <stdio.h>
#include <string.h>
#include <inttypes.h>
#include "freertos/FreeRTOS.h"
#include "freertos/event_groups.h"
#include "sys/socket.h" // for INADDR_ANY
#include "esp_netif.h"
#include "esp_log.h"
#include "esp_system.h"
#include "esp_event.h"
#include "esp_log.h"
#include "esp_netif.h"
#include "esp_netif_ppp.h"
#include "freertos/FreeRTOS.h"
#include "freertos/event_groups.h"
#include "esp_console.h"
#include "esp_event.h"
#include "esp_bit_defs.h"
#include "argtable3/argtable3.h"
#include "iperf.h"
#include "sdkconfig.h"
/* "iperf" command */
static struct {
struct arg_str *ip;
struct arg_lit *server;
struct arg_lit *udp;
struct arg_lit *version;
struct arg_int *port;
struct arg_int *length;
struct arg_int *interval;
struct arg_int *time;
struct arg_int *bw_limit;
struct arg_lit *abort;
struct arg_end *end;
} iperf_args;
static int ppp_cmd_iperf(int argc, char **argv)
{
int nerrors = arg_parse(argc, argv, (void **)&iperf_args);
// ethernet iperf only support IPV4 address
iperf_cfg_t cfg = {.type = IPERF_IP_TYPE_IPV4};
if (nerrors != 0) {
arg_print_errors(stderr, iperf_args.end, argv[0]);
return 0;
}
/* iperf -a */
if (iperf_args.abort->count != 0) {
iperf_stop();
return 0;
}
if (((iperf_args.ip->count == 0) && (iperf_args.server->count == 0)) ||
((iperf_args.ip->count != 0) && (iperf_args.server->count != 0))) {
ESP_LOGE(__func__, "Wrong mode! ESP32 should run in client or server mode");
return 0;
}
/* iperf -s */
if (iperf_args.ip->count == 0) {
cfg.flag |= IPERF_FLAG_SERVER;
}
/* iperf -c SERVER_ADDRESS */
else {
cfg.destination_ip4 = esp_ip4addr_aton(iperf_args.ip->sval[0]);
cfg.flag |= IPERF_FLAG_CLIENT;
}
if (iperf_args.length->count == 0) {
cfg.len_send_buf = 0;
} else {
cfg.len_send_buf = iperf_args.length->ival[0];
}
cfg.source_ip4 = INADDR_ANY;
/* iperf -u */
if (iperf_args.udp->count == 0) {
cfg.flag |= IPERF_FLAG_TCP;
} else {
cfg.flag |= IPERF_FLAG_UDP;
}
/* iperf -p */
if (iperf_args.port->count == 0) {
cfg.sport = IPERF_DEFAULT_PORT;
cfg.dport = IPERF_DEFAULT_PORT;
} else {
if (cfg.flag & IPERF_FLAG_SERVER) {
cfg.sport = iperf_args.port->ival[0];
cfg.dport = IPERF_DEFAULT_PORT;
} else {
cfg.sport = IPERF_DEFAULT_PORT;
cfg.dport = iperf_args.port->ival[0];
}
}
/* iperf -i */
if (iperf_args.interval->count == 0) {
cfg.interval = IPERF_DEFAULT_INTERVAL;
} else {
cfg.interval = iperf_args.interval->ival[0];
if (cfg.interval <= 0) {
cfg.interval = IPERF_DEFAULT_INTERVAL;
}
}
/* iperf -t */
if (iperf_args.time->count == 0) {
cfg.time = IPERF_DEFAULT_TIME;
} else {
cfg.time = iperf_args.time->ival[0];
if (cfg.time <= cfg.interval) {
cfg.time = cfg.interval;
}
}
/* iperf -b */
if (iperf_args.bw_limit->count == 0) {
cfg.bw_lim = IPERF_DEFAULT_NO_BW_LIMIT;
} else {
cfg.bw_lim = iperf_args.bw_limit->ival[0];
if (cfg.bw_lim <= 0) {
cfg.bw_lim = IPERF_DEFAULT_NO_BW_LIMIT;
}
}
printf("mode=%s-%s sip=" IPSTR ":%" PRIu16 ", dip=%" PRIu32 ".%" PRIu32 ".%" PRIu32 ".%" PRIu32 ":%" PRIu16 ", interval=%" PRIu32 ", time=%" PRIu32 "\r\n",
cfg.flag & IPERF_FLAG_TCP ? "tcp" : "udp",
cfg.flag & IPERF_FLAG_SERVER ? "server" : "client",
(uint16_t) cfg.source_ip4 & 0xFF,
(uint16_t) (cfg.source_ip4 >> 8) & 0xFF,
(uint16_t) (cfg.source_ip4 >> 16) & 0xFF,
(uint16_t) (cfg.source_ip4 >> 24) & 0xFF,
cfg.sport,
cfg.destination_ip4 & 0xFF, (cfg.destination_ip4 >> 8) & 0xFF,
(cfg.destination_ip4 >> 16) & 0xFF, (cfg.destination_ip4 >> 24) & 0xFF, cfg.dport,
cfg.interval, cfg.time);
iperf_start(&cfg);
return 0;
}
void register_iperf(void)
{
iperf_args.ip = arg_str0("c", "client", "<ip>",
"run in client mode, connecting to <host>");
iperf_args.server = arg_lit0("s", "server", "run in server mode");
iperf_args.udp = arg_lit0("u", "udp", "use UDP rather than TCP");
iperf_args.version = arg_lit0("V", "ipv6_domain", "use IPV6 address rather than IPV4");
iperf_args.port = arg_int0("p", "port", "<port>",
"server port to listen on/connect to");
iperf_args.length = arg_int0("l", "len", "<length>", "set read/write buffer size");
iperf_args.interval = arg_int0("i", "interval", "<interval>",
"seconds between periodic bandwidth reports");
iperf_args.time = arg_int0("t", "time", "<time>", "time in seconds to transmit for (default 10 secs)");
iperf_args.bw_limit = arg_int0("b", "bandwidth", "<bandwidth>", "bandwidth to send at in Mbits/sec");
iperf_args.abort = arg_lit0("a", "abort", "abort running iperf");
iperf_args.end = arg_end(1);
const esp_console_cmd_t iperf_cmd = {
.command = "iperf",
.help = "iperf command",
.hint = NULL,
.func = &ppp_cmd_iperf,
.argtable = &iperf_args
};
ESP_ERROR_CHECK(esp_console_cmd_register(&iperf_cmd));
}

View File

@ -0,0 +1 @@
CONFIG_EPPP_LINK_DEVICE_SPI=y

View File

@ -0,0 +1 @@
CONFIG_EPPP_LINK_DEVICE_UART=y

View File

@ -0,0 +1,4 @@
CONFIG_LWIP_PPP_SUPPORT=y
CONFIG_LWIP_PPP_SERVER_SUPPORT=y
CONFIG_LWIP_PPP_VJ_HEADER_COMPRESSION=n
CONFIG_LWIP_PPP_DEBUG_ON=y

View File

@ -0,0 +1,6 @@
# The following five lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(pppos_slave)

View File

@ -0,0 +1,7 @@
# Wi-Fi station to PPPoS server
This example demonstrate using NAPT to bring connectivity from WiFi station to PPPoS server.
This example expect a PPPoS client to connect to the server and use the connectivity.
The client could be a Linux computer with `pppd` service or another microcontroller with PPP client (or another ESP32 with not WiFi interface)

View File

@ -0,0 +1,2 @@
idf_component_register(SRCS "eppp_slave.c"
INCLUDE_DIRS ".")

View File

@ -0,0 +1,45 @@
menu "Example Configuration"
config ESP_WIFI_SSID
string "WiFi SSID"
default "myssid"
help
SSID (network name) for the example to connect to.
config ESP_WIFI_PASSWORD
string "WiFi Password"
default "mypassword"
help
WiFi password (WPA or WPA2) for the example to use.
config ESP_MAXIMUM_RETRY
int "Maximum retry"
default 5
help
Set the Maximum retry to avoid station reconnecting to the AP unlimited when the AP is really inexistent.
config EXAMPLE_UART_TX_PIN
int "TXD Pin Number"
depends on EPPP_LINK_DEVICE_UART
default 11
range 0 31
help
Pin number of UART TX.
config EXAMPLE_UART_RX_PIN
int "RXD Pin Number"
depends on EPPP_LINK_DEVICE_UART
default 10
range 0 31
help
Pin number of UART RX.
config EXAMPLE_UART_BAUDRATE
int "Baudrate"
depends on EPPP_LINK_DEVICE_UART
default 2000000
range 0 4000000
help
Baudrate used by the PPP over UART
endmenu

View File

@ -0,0 +1,147 @@
/*
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/event_groups.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "eppp_link.h"
static const char *TAG = "eppp_slave";
#if CONFIG_SOC_WIFI_SUPPORTED
/* FreeRTOS event group to signal when we are connected*/
static EventGroupHandle_t s_wifi_event_group;
/* The event group allows multiple bits for each event, but we only care about two events:
* - we are connected to the AP with an IP
* - we failed to connect after the maximum amount of retries */
#define WIFI_CONNECTED_BIT BIT0
#define WIFI_FAIL_BIT BIT1
static int s_retry_num = 0;
static void event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data)
{
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
esp_wifi_connect();
} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
if (s_retry_num < CONFIG_ESP_MAXIMUM_RETRY) {
esp_wifi_connect();
s_retry_num++;
ESP_LOGI(TAG, "retry to connect to the AP");
} else {
xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);
}
ESP_LOGI(TAG, "connect to the AP fail");
} else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
ip_event_got_ip_t *event = (ip_event_got_ip_t *) event_data;
ESP_LOGI(TAG, "got ip:" IPSTR, IP2STR(&event->ip_info.ip));
s_retry_num = 0;
xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
}
}
void init_network_interface(void)
{
s_wifi_event_group = xEventGroupCreate();
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
esp_netif_create_default_wifi_sta();
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
esp_event_handler_instance_t instance_any_id;
esp_event_handler_instance_t instance_got_ip;
ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT,
ESP_EVENT_ANY_ID,
&event_handler,
NULL,
&instance_any_id));
ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT,
IP_EVENT_STA_GOT_IP,
&event_handler,
NULL,
&instance_got_ip));
wifi_config_t wifi_config = {
.sta = {
.ssid = CONFIG_ESP_WIFI_SSID,
.password = CONFIG_ESP_WIFI_PASSWORD,
},
};
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA) );
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config) );
ESP_ERROR_CHECK(esp_wifi_start() );
ESP_LOGI(TAG, "wifi_init_sta finished.");
/* Waiting until either the connection is established (WIFI_CONNECTED_BIT) or connection failed for the maximum
* number of re-tries (WIFI_FAIL_BIT). The bits are set by event_handler() (see above) */
EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group,
WIFI_CONNECTED_BIT | WIFI_FAIL_BIT,
pdFALSE,
pdFALSE,
portMAX_DELAY);
/* xEventGroupWaitBits() returns the bits before the call returned, hence we can test which event actually
* happened. */
if (bits & WIFI_CONNECTED_BIT) {
ESP_LOGI(TAG, "connected to ap SSID:%s password:%s",
CONFIG_ESP_WIFI_SSID, CONFIG_ESP_WIFI_PASSWORD);
} else if (bits & WIFI_FAIL_BIT) {
ESP_LOGI(TAG, "Failed to connect to SSID:%s, password:%s",
CONFIG_ESP_WIFI_SSID, CONFIG_ESP_WIFI_PASSWORD);
} else {
ESP_LOGE(TAG, "UNEXPECTED EVENT");
}
}
#else
void init_network_interface(void)
{
// placeholder to initialize any other network interface if WiFi is not available
}
#endif // SoC WiFi capable chip
void app_main(void)
{
//Initialize NVS
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
init_network_interface(); // WiFi station if withing SoC capabilities (otherwise a placeholder)
eppp_config_t config = EPPP_DEFAULT_SERVER_CONFIG();
#if CONFIG_EPPP_LINK_DEVICE_SPI
config.transport = EPPP_TRANSPORT_SPI;
#else
config.transport = EPPP_TRANSPORT_UART;
config.uart.tx_io = CONFIG_EXAMPLE_UART_TX_PIN;
config.uart.rx_io = CONFIG_EXAMPLE_UART_RX_PIN;
config.uart.baud = CONFIG_EXAMPLE_UART_BAUDRATE;
#endif
esp_netif_t *eppp_netif = eppp_listen(&config);
if (eppp_netif == NULL) {
ESP_LOGE(TAG, "Failed to setup connection");
return ;
}
ESP_ERROR_CHECK(esp_netif_napt_enable(eppp_netif));
}

View File

@ -0,0 +1,4 @@
dependencies:
espressif/eppp_link:
version: "*"
override_path: "../../.."

View File

@ -0,0 +1 @@
CONFIG_EPPP_LINK_DEVICE_SPI=y

View File

@ -0,0 +1 @@
CONFIG_EPPP_LINK_DEVICE_UART=y

View File

@ -0,0 +1,6 @@
CONFIG_LWIP_IP_FORWARD=y
CONFIG_LWIP_IPV4_NAPT=y
CONFIG_LWIP_TCPIP_TASK_STACK_SIZE=4096
CONFIG_LWIP_PPP_SUPPORT=y
CONFIG_LWIP_PPP_SERVER_SUPPORT=y
CONFIG_LWIP_PPP_VJ_HEADER_COMPRESSION=n

View File

@ -0,0 +1,6 @@
version: 0.0.1
url: https://github.com/espressif/esp-protocols/tree/master/components/eppp_link
description: The component provides a general purpose PPP connectivity, typically used as WiFi-PPP router
dependencies:
idf:
version: '>=5.2'

View File

@ -0,0 +1,111 @@
/*
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#define EPPP_DEFAULT_SERVER_IP() ESP_IP4TOADDR(192, 168, 11, 1)
#define EPPP_DEFAULT_CLIENT_IP() ESP_IP4TOADDR(192, 168, 11, 2)
#define EPPP_DEFAULT_CONFIG(our_ip, their_ip) { \
.transport = EPPP_TRANSPORT_UART, \
.spi = { \
.host = 1, \
.mosi = 11, \
.miso = 13, \
.sclk = 12, \
.cs = 10, \
.intr = 2, \
.freq = 16*1000*1000, \
.input_delay_ns = 0, \
.cs_ena_pretrans = 0, \
.cs_ena_posttrans = 0, \
}, \
.uart = { \
.port = 1, \
.baud = 921600, \
.tx_io = 25, \
.rx_io = 26, \
.queue_size = 16, \
.rx_buffer_size = 1024, \
}, \
. task = { \
.run_task = true, \
.stack_size = 4096, \
.priority = 8, \
}, \
. ppp = { \
.our_ip4_addr.addr = our_ip, \
.their_ip4_addr.addr = their_ip, \
} \
}
#define EPPP_DEFAULT_SERVER_CONFIG() EPPP_DEFAULT_CONFIG(EPPP_DEFAULT_SERVER_IP(), EPPP_DEFAULT_CLIENT_IP())
#define EPPP_DEFAULT_CLIENT_CONFIG() EPPP_DEFAULT_CONFIG(EPPP_DEFAULT_CLIENT_IP(), EPPP_DEFAULT_SERVER_IP())
typedef enum eppp_type {
EPPP_SERVER,
EPPP_CLIENT,
} eppp_type_t;
typedef enum eppp_transport {
EPPP_TRANSPORT_UART,
EPPP_TRANSPORT_SPI,
} eppp_transport_t;
typedef struct eppp_config_t {
eppp_transport_t transport;
struct eppp_config_spi_s {
int host;
int mosi;
int miso;
int sclk;
int cs;
int intr;
int freq;
int input_delay_ns;
int cs_ena_pretrans;
int cs_ena_posttrans;
} spi;
struct eppp_config_uart_s {
int port;
int baud;
int tx_io;
int rx_io;
int queue_size;
int rx_buffer_size;
} uart;
struct eppp_config_task_s {
bool run_task;
int stack_size;
int priority;
} task;
struct eppp_config_pppos_s {
esp_ip4_addr_t our_ip4_addr;
esp_ip4_addr_t their_ip4_addr;
} ppp;
} eppp_config_t;
esp_netif_t *eppp_connect(eppp_config_t *config);
esp_netif_t *eppp_listen(eppp_config_t *config);
void eppp_close(esp_netif_t *netif);
esp_netif_t *eppp_init(eppp_type_t role, eppp_config_t *config);
void eppp_deinit(esp_netif_t *netif);
esp_netif_t *eppp_open(eppp_type_t role, eppp_config_t *config, int connect_timeout_ms);
esp_err_t eppp_netif_stop(esp_netif_t *netif, int stop_timeout_ms);
esp_err_t eppp_netif_start(esp_netif_t *netif);
esp_err_t eppp_perform(esp_netif_t *netif);

View File

@ -0,0 +1,7 @@
# The following four lines of boilerplate have to be in your project's CMakeLists
# in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.16)
set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/tools/unit-test-app/components)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(test_app)

View File

@ -0,0 +1,73 @@
# Test application running both server and client on the same device
Need to connect client's Tx to server's Rx and vice versa:
GPIO25 - GPIO4
GPIO26 - GPIO5
We wait for the connection and then we start pinging the client's address on server's netif.
## Example of output:
```
I (393) eppp_test_app: [APP] Startup..
I (393) eppp_test_app: [APP] Free memory: 296332 bytes
I (393) eppp_test_app: [APP] IDF version: v5.3-dev-1154-gf14d9e7431-dirty
I (423) uart: ESP_INTR_FLAG_IRAM flag not set while CONFIG_UART_ISR_IN_IRAM is enabled, flag updated
I (423) uart: queue free spaces: 16
I (433) eppp_link: Waiting for IP address
I (433) uart: ESP_INTR_FLAG_IRAM flag not set while CONFIG_UART_ISR_IN_IRAM is enabled, flag updated
I (443) uart: queue free spaces: 16
I (443) eppp_link: Waiting for IP address
I (6473) esp-netif_lwip-ppp: Connected
I (6513) eppp_link: Got IPv4 event: Interface "pppos_client" address: 192.168.11.2
I (6523) esp-netif_lwip-ppp: Connected
I (6513) eppp_link: Connected!
I (6523) eppp_link: Got IPv4 event: Interface "pppos_server" address: 192.168.11.1
I (6553) main_task: Returned from app_main()
64bytes from 192.168.11.2 icmp_seq=1 ttl=255 time=18 ms
64bytes from 192.168.11.2 icmp_seq=2 ttl=255 time=19 ms
64bytes from 192.168.11.2 icmp_seq=3 ttl=255 time=19 ms
64bytes from 192.168.11.2 icmp_seq=4 ttl=255 time=20 ms
64bytes from 192.168.11.2 icmp_seq=5 ttl=255 time=19 ms
64bytes from 192.168.11.2 icmp_seq=6 ttl=255 time=19 ms
64bytes from 192.168.11.2 icmp_seq=7 ttl=255 time=19 ms
From 192.168.11.2 icmp_seq=8 timeout // <-- Disconnected Tx-Rx wires
From 192.168.11.2 icmp_seq=9 timeout
```
## Test cases
This test app exercises these methods of setting up server-client connection:
* simple blocking API (eppp_listen() <--> eppp_connect()): Uses network events internally and waits for connection
* simplified non-blocking API (eppp_open(EPPP_SERVER, ...) <--> eppp_open(EPPP_SERVER, ...) ): Uses events internally, optionally waits for connecting
* manual API (eppp_init(), eppp_netif_start(), eppp_perform()): User to manually drive Rx task
- Note that the ping test for this test case takes longer, since we call perform for both server and client from one task, for example:
```
TEST(eppp_test, open_close_taskless)I (28562) uart: ESP_INTR_FLAG_IRAM flag not set while CONFIG_UART_ISR_IN_IRAM is enabled, flag updated
I (28572) uart: ESP_INTR_FLAG_IRAM flag not set while CONFIG_UART_ISR_IN_IRAM is enabled, flag updated
Note: esp_netif_init() has been called. Until next reset, TCP/IP task will periodicially allocate memory and consume CPU time.
I (28602) uart: ESP_INTR_FLAG_IRAM flag not set while CONFIG_UART_ISR_IN_IRAM is enabled, flag updated
I (28612) uart: queue free spaces: 16
I (28612) uart: ESP_INTR_FLAG_IRAM flag not set while CONFIG_UART_ISR_IN_IRAM is enabled, flag updated
I (28622) uart: queue free spaces: 16
I (28642) esp-netif_lwip-ppp: Connected
I (28642) esp-netif_lwip-ppp: Connected
I (28642) test: Got IPv4 event: Interface "pppos_server(EPPP0)" address: 192.168.11.1
I (28642) esp-netif_lwip-ppp: Connected
I (28652) test: Got IPv4 event: Interface "pppos_client(EPPP1)" address: 192.168.11.2
I (28662) esp-netif_lwip-ppp: Connected
64bytes from 192.168.11.2 icmp_seq=1 ttl=255 time=93 ms
64bytes from 192.168.11.2 icmp_seq=2 ttl=255 time=98 ms
64bytes from 192.168.11.2 icmp_seq=3 ttl=255 time=99 ms
64bytes from 192.168.11.2 icmp_seq=4 ttl=255 time=99 ms
64bytes from 192.168.11.2 icmp_seq=5 ttl=255 time=99 ms
5 packets transmitted, 5 received, time 488ms
I (29162) esp-netif_lwip-ppp: User interrupt
I (29162) test: Disconnected interface "pppos_client(EPPP1)"
I (29172) esp-netif_lwip-ppp: User interrupt
I (29172) test: Disconnected interface "pppos_server(EPPP0)"
MALLOC_CAP_8BIT usage: Free memory delta: 0 Leak threshold: -64
MALLOC_CAP_32BIT usage: Free memory delta: 0 Leak threshold: -64
PASS
```

View File

@ -0,0 +1,4 @@
idf_component_register(SRCS app_main.c
INCLUDE_DIRS "."
REQUIRES test_utils
PRIV_REQUIRES unity nvs_flash esp_netif driver esp_event)

View File

@ -0,0 +1,344 @@
/*
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <stdio.h>
#include <stdint.h>
#include <stddef.h>
#include "esp_system.h"
#include "esp_event.h"
#include "esp_netif.h"
#include "esp_netif_ppp.h"
#include "eppp_link.h"
#include "lwip/sockets.h"
#include "esp_log.h"
#include "ping/ping_sock.h"
#include "driver/uart.h"
#include "test_utils.h"
#include "unity.h"
#include "test_utils.h"
#include "unity_fixture.h"
#include "memory_checks.h"
#include "lwip/sys.h"
#define CLIENT_INFO_CONNECTED BIT0
#define CLIENT_INFO_DISCONNECT BIT1
#define CLIENT_INFO_CLOSED BIT2
#define PING_SUCCEEDED BIT3
#define PING_FAILED BIT4
#define STOP_WORKER_TASK BIT5
#define WORKER_TASK_STOPPED BIT6
TEST_GROUP(eppp_test);
TEST_SETUP(eppp_test)
{
// Perform some open/close operations to disregard lazy init one-time allocations
// LWIP: core protection mutex
sys_arch_protect();
sys_arch_unprotect(0);
// UART: install and delete both drivers to disregard potential leak in allocated interrupt slot
TEST_ESP_OK(uart_driver_install(UART_NUM_1, 256, 0, 0, NULL, 0));
TEST_ESP_OK(uart_driver_delete(UART_NUM_1));
TEST_ESP_OK(uart_driver_install(UART_NUM_2, 256, 0, 0, NULL, 0));
TEST_ESP_OK(uart_driver_delete(UART_NUM_2));
// PING: used for timestamps
struct timeval time;
gettimeofday(&time, NULL);
test_utils_record_free_mem();
TEST_ESP_OK(test_utils_set_leak_level(0, ESP_LEAK_TYPE_CRITICAL, ESP_COMP_LEAK_GENERAL));
}
TEST_TEAR_DOWN(eppp_test)
{
test_utils_finish_and_evaluate_leaks(32, 64);
}
static void test_on_ping_end(esp_ping_handle_t hdl, void *args)
{
EventGroupHandle_t event = args;
uint32_t transmitted;
uint32_t received;
uint32_t total_time_ms;
esp_ping_get_profile(hdl, ESP_PING_PROF_REQUEST, &transmitted, sizeof(transmitted));
esp_ping_get_profile(hdl, ESP_PING_PROF_REPLY, &received, sizeof(received));
esp_ping_get_profile(hdl, ESP_PING_PROF_DURATION, &total_time_ms, sizeof(total_time_ms));
printf("%" PRId32 " packets transmitted, %" PRId32 " received, time %" PRId32 "ms\n", transmitted, received, total_time_ms);
if (transmitted == received) {
xEventGroupSetBits(event, PING_SUCCEEDED);
} else {
xEventGroupSetBits(event, PING_FAILED);
}
}
static void test_on_ping_success(esp_ping_handle_t hdl, void *args)
{
uint8_t ttl;
uint16_t seqno;
uint32_t elapsed_time, recv_len;
ip_addr_t target_addr;
esp_ping_get_profile(hdl, ESP_PING_PROF_SEQNO, &seqno, sizeof(seqno));
esp_ping_get_profile(hdl, ESP_PING_PROF_TTL, &ttl, sizeof(ttl));
esp_ping_get_profile(hdl, ESP_PING_PROF_IPADDR, &target_addr, sizeof(target_addr));
esp_ping_get_profile(hdl, ESP_PING_PROF_SIZE, &recv_len, sizeof(recv_len));
esp_ping_get_profile(hdl, ESP_PING_PROF_TIMEGAP, &elapsed_time, sizeof(elapsed_time));
printf("%" PRId32 "bytes from %s icmp_seq=%d ttl=%d time=%" PRId32 " ms\n",
recv_len, inet_ntoa(target_addr.u_addr.ip4), seqno, ttl, elapsed_time);
}
struct client_info {
esp_netif_t *netif;
EventGroupHandle_t event;
};
static void open_client_task(void *ctx)
{
struct client_info *info = ctx;
eppp_config_t config = EPPP_DEFAULT_CLIENT_CONFIG();
config.uart.port = UART_NUM_2;
config.uart.tx_io = 4;
config.uart.rx_io = 5;
info->netif = eppp_connect(&config);
xEventGroupSetBits(info->event, CLIENT_INFO_CONNECTED);
// wait for disconnection trigger
EventBits_t bits = xEventGroupWaitBits(info->event, CLIENT_INFO_DISCONNECT, pdFALSE, pdFALSE, pdMS_TO_TICKS(50000));
TEST_ASSERT_EQUAL(bits & CLIENT_INFO_DISCONNECT, CLIENT_INFO_DISCONNECT);
eppp_close(info->netif);
xEventGroupSetBits(info->event, CLIENT_INFO_CLOSED);
vTaskDelete(NULL);
}
TEST(eppp_test, init_deinit)
{
// Init and deinit server size
eppp_config_t config = EPPP_DEFAULT_CONFIG(0, 0);
esp_netif_t *netif = eppp_init(EPPP_SERVER, &config);
TEST_ASSERT_NOT_NULL(netif);
eppp_deinit(netif);
netif = NULL;
// Init and deinit client size
netif = eppp_init(EPPP_CLIENT, &config);
TEST_ASSERT_NOT_NULL(netif);
eppp_deinit(netif);
}
static EventBits_t ping_test(uint32_t addr, esp_netif_t *netif, EventGroupHandle_t event)
{
ip_addr_t target_addr = { .type = IPADDR_TYPE_V4, .u_addr.ip4.addr = addr };
esp_ping_config_t ping_config = ESP_PING_DEFAULT_CONFIG();
ping_config.interval_ms = 100;
ping_config.target_addr = target_addr;
ping_config.interface = esp_netif_get_netif_impl_index(netif);
esp_ping_callbacks_t cbs = { .cb_args = event, .on_ping_end = test_on_ping_end, .on_ping_success = test_on_ping_success };
esp_ping_handle_t ping;
esp_ping_new_session(&ping_config, &cbs, &ping);
esp_ping_start(ping);
// Wait for the client thread closure and delete locally created objects
EventBits_t bits = xEventGroupWaitBits(event, PING_SUCCEEDED | PING_FAILED, pdFALSE, pdFALSE, pdMS_TO_TICKS(50000));
TEST_ASSERT_EQUAL(bits & (PING_SUCCEEDED | PING_FAILED), PING_SUCCEEDED);
esp_ping_stop(ping);
esp_ping_delete_session(ping);
return bits;
}
TEST(eppp_test, open_close)
{
test_case_uses_tcpip();
eppp_config_t config = EPPP_DEFAULT_SERVER_CONFIG();
struct client_info client = { .netif = NULL, .event = xEventGroupCreate()};
TEST_ESP_OK(esp_event_loop_create_default());
TEST_ASSERT_NOT_NULL(client.event);
// Need to connect the client in a separate thread, as the simplified API blocks until connection
xTaskCreate(open_client_task, "client_task", 4096, &client, 5, NULL);
// Now start the server
esp_netif_t *eppp_server = eppp_listen(&config);
// Wait for the client to connect
EventBits_t bits = xEventGroupWaitBits(client.event, CLIENT_INFO_CONNECTED, pdFALSE, pdFALSE, pdMS_TO_TICKS(50000));
TEST_ASSERT_EQUAL(bits & CLIENT_INFO_CONNECTED, CLIENT_INFO_CONNECTED);
// Check that both server and client are valid netif pointers
TEST_ASSERT_NOT_NULL(eppp_server);
TEST_ASSERT_NOT_NULL(client.netif);
// Now that we're connected, let's try to ping clients address
bits = ping_test(config.ppp.their_ip4_addr.addr, eppp_server, client.event);
TEST_ASSERT_EQUAL(bits & (PING_SUCCEEDED | PING_FAILED), PING_SUCCEEDED);
// Trigger client disconnection and close the server
xEventGroupSetBits(client.event, CLIENT_INFO_DISCONNECT);
eppp_close(eppp_server);
// Wait for the client thread closure and delete locally created objects
bits = xEventGroupWaitBits(client.event, CLIENT_INFO_CLOSED, pdFALSE, pdFALSE, pdMS_TO_TICKS(50000));
TEST_ASSERT_EQUAL(bits & CLIENT_INFO_CLOSED, CLIENT_INFO_CLOSED);
TEST_ESP_OK(esp_event_loop_delete_default());
vEventGroupDelete(client.event);
// wait for the lwip sockets to close cleanly
vTaskDelay(pdMS_TO_TICKS(1000));
}
static void on_event(void *arg, esp_event_base_t base, int32_t event_id, void *data)
{
EventGroupHandle_t event = arg;
if (base == IP_EVENT && event_id == IP_EVENT_PPP_GOT_IP) {
ip_event_got_ip_t *e = (ip_event_got_ip_t *)data;
esp_netif_t *netif = e->esp_netif;
ESP_LOGI("test", "Got IPv4 event: Interface \"%s(%s)\" address: " IPSTR, esp_netif_get_desc(netif),
esp_netif_get_ifkey(netif), IP2STR(&e->ip_info.ip));
if (strcmp("pppos_server", esp_netif_get_desc(netif)) == 0) {
xEventGroupSetBits(event, 1 << EPPP_SERVER);
} else if (strcmp("pppos_client", esp_netif_get_desc(netif)) == 0) {
xEventGroupSetBits(event, 1 << EPPP_CLIENT);
}
} else if (base == NETIF_PPP_STATUS && event_id == NETIF_PPP_ERRORUSER) {
esp_netif_t **netif = data;
ESP_LOGI("test", "Disconnected interface \"%s(%s)\"", esp_netif_get_desc(*netif), esp_netif_get_ifkey(*netif));
if (strcmp("pppos_server", esp_netif_get_desc(*netif)) == 0) {
xEventGroupSetBits(event, 1 << EPPP_SERVER);
} else if (strcmp("pppos_client", esp_netif_get_desc(*netif)) == 0) {
xEventGroupSetBits(event, 1 << EPPP_CLIENT);
}
}
}
TEST(eppp_test, open_close_nonblocking)
{
test_case_uses_tcpip();
EventGroupHandle_t event = xEventGroupCreate();
eppp_config_t server_config = EPPP_DEFAULT_SERVER_CONFIG();
TEST_ESP_OK(esp_event_loop_create_default());
// Open the server size
TEST_ESP_OK(esp_event_handler_register(IP_EVENT, ESP_EVENT_ANY_ID, on_event, event));
esp_netif_t *eppp_server = eppp_open(EPPP_SERVER, &server_config, 0);
TEST_ASSERT_NOT_NULL(eppp_server);
// Open the client size
eppp_config_t client_config = EPPP_DEFAULT_SERVER_CONFIG();
client_config.uart.port = UART_NUM_2;
client_config.uart.tx_io = 4;
client_config.uart.rx_io = 5;
esp_netif_t *eppp_client = eppp_open(EPPP_CLIENT, &client_config, 0);
TEST_ASSERT_NOT_NULL(eppp_client);
const EventBits_t wait_bits = (1 << EPPP_SERVER) | (1 << EPPP_CLIENT);
EventBits_t bits = xEventGroupWaitBits(event, wait_bits, pdTRUE, pdTRUE, pdMS_TO_TICKS(50000));
TEST_ASSERT_EQUAL(bits & wait_bits, wait_bits);
// Now that we're connected, let's try to ping clients address
bits = ping_test(server_config.ppp.their_ip4_addr.addr, eppp_server, event);
TEST_ASSERT_EQUAL(bits & (PING_SUCCEEDED | PING_FAILED), PING_SUCCEEDED);
// stop network for both client and server
eppp_netif_stop(eppp_client, 0); // ignore result, since we're not waiting for clean close
eppp_close(eppp_server);
eppp_close(eppp_client); // finish client close
TEST_ESP_OK(esp_event_loop_delete_default());
vEventGroupDelete(event);
// wait for the lwip sockets to close cleanly
vTaskDelay(pdMS_TO_TICKS(1000));
}
struct worker {
esp_netif_t *eppp_server;
esp_netif_t *eppp_client;
EventGroupHandle_t event;
};
static void worker_task(void *ctx)
{
struct worker *info = ctx;
while (1) {
eppp_perform(info->eppp_server);
eppp_perform(info->eppp_client);
if (xEventGroupGetBits(info->event) & STOP_WORKER_TASK) {
break;
}
}
xEventGroupSetBits(info->event, WORKER_TASK_STOPPED);
vTaskDelete(NULL);
}
TEST(eppp_test, open_close_taskless)
{
test_case_uses_tcpip();
struct worker info = { .event = xEventGroupCreate() };
TEST_ESP_OK(esp_event_loop_create_default());
TEST_ESP_OK(esp_event_handler_register(IP_EVENT, ESP_EVENT_ANY_ID, on_event, info.event));
TEST_ESP_OK(esp_event_handler_register(NETIF_PPP_STATUS, ESP_EVENT_ANY_ID, on_event, info.event));
// Create server
eppp_config_t server_config = EPPP_DEFAULT_SERVER_CONFIG();
info.eppp_server = eppp_init(EPPP_SERVER, &server_config);
TEST_ASSERT_NOT_NULL(info.eppp_server);
// Create client
eppp_config_t client_config = EPPP_DEFAULT_CLIENT_CONFIG();
client_config.uart.port = UART_NUM_2;
client_config.uart.tx_io = 4;
client_config.uart.rx_io = 5;
info.eppp_client = eppp_init(EPPP_CLIENT, &client_config);
TEST_ASSERT_NOT_NULL(info.eppp_client);
// Start workers
xTaskCreate(worker_task, "worker", 4096, &info, 5, NULL);
// Start network
TEST_ESP_OK(eppp_netif_start(info.eppp_server));
TEST_ESP_OK(eppp_netif_start(info.eppp_client));
const EventBits_t wait_bits = (1 << EPPP_SERVER) | (1 << EPPP_CLIENT);
EventBits_t bits = xEventGroupWaitBits(info.event, wait_bits, pdTRUE, pdTRUE, pdMS_TO_TICKS(50000));
TEST_ASSERT_EQUAL(bits & wait_bits, wait_bits);
xEventGroupClearBits(info.event, wait_bits);
// Now that we're connected, let's try to ping clients address
bits = ping_test(server_config.ppp.their_ip4_addr.addr, info.eppp_server, info.event);
TEST_ASSERT_EQUAL(bits & (PING_SUCCEEDED | PING_FAILED), PING_SUCCEEDED);
// stop network for both client and server, we won't wait for completion so expecting ESP_FAIL
TEST_ASSERT_EQUAL(eppp_netif_stop(info.eppp_client, 0), ESP_FAIL);
TEST_ASSERT_EQUAL(eppp_netif_stop(info.eppp_server, 0), ESP_FAIL);
// and wait for completion
bits = xEventGroupWaitBits(info.event, wait_bits, pdTRUE, pdTRUE, pdMS_TO_TICKS(50000));
TEST_ASSERT_EQUAL(bits & wait_bits, wait_bits);
// now stop the worker
xEventGroupSetBits(info.event, STOP_WORKER_TASK);
bits = xEventGroupWaitBits(info.event, WORKER_TASK_STOPPED, pdTRUE, pdTRUE, pdMS_TO_TICKS(50000));
TEST_ASSERT_EQUAL(bits & WORKER_TASK_STOPPED, WORKER_TASK_STOPPED);
// and destroy objects
eppp_deinit(info.eppp_server);
eppp_deinit(info.eppp_client);
TEST_ESP_OK(esp_event_loop_delete_default());
vEventGroupDelete(info.event);
// wait for the lwip sockets to close cleanly
vTaskDelay(pdMS_TO_TICKS(1000));
}
TEST_GROUP_RUNNER(eppp_test)
{
RUN_TEST_CASE(eppp_test, init_deinit)
RUN_TEST_CASE(eppp_test, open_close)
RUN_TEST_CASE(eppp_test, open_close_nonblocking)
RUN_TEST_CASE(eppp_test, open_close_taskless)
}
void app_main(void)
{
UNITY_MAIN(eppp_test);
}

View File

@ -0,0 +1,4 @@
dependencies:
espressif/eppp_link:
version: "*"
override_path: "../../.."

View File

@ -0,0 +1,9 @@
CONFIG_IDF_TARGET="esp32"
CONFIG_UART_ISR_IN_IRAM=y
CONFIG_ESP_NETIF_IP_LOST_TIMER_INTERVAL=0
CONFIG_FREERTOS_UNICORE=y
CONFIG_LWIP_PPP_SUPPORT=y
CONFIG_LWIP_PPP_SERVER_SUPPORT=y
CONFIG_LWIP_PPP_VJ_HEADER_COMPRESSION=n
CONFIG_LWIP_PPP_DEBUG_ON=y
CONFIG_UNITY_ENABLE_FIXTURE=y

View File

@ -3,6 +3,6 @@ commitizen:
bump_message: 'bump(modem): $current_version -> $new_version'
pre_bump_hooks: python ../../ci/changelog.py esp_modem
tag_format: modem-v$version
version: 1.0.5
version: 1.1.0
version_files:
- idf_component.yml

View File

@ -1,5 +1,28 @@
# Changelog
## [1.1.0](https://github.com/espressif/esp-protocols/commits/modem-v1.1.0)
### Features
- Added support for at_raw() command ([ae38110](https://github.com/espressif/esp-protocols/commit/ae38110), [#471](https://github.com/espressif/esp-protocols/issues/471))
- Added iperf test for PPP netifs ([976e98d](https://github.com/espressif/esp-protocols/commit/976e98d))
- Added test that performs OTA to exercise modem layers ([f2223dd](https://github.com/espressif/esp-protocols/commit/f2223dd))
### Bug Fixes
- Fixed OTA test to gracefully fail with no verification ([1dc4299](https://github.com/espressif/esp-protocols/commit/1dc4299))
- Added C-API to configure APN ([ce7dadd](https://github.com/espressif/esp-protocols/commit/ce7dadd), [#485](https://github.com/espressif/esp-protocols/issues/485))
- Fixed AT commands to copy strings to prevent overrides ([741d166](https://github.com/espressif/esp-protocols/commit/741d166), [#463](https://github.com/espressif/esp-protocols/issues/463))
- Fixed incorrect dial command format ([0998f3d](https://github.com/espressif/esp-protocols/commit/0998f3d), [#433](https://github.com/espressif/esp-protocols/issues/433))
- Fixed documentation and example on creating custom device ([577de67](https://github.com/espressif/esp-protocols/commit/577de67), [#452](https://github.com/espressif/esp-protocols/issues/452))
- Removed CI jobs for IDF v4.2 ([d88cd61](https://github.com/espressif/esp-protocols/commit/d88cd61))
- Fixed OAT test to verify server cert and CN ([edc3e72](https://github.com/espressif/esp-protocols/commit/edc3e72))
- Fixed set_pdp_context() command timeout ([1d80cbc](https://github.com/espressif/esp-protocols/commit/1d80cbc), [#455](https://github.com/espressif/esp-protocols/issues/455))
### Updated
- docs(modem): Added description of manual test procedure ([68ce794](https://github.com/espressif/esp-protocols/commit/68ce794))
## [1.0.5](https://github.com/espressif/esp-protocols/commits/modem-v1.0.5)
### Major changes

View File

@ -1,4 +1,4 @@
version: "1.0.5"
version: "1.1.0"
description: Library for communicating with cellular modems in command and data modes
url: https://github.com/espressif/esp-protocols/tree/master/components/esp_modem
issues: https://github.com/espressif/esp-protocols/issues

View File

@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@ -119,8 +119,27 @@ esp_err_t esp_modem_set_error_cb(esp_modem_dce_t *dce, esp_modem_terminal_error_
*/
esp_err_t esp_modem_set_mode(esp_modem_dce_t *dce, esp_modem_dce_mode_t mode);
/**
* @brief Convenient function to run arbitrary commands from C-API
*
* @param dce Modem DCE handle
* @param command Command to send
* @param got_line_cb Callback function which is called whenever we receive a line
* @param timeout_ms Command timeout
* @return ESP_OK on success, ESP_FAIL on failure
*/
esp_err_t esp_modem_command(esp_modem_dce_t *dce, const char *command, esp_err_t(*got_line_cb)(uint8_t *data, size_t len), uint32_t timeout_ms);
/**
* @brief Sets the APN and configures it into the modem's PDP context
*
* @param dce Modem DCE handle
* @param apn Access Point Name
* @return ESP_OK on success
*/
esp_err_t esp_modem_set_apn(esp_modem_dce_t *dce, const char *apn);
/**
* @}
*/

View File

@ -448,3 +448,10 @@ extern "C" esp_err_t esp_modem_set_baud(esp_modem_dce_t *dce_wrap, int baud)
{
return command_response_to_esp_err(dce_wrap->dce->set_baud(baud));
}
extern "C" esp_err_t esp_modem_set_apn(esp_modem_dce_t *dce_wrap, const char *apn)
{
auto new_pdp = std::unique_ptr<PdpContext>(new PdpContext(apn));
dce_wrap->dce->get_module()->configure_pdp_context(std::move(new_pdp));
return ESP_OK;
}

View File

@ -0,0 +1,47 @@
# ESP-Modem Testing
This folder contains automated and manual tests for esp-modem component. Beside these tests, some jobs are executed in CI to exercise standard examples (please refer to the CI definition and CI related sdkconfigs in examples).
List of test projects:
* `host_test` -- esp_modem is build on host (linux), modem's terminal in mocked using Loobpack class which creates simple responders to AT and CMUX mode. This test is executed in CI.
* `target` -- test executed on target with no modem device, just a pppd running on the test runner. This test is executed in CI.
* `target_ota` -- Manual test which perform OTA over PPP.
* `target_iperf` -- Manual test to measure data throughput via PPP.
## Manual testing
Prior to every `esp_modem` release, these manual tests must be executed and pass
(IDF-9074 to move the manual tests to CI)
### `target_ota`
Make sure that the UART ISR is not in IRAM, so the error messages are expected in the log, but the ESP32 should recover and continue with downloading the image.
Perform the test for these devices
* SIM7600 (CMUX mode)
* BG96 (CMUX mode)
* SIM7000 (PPP mode)
* A7672 (CMUX mode -- the only device with 2 byte CMUX payload), so the test is expected to fail more often if (`CONFIG_ESP_MODEM_CMUX_DEFRAGMENT_PAYLOAD=y` && `CONFIG_ESP_MODEM_USE_INFLATABLE_BUFFER_IF_NEEDED=n` && dte_buffer < device max payload)
* NetworkDCE -- no modem device, pppd (PPP mode)
Perform the test with these configurations:
* CONFIG_TEST_USE_VFS_TERM (y/n)
* CONFIG_ESP_HTTP_CLIENT_ENABLE_CUSTOM_TRANSPORT (y/n)
* CONFIG_ESP_MODEM_CMUX_DEFRAGMENT_PAYLOAD (y/n)
* CONFIG_ESP_MODEM_USE_INFLATABLE_BUFFER_IF_NEEDED (y/n)
**Criteria for passing the test**
The test should complete the download with maximum 1 retry (50% of OTA failure)
In case of CMUX mode, we're trying to exit CMUX at the end of the test. This step might also fail for some devices, as the CMUX-exit is not supported on certain devices (when the final error message appears that the device failed to exit CMUX, we just verify the new image by reseting the ESP32)
### `target_iperf`
Run these 4 `iperf` configurations (either manually or using `pytest`):
* tcp_tx_throughput
* tcp_rx_throughput
* udp_tx_throughput
* udp_rx_throughput
And verify in all four cases the value is about 0.70 Mbps

View File

@ -35,6 +35,10 @@ bool manual_ota::begin()
esp_transport_handle_t tcp = esp_transport_tcp_init();
ssl_ = esp_transport_batch_tls_init(tcp, max_buffer_size_);
http_.config_.transport = ssl_;
if (http_.config_.cert_pem == nullptr || common_name_ == nullptr) {
ESP_LOGE(TAG, "TLS with no verification is not supported");
return fail();
}
if (!esp_transport_batch_set_ca_cert(ssl_, http_.config_.cert_pem, 0)) {
return fail();
}

View File

@ -40,7 +40,7 @@ public:
/**
* @brief Set common name of the server to verify
*/
const char *common_name_;
const char *common_name_{};
/**
* @brief Wrapper around the http client -- Please set the http config
*/

View File

@ -1039,10 +1039,9 @@ static void esp_websocket_client_task(void *pv)
ESP_LOGD(TAG, " Waiting for TCP connection to be closed by the server");
int ret = esp_transport_ws_poll_connection_closed(client->transport, 1000);
if (ret == 0) {
// still waiting
break;
}
if (ret < 0) {
ESP_LOGW(TAG, "Did not get TCP close within expected delay");
} else if (ret < 0) {
ESP_LOGW(TAG, "Connection terminated while waiting for clean TCP close");
}
client->run = false;

View File

@ -0,0 +1,35 @@
# ESP Websocket Client - Host Example
This example demonstrates the ESP websocket client using the `linux` target. It allows for compilation and execution of the example directly within a Linux environment.
## Compilation and Execution
To compile and execute this example on Linux need to set target `linux`
```
idf.py --preview set-target linux
idf.py build
./websocket.elf
```
## Example Output
```
I (164532) websocket: [APP] Startup..
I (164532) websocket: [APP] Free memory: 4294967295 bytes
I (164532) websocket: [APP] IDF version: v5.3-dev-1353-gb3f7e2c8a4
I (164538) websocket: Connecting to ws://echo.websocket.events...
W (164538) websocket_client: `reconnect_timeout_ms` is not set, or it is less than or equal to zero, using default time out 10000 (milliseconds)
W (164538) websocket_client: `network_timeout_ms` is not set, or it is less than or equal to zero, using default time out 10000 (milliseconds)
I (165103) websocket: WEBSOCKET_EVENT_CONNECTED
I (165539) websocket: Sending hello 0000
I (165627) websocket: WEBSOCKET_EVENT_DATA
I (165627) websocket: Received opcode=1
W (165627) websocket: Received=hello 0000
W (165627) websocket: Total payload length=10, data_len=10, current payload offset=0
I (166539) websocket: Sending fragmented message
```
## Coverage Reporting
For generating a coverage report, it's necessary to enable `CONFIG_GCOV_ENABLED=y` option. Set the following configuration in your project's SDK configuration file (`sdkconfig.ci.coverage`, `sdkconfig.ci.linux` or via `menuconfig`):

View File

@ -1,3 +1,4 @@
dependencies:
idf: ">5.2"
protocol_examples_common:
path: ${IDF_PATH}/examples/common_components/protocol_examples_common

View File

@ -4,5 +4,3 @@ CONFIG_IDF_TARGET_LINUX=y
CONFIG_ESP_EVENT_POST_FROM_ISR=n
CONFIG_ESP_EVENT_POST_FROM_IRAM_ISR=n
CONFIG_WEBSOCKET_URI="ws://echo.websocket.events"
CONFIG_WEBSOCKET_URI_FROM_STRING=y
CONFIG_WEBSOCKET_URI_FROM_STDIN=n

View File

@ -3,6 +3,3 @@ CONFIG_IDF_TARGET_LINUX=y
CONFIG_ESP_EVENT_POST_FROM_ISR=n
CONFIG_ESP_EVENT_POST_FROM_IRAM_ISR=n
CONFIG_WEBSOCKET_URI="ws://echo.websocket.events"
CONFIG_WEBSOCKET_URI_FROM_STRING=y
CONFIG_WEBSOCKET_URI_FROM_STDIN=n
CONFIG_EXAMPLE_CONNECT_WIFI=n

View File

@ -0,0 +1,5 @@
CONFIG_IDF_TARGET="linux"
CONFIG_IDF_TARGET_LINUX=y
CONFIG_ESP_EVENT_POST_FROM_ISR=n
CONFIG_ESP_EVENT_POST_FROM_IRAM_ISR=n
CONFIG_WEBSOCKET_URI="ws://echo.websocket.events"

View File

@ -13,6 +13,55 @@ This example can be executed on any ESP32 board, the only required interface is
* Open the project configuration menu (`idf.py menuconfig`)
* Configure Wi-Fi or Ethernet under "Example Connection Configuration" menu.
* Configure the websocket endpoint URI under "Example Configuration", if "WEBSOCKET_URI_FROM_STDIN" is selected then the example application will connect to the URI it reads from stdin (used for testing)
* To test a WebSocket client example over TLS, please enable one of the following configurations: `CONFIG_WS_OVER_TLS_MUTUAL_AUTH` or `CONFIG_WS_OVER_TLS_SERVER_AUTH`. See the sections below for more details.
### Server Certificate Verification
* Mutual Authentication: When `CONFIG_WS_OVER_TLS_MUTUAL_AUTH=y` is enabled, it's essential to provide valid certificates for both the server and client.
This ensures a secure two-way verification process.
* Server-Only Authentication: To perform verification of the server's certificate only (without requiring a client certificate), set `CONFIG_WS_OVER_TLS_SERVER_AUTH=y`.
This method skips client certificate verification.
* Example below demonstrates how to generate a new self signed certificates for the server and client using the OpenSSL command line tool
Please note: This example represents an extremely simplified approach to generating self-signed certificates/keys with a single common CA, devoid of CN checks, lacking password protection, and featuring hardcoded key sizes and types. It is intended solely for testing purposes.
In the outlined steps, we are omitting the configuration of the CN (Common Name) field due to the context of a testing environment. However, it's important to recognize that the CN field is a critical element of SSL/TLS certificates, significantly influencing the security and efficacy of HTTPS communications. This field facilitates the verification of a website's identity, enhancing trust and security in web interactions. In practical deployments beyond testing scenarios, ensuring the CN field is accurately set is paramount for maintaining the integrity and reliability of secure communications
### Generating a self signed Certificates with OpenSSL
* The example below outlines the process for creating new certificates for both the server and client using OpenSSL, a widely-used command line tool for implementing TLS protocol:
```
Generate the CA's Private Key;
openssl genrsa -out ca_key.pem 2048
Create the CA's Certificate
openssl req -new -x509 -days 3650 -key ca_key.pem -out ca_cert.pem
Generate the Server's Private Key
openssl genrsa -out server_key.pem 2048
Generate a Certificate Signing Request (CSR) for the Server
openssl req -new -key server_key.pem -out server_csr.pem
Sign the Server's CSR with the CA's Certificate
openssl x509 -req -days 3650 -in server_csr.pem -CA ca_cert.pem -CAkey ca_key.pem -CAcreateserial -out server_cert.pem
Generate the Client's Private Key
openssl genrsa -out client_key.pem 2048
Generate a Certificate Signing Request (CSR) for the Client
openssl req -new -key client_key.pem -out client_csr.pem
Sign the Client's CSR with the CA's Certificate
openssl x509 -req -days 3650 -in client_csr.pem -CA ca_cert.pem -CAkey ca_key.pem -CAcreateserial -out client_cert.pem
```
Expiry time and metadata fields can be adjusted in the invocation.
Please see the openssl man pages (man openssl) for more details.
It is **strongly recommended** to not reuse the example certificate in your application;
it is included only for demonstration.
### Build and Flash

View File

@ -1,2 +1,22 @@
idf_component_register(SRCS "websocket_example.c"
INCLUDE_DIRS ".")
set(SRC_FILES "websocket_example.c") # Define source files
set(INCLUDE_DIRS ".") # Define include directories
set(EMBED_FILES "") # Initialize an empty list for files to embed
# Conditionally append files to the list based on configuration
#if(CONFIG_WS_OVER_TLS_MUTAL_AUTH)
list(APPEND EMBED_FILES
"certs/client_cert.pem"
"certs/ca_cert.pem"
"certs/client_key.pem")
#endif()
# For testing purpose we are using CA of wss://echo.websocket.events
#if(CONFIG_WS_OVER_TLS_SERVER_AUTH)
list(APPEND EMBED_FILES
"certs/ca_certificate_public_domain.pem")
#endif()
# Register the component with source files, include dirs, and any conditionally added embedded files
idf_component_register(SRCS "${SRC_FILES}"
INCLUDE_DIRS "${INCLUDE_DIRS}"
EMBED_TXTFILES "${EMBED_FILES}")

View File

@ -16,10 +16,30 @@ menu "Example Configuration"
config WEBSOCKET_URI
string "Websocket endpoint URI"
depends on WEBSOCKET_URI_FROM_STRING
default "ws://echo.websocket.events"
default "wss://echo.websocket.events"
help
URL of websocket endpoint this example connects to and sends echo
config WS_OVER_TLS_SERVER_AUTH
bool "Enable WebSocket over TLS with Server Certificate Verification Only"
default y
help
Enables WebSocket connections over TLS (WSS) with server certificate verification.
This setting mandates the client to verify the servers certificate, while the server
does not require client certificate verification.
config WS_OVER_TLS_MUTUAL_AUTH
bool "Enable WebSocket over TLS with Server Client Mutual Authentification"
default n
help
Enables WebSocket connections over TLS (WSS) with server and client mutual certificate verification.
config WS_OVER_TLS_SKIP_COMMON_NAME_CHECK
bool "Skip common name(CN) check during TLS authentification"
default n
help
Skipping Common Name(CN) check during TLS(WSS) authentificatio
if CONFIG_IDF_TARGET = "linux"
config GCOV_ENABLED
bool "Coverage analyzer"

View File

@ -0,0 +1,21 @@
-----BEGIN CERTIFICATE-----
MIIDazCCAlOgAwIBAgIUL04QhbSEt5oNbV4f7CeLLqTCw2gwDQYJKoZIhvcNAQEL
BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yNDAyMjMwODA2MjVaFw0zNDAy
MjAwODA2MjVaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw
HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB
AQUAA4IBDwAwggEKAoIBAQDjc78SuXAmJeBc0el2/m+2lwtk3J/VrNxHYkhjHa8K
/ybU89VvKGuv9+L3IP67WMguFTaMgivJYUePjfMchtNJLJ+4cR9BkBKH4JnyXDae
s0a5181LxRo8rqcaOw9hmJTgt9R4dIRTR3GN2/VLhlR+L9OTYA54RUtMyMMpyk5M
YIJbcOwiwkVLsIYnexXDfgz9vQGl/2vBQ/RBtDBvbSyBiWox9SuzOrya1HUBzJkM
Iu5L0bSa0LAeXHT3i3P1Y4WPt9ub70OhUNfJtHC+XbGFSEkkQG+lfbXU75XLoMWa
iATMREOcb3Mq+pn1G8o1ZHVc6lBHUkfrNfxs5P/GQcSvAgMBAAGjUzBRMB0GA1Ud
DgQWBBQGkdK2gR2HrQTnZnbuWO7I1+wdxDAfBgNVHSMEGDAWgBQGkdK2gR2HrQTn
ZnbuWO7I1+wdxDAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBx
G0hFtMwV/agIwC3ZaYC36ZWiijFzWkJSZG+fqAy32mSoVL2uQvOT8vEfF0ZnAcPc
JI4oI059dBhAVlwqv6uLHyD4Gf2bF4oSLljdTz3X23llF+/wrTC2LLqMrm09aUC0
ac74Q0FVwVJJcqH1HgemCMVjna5MkwNA6B+q7uR3eQ692VqXk6vjd4fRLBg1bBO1
hXjasfNxA8A9quORF5+rjYrwyUZHuzcs0FfSClckIt4tHKtt4moLufOW6/PM4fRe
AgdDfiTupxYLJFz4hFPhfgCh4TjQ+f9+uP4IAjW42dJmTVZjLEku/hm5lxCFObAq
RgfaNwH8Ug1r1xswjSZG
-----END CERTIFICATE-----

View File

@ -0,0 +1,30 @@
-----BEGIN CERTIFICATE-----
MIIFFjCCAv6gAwIBAgIRAJErCErPDBinU/bWLiWnX1owDQYJKoZIhvcNAQELBQAw
TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMjAwOTA0MDAwMDAw
WhcNMjUwOTE1MTYwMDAwWjAyMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNTGV0J3Mg
RW5jcnlwdDELMAkGA1UEAxMCUjMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
AoIBAQC7AhUozPaglNMPEuyNVZLD+ILxmaZ6QoinXSaqtSu5xUyxr45r+XXIo9cP
R5QUVTVXjJ6oojkZ9YI8QqlObvU7wy7bjcCwXPNZOOftz2nwWgsbvsCUJCWH+jdx
sxPnHKzhm+/b5DtFUkWWqcFTzjTIUu61ru2P3mBw4qVUq7ZtDpelQDRrK9O8Zutm
NHz6a4uPVymZ+DAXXbpyb/uBxa3Shlg9F8fnCbvxK/eG3MHacV3URuPMrSXBiLxg
Z3Vms/EY96Jc5lP/Ooi2R6X/ExjqmAl3P51T+c8B5fWmcBcUr2Ok/5mzk53cU6cG
/kiFHaFpriV1uxPMUgP17VGhi9sVAgMBAAGjggEIMIIBBDAOBgNVHQ8BAf8EBAMC
AYYwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMBIGA1UdEwEB/wQIMAYB
Af8CAQAwHQYDVR0OBBYEFBQusxe3WFbLrlAJQOYfr52LFMLGMB8GA1UdIwQYMBaA
FHm0WeZ7tuXkAXOACIjIGlj26ZtuMDIGCCsGAQUFBwEBBCYwJDAiBggrBgEFBQcw
AoYWaHR0cDovL3gxLmkubGVuY3Iub3JnLzAnBgNVHR8EIDAeMBygGqAYhhZodHRw
Oi8veDEuYy5sZW5jci5vcmcvMCIGA1UdIAQbMBkwCAYGZ4EMAQIBMA0GCysGAQQB
gt8TAQEBMA0GCSqGSIb3DQEBCwUAA4ICAQCFyk5HPqP3hUSFvNVneLKYY611TR6W
PTNlclQtgaDqw+34IL9fzLdwALduO/ZelN7kIJ+m74uyA+eitRY8kc607TkC53wl
ikfmZW4/RvTZ8M6UK+5UzhK8jCdLuMGYL6KvzXGRSgi3yLgjewQtCPkIVz6D2QQz
CkcheAmCJ8MqyJu5zlzyZMjAvnnAT45tRAxekrsu94sQ4egdRCnbWSDtY7kh+BIm
lJNXoB1lBMEKIq4QDUOXoRgffuDghje1WrG9ML+Hbisq/yFOGwXD9RiX8F6sw6W4
avAuvDszue5L3sz85K+EC4Y/wFVDNvZo4TYXao6Z0f+lQKc0t8DQYzk1OXVu8rp2
yJMC6alLbBfODALZvYH7n7do1AZls4I9d1P4jnkDrQoxB3UqQ9hVl3LEKQ73xF1O
yK5GhDDX8oVfGKF5u+decIsH4YaTw7mP3GFxJSqv3+0lUFJoi5Lc5da149p90Ids
hCExroL1+7mryIkXPeFM5TgO9r0rvZaBFOvV2z0gp35Z0+L4WPlbuEjN/lxPFin+
HlUjr8gRsI3qfJOQFy/9rKIJR0Y/8Omwt/8oTWgy1mdeHmmjk7j1nYsvC9JSQ6Zv
MldlTTKB3zhThV1+XWYp6rjd5JW1zbVWEkLNxE7GJThEUG3szgBVGP7pSWTUTsqX
nLRbwHOoq7hHwg==
-----END CERTIFICATE-----

View File

@ -0,0 +1,20 @@
-----BEGIN CERTIFICATE-----
MIIDWjCCAkKgAwIBAgIUUPCOgMA2v09E29fCkogx3RUBRtEwDQYJKoZIhvcNAQEL
BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yNDAyMjMwODA3MzFaFw0zNDAy
MjAwODA3MzFaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw
HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB
AQUAA4IBDwAwggEKAoIBAQCrNeomxI2aoP+4iUy5SiA+41oHUDZDFeJOBjv5JCsK
mlvFqxE9zynmPOVpuABErOJwzerPTa4NYKvuvs5CxVJUV5CXtWANuu9majioZNzj
f877MDNX/GnZHK2gnkxVrZCPaDmx9yiMsFMXgmfdrDhwoUpXbdgSyeU/al9Ds2kF
0hrHOH2LBWt/mVeLbONU5CC1HOdVVw+uRlhVlxnfhTPd/Nru3rJx7R0sN7qXcZpJ
PL87WvrszLVOux24DeaOz9oiD2b7egFyUuq1BM25iCwi8s/Ths8xd0Ca1d8mEcHW
FVd4w2+nUMXFE+IbP+wo6FXuiSaOBNri3rztpvCCMaWjAgMBAAGjQjBAMB0GA1Ud
DgQWBBSOlA+9Vfbcfy8iS4HSd4V0KPtm4jAfBgNVHSMEGDAWgBQGkdK2gR2HrQTn
ZnbuWO7I1+wdxDANBgkqhkiG9w0BAQsFAAOCAQEAOmzm/MwowKTrSpMSrmfA3MmW
ULzsfa25WyAoTl90ATlg4653Y7pRaNfdvVvyi2V2LlPcmc7E0rfD53t1NxjDH1uM
LgFMTNEaZ9nMRSW0kMiwaRpvmXS8Eb9PXfvIM/Mw0co/aMOtAQnfTGIqsgkQwKyk
1GG7QKQq3p4QGu5ZaTnjnaoa79hODt+0xQDD1wp6C9xwBY0M4gndAi3wkOeFkGv+
OmGPtaCBu5V9tJCZ9dfZvjkaK44NGwDw0urAcYRK2h7asnlflu7cnlGMBB0qY4kQ
BX5WI8UjN6rECBHbtNRvEh06ogDdHbxYV+TibrqkkeDRw6HX1qqiEJ+iCgWEDQ==
-----END CERTIFICATE-----

View File

@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCrNeomxI2aoP+4
iUy5SiA+41oHUDZDFeJOBjv5JCsKmlvFqxE9zynmPOVpuABErOJwzerPTa4NYKvu
vs5CxVJUV5CXtWANuu9majioZNzjf877MDNX/GnZHK2gnkxVrZCPaDmx9yiMsFMX
gmfdrDhwoUpXbdgSyeU/al9Ds2kF0hrHOH2LBWt/mVeLbONU5CC1HOdVVw+uRlhV
lxnfhTPd/Nru3rJx7R0sN7qXcZpJPL87WvrszLVOux24DeaOz9oiD2b7egFyUuq1
BM25iCwi8s/Ths8xd0Ca1d8mEcHWFVd4w2+nUMXFE+IbP+wo6FXuiSaOBNri3rzt
pvCCMaWjAgMBAAECggEAOTWjz16AXroLmRMv8v5E9h6sN6Ni7lnCrAXDRoYCZ+Ga
Ztu5wCiYPJn+oqvcUxZd+Ammu6yeS1QRP468h20+DHbSFw+BUDU1x8gYtJQ3h0Fu
3VqG3ZC3odfGYNRkd4CuvGy8Uq5e+1vz9/gYUuc4WNJccAiBWg3ir6UQviOWJV46
LGfdEd9hVvIGl5pmArMBVYdpj9+JHunDtG4uQxiWla5pdLjlkC2mGexD18T9d718
6I+o3YHv1Y9RPT1d4rNhYQWx6YdTTD2rmS7nTrzroj/4fXsblpXzR+/l7crlNERY
67RMPwgDR1NiAbCAJKsSbMS66lRCNlhTM4YffGAN6QKBgQDkIdcNm9j49SK5Wbl5
j8U6UbcVYPzPG+2ea+fDfUIafA0VQHIuX6FgA17Kp7BDX9ldKtSBpr0Z8vetVswr
agmXVMR/7QdvnZ9NpL66YA/BRs67CvsryVu4AVAzThFGySmlcXGlPq47doWDQ3B9
0BOEnVoeDXR3SabaNsEbhDYn1wKBgQDAIAUyhJcgz+LcgaAtBwdnEN57y66JlRVZ
bsb6cEG/MNmnLjQYsplJjNbz4yrB5ukTChPTGRF/JQRqHoXh6DGQFHvobukwwA6x
RAIIq0NLJ5HUipfOi+VpCbWUHdoUNhwjAB2qVtD4LXE2Lyn46C8ET5eRtRjUKpzV
lpsq63KHFQKBgFB+cDbpCoGtXPcxZXQy+lA9jPAKLKmXHRyMzlX32F8n7iXVe3RJ
YdNS3Rt8V4EuTK/G8PxeLNL/G80ZlyiqXX/79Ol+ZOVJJHBs9K8mPejgZwkwMrec
cLRYIkg3/3iOehdaE9NOboOkqi9KmGKMDJb6PlXkQXflkO3l6/UdjU45AoGAen0v
sxiTncjMU1eVfn+nuY8ouXaPbYoOFXmqBItDb5i+e3baohBj6F+Rv+ZKIVuNp6Ta
JNErtYstOFcDdpbp2nkk0ni71WftNhkszsgZ3DV7JS3DQV0xwvj8ulUZ757b63is
cShujHu0XR5OvTGSoEX6VVxHWyVb3lTp0sBPwU0CgYBe2Ieuya0X8mAbputFN64S
Kv++dqktTUT8i+tp07sIrpDeYwO3D89x9kVSJj4ImlmhiBVGkxFWPkpGyBLotdse
Ai/E6f5I7CDSZZC0ZucgcItNd4Yy459QY+dFwFtT3kIaD9ml8fnqQ83J9W8DWtv9
6mY9FnUUufbJcpHxN58RTw==
-----END PRIVATE KEY-----

View File

@ -0,0 +1,20 @@
-----BEGIN CERTIFICATE-----
MIIDWjCCAkKgAwIBAgIUUPCOgMA2v09E29fCkogx3RUBRtAwDQYJKoZIhvcNAQEL
BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yNDAyMjMwODA2NTlaFw0zNDAy
MjAwODA2NTlaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw
HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB
AQUAA4IBDwAwggEKAoIBAQC8WWbDxnLzTSfuQaO+kQnnzbwjhUHWn58s+BIEaO8M
GG6bX+8r/SH9XjMfFS36qAN3qxgRun3YoRTaHc2QByiGjf5IL4EAPDnLN+NzUIL5
7Gi2QPQP/GksAsOGKWk/nMRPk1vcMptkFVIWSp474SQ0A92Z9z0dUIqBpjRa34kr
HsAIcT59/EG7YBBadMk0fQIxQVLh3Vosky85q+0waFihe47Ef5U2UftexoUx4Vcz
6EtP60Wx+4qN+FLsr+n2B7Oz2ITqfwgqLzjNLZwm9bMjcLZ0fWm1A/W1C989MXwI
w6DAPEZv7pbgp8r9phyrNieSDuuRaCvFsaXh6troAjLxAgMBAAGjQjBAMB0GA1Ud
DgQWBBRJCYAQG2+1FN5P/wyAR1AsrAyb4DAfBgNVHSMEGDAWgBQGkdK2gR2HrQTn
ZnbuWO7I1+wdxDANBgkqhkiG9w0BAQsFAAOCAQEAmllul/GIH7RVq85mM/SxP47J
M7Z7T032KuR3n/Psyv2iq/uEV2CUje3XrKNwR2PaJL4Q6CtoWy7xgIP+9CBbjddR
M7sdNQab8P2crAUtBKnkNOl/na/5KnXnjwi/PmWJJ9i2Cqt0PPkaykTWp/MLfYIw
RPkY2Yo8f8gEiqXQd+0qTuMgumbgkPq3V8Lk1ocy62F5/qUhXxH+ifAXEoUQS6EG
8DlgwdZlfUY+jeM6N56WzYmxD1syjNW7faPio+qXINfpYatROhqphaMQ5SA6TRj6
jcnLa31TdDdWmWYDcYgZntAv6yGi3rh0MdYqeNS0FKlMKmaH81VHs7V1UUXwUQ==
-----END CERTIFICATE-----

View File

@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC8WWbDxnLzTSfu
QaO+kQnnzbwjhUHWn58s+BIEaO8MGG6bX+8r/SH9XjMfFS36qAN3qxgRun3YoRTa
Hc2QByiGjf5IL4EAPDnLN+NzUIL57Gi2QPQP/GksAsOGKWk/nMRPk1vcMptkFVIW
Sp474SQ0A92Z9z0dUIqBpjRa34krHsAIcT59/EG7YBBadMk0fQIxQVLh3Vosky85
q+0waFihe47Ef5U2UftexoUx4Vcz6EtP60Wx+4qN+FLsr+n2B7Oz2ITqfwgqLzjN
LZwm9bMjcLZ0fWm1A/W1C989MXwIw6DAPEZv7pbgp8r9phyrNieSDuuRaCvFsaXh
6troAjLxAgMBAAECggEACNVCggTxCCMCr+RJKxs/NS1LWPkbZNbYjrHVmnpXV6Bf
s460t0HoUasUx6zlGp+9heOyvcYat8maIj6KkOodBu5q0fTUXm/0n+ivlI1ejxz8
ritupr9GKWe5xrVzd6XA+SBmivWenvt2/Y+jSxica4oQ3vMe3RyVWk4yn15jXu+9
7B9lNyNeZtOBr6OozHGLYw4dwWcBNv2S6wevRKfHPwn/Ch5yTH1uAskgoMxUuyK2
ynNVHWUhyS4pFU7Tex5ENDel15VYdbxV/2lQ2W6fHMLtC5GWKJXXbigCX7pfOpzC
BFJEfZl7ze/qptE9AR7DkLFYyMtrS7OlebYbLDOM9wKBgQD+rTdwULZibpKwlI3a
9Y22d4N/EDFvuu8LnuEiVQnXgwg9M+tlaa2liP18j1a7y/FCfoXf5sjUWCsdYR6d
C0TuiOGI59hYGI94NvVLAmOutR+vJ/3jhbv5wyqEQLhJ42Yz9kWBrDCI+V3q3TdO
H7wcH6suUIZpeLEJF4qHzY/1dwKBgQC9U/Pvswiww8sfysmd5shUNo4ofAZnTM1A
ak6pWE3lSyiOkSm+3B2GqxYWLRoo1v+pTyhhXDtRRmxGtMNrKCsmlHef/o3c6kkG
cuC2h/DiSmoITHy3BYKJoDeE54E8ubXUUKqHo41LYUs+D7M/IGxeiO13MUoIrEtF
AwzVWPBU1wKBgH8barD2x6Bm+XWCHy6qIZlxGsMfDN1r2gTdvhWJhcj3D/Sj5heO
X+lfbsxtKee+yOHcDesK3y8D9jjKkSHmTvgSfyX6OML3NxvTqidOwPugUHj2J8QX
qhLk8mJhftj50reacWRf0TV76ADhecnXEuaic6hA7mTTpOAZzL0svm3PAoGBALWF
r6VLX3KzVqZVtLb7FWmAoQ35093pCgXPpznAW3cTd4Axd/fxbTG4CUYb2i/760X2
ij3Gw2yqe5fTKmYsLisgQA2bb4K28msHa6I2dmNQe5cXVp/X3Y98mJ6JpCSH3ekB
qm7ABfGXCCApx28n9B8zY5JbJKNqJgS15vELA+ojAoGAAkaV2w46+3iQ6gJtQepr
zGNybiYBx/Wo5fDdTS5u0xN+ZdC9fl2Zs0n7sMmUT8bWdDLcMnntHHO+oDIKyRHs
TQh1n68vQ4JoegQv3Z9Z/TLEKqr9gyJC1Ao6M4bZpPhUWQwupfHColtsr2TskcnJ
Nf2FpJZ7z6fQEShGlK1yTXM=
-----END PRIVATE KEY-----

View File

@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
@ -91,7 +91,7 @@ static void websocket_event_handler(void *handler_args, esp_event_base_t base, i
if (data->op_code == 0x08 && data->data_len == 2) {
ESP_LOGW(TAG, "Received closed message with code=%d", 256 * data->data_ptr[0] + data->data_ptr[1]);
} else {
ESP_LOGW(TAG, "Received=%.*s", data->data_len, (char *)data->data_ptr);
ESP_LOGW(TAG, "Received=%.*s\n\n", data->data_len, (char *)data->data_ptr);
}
// If received data contains json structure it succeed to parse
@ -143,6 +143,28 @@ static void websocket_app_start(void)
websocket_cfg.uri = CONFIG_WEBSOCKET_URI;
#endif /* CONFIG_WEBSOCKET_URI_FROM_STDIN */
#if CONFIG_WS_OVER_TLS_MUTUAL_AUTH
/* Configuring client certificates for mutual authentification */
extern const char cacert_start[] asm("_binary_ca_cert_pem_start"); // CA certificate
extern const char cert_start[] asm("_binary_client_cert_pem_start"); // Client certificate
extern const char cert_end[] asm("_binary_client_cert_pem_end");
extern const char key_start[] asm("_binary_client_key_pem_start"); // Client private key
extern const char key_end[] asm("_binary_client_key_pem_end");
websocket_cfg.cert_pem = cacert_start;
websocket_cfg.client_cert = cert_start;
websocket_cfg.client_cert_len = cert_end - cert_start;
websocket_cfg.client_key = key_start;
websocket_cfg.client_key_len = key_end - key_start;
#elif CONFIG_WS_OVER_TLS_SERVER_AUTH
extern const char cacert_start[] asm("_binary_ca_certificate_public_domain_pem_start"); // CA cert of wss://echo.websocket.event, modify it if using another server
websocket_cfg.cert_pem = cacert_start;
#endif
#if CONFIG_WS_OVER_TLS_SKIP_COMMON_NAME_CHECK
websocket_cfg.skip_cert_common_name_check = true;
#endif
ESP_LOGI(TAG, "Connecting to %s...", websocket_cfg.uri);
esp_websocket_client_handle_t client = esp_websocket_client_init(&websocket_cfg);

View File

@ -1,13 +1,16 @@
# SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
# SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Unlicense OR CC0-1.0
import json
import random
import re
import socket
import ssl
import string
import sys
from threading import Event, Thread
from SimpleWebSocketServer import SimpleWebSocketServer, WebSocket
from SimpleWebSocketServer import (SimpleSSLWebSocketServer,
SimpleWebSocketServer, WebSocket)
def get_my_ip():
@ -27,7 +30,7 @@ class WebsocketTestEcho(WebSocket):
def handleMessage(self):
self.sendMessage(self.data)
print('Server sent: {}'.format(self.data))
print('\n Server sent: {}\n'.format(self.data))
def handleConnected(self):
print('Connection from: {}'.format(self.address))
@ -44,12 +47,23 @@ class Websocket(object):
conn.sendMessage(data)
def run(self):
self.server = SimpleWebSocketServer('', self.port, WebsocketTestEcho)
if self.use_tls is True:
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
ssl_context.load_cert_chain(certfile='main/certs/server_cert.pem', keyfile='main/certs/server_key.pem')
if self.client_verify is True:
ssl_context.load_verify_locations(cafile='main/certs/ca_cert.pem')
ssl_context.verify = ssl.CERT_REQUIRED
ssl_context.check_hostname = False
self.server = SimpleSSLWebSocketServer('', self.port, WebsocketTestEcho, ssl_context=ssl_context)
else:
self.server = SimpleWebSocketServer('', self.port, WebsocketTestEcho)
while not self.exit_event.is_set():
self.server.serveonce()
def __init__(self, port):
def __init__(self, port, use_tls, verify):
self.port = port
self.use_tls = use_tls
self.client_verify = verify
self.exit_event = Event()
self.thread = Thread(target=self.run)
self.thread.start()
@ -77,6 +91,7 @@ def test_examples_protocol_websocket(dut):
for i in range(0, 5):
dut.expect(re.compile(b'Received=hello (\\d)'))
print('All echos received')
sys.stdout.flush()
def test_close(dut):
code = dut.expect(
@ -103,7 +118,8 @@ def test_examples_protocol_websocket(dut):
match = dut.expect(
re.compile(b'Json=({[a-zA-Z0-9]*).*}')).group(0).decode()[5:]
if match == str(data[0]):
print('Sent message and received message are equal')
print('Sent message and received message are equal \n')
sys.stdout.flush()
else:
raise ValueError(
'DUT received string do not match sent string, \nexpected: {}\nwith length {}\
@ -126,7 +142,8 @@ def test_examples_protocol_websocket(dut):
recv_msg += match
if recv_msg == send_msg:
print('Sent message and received message are equal')
print('Sent message and received message are equal \n')
sys.stdout.flush()
else:
raise ValueError(
'DUT received string do not match sent string, \nexpected: {}\nwith length {}\
@ -145,14 +162,24 @@ def test_examples_protocol_websocket(dut):
uri = dut.app.sdkconfig['WEBSOCKET_URI']
uri_from_stdin = False
if dut.app.sdkconfig.get('WS_OVER_TLS_MUTUAL_AUTH') is True:
use_tls = True
client_verify = True
else:
use_tls = False
client_verify = False
except Exception:
print('ENV_TEST_FAILURE: Cannot find uri settings in sdkconfig')
raise
if uri_from_stdin:
server_port = 8080
with Websocket(server_port) as ws:
uri = 'ws://{}:{}'.format(get_my_ip(), server_port)
with Websocket(server_port, use_tls, client_verify) as ws:
if use_tls is True:
uri = 'wss://{}:{}'.format(get_my_ip(), server_port)
else:
uri = 'ws://{}:{}'.format(get_my_ip(), server_port)
print('DUT connecting to {}'.format(uri))
dut.expect('Please enter uri of websocket endpoint', timeout=30)
dut.write(uri)

View File

@ -1,7 +1,7 @@
CONFIG_IDF_TARGET="esp32"
CONFIG_IDF_TARGET_LINUX=n
CONFIG_WEBSOCKET_URI_FROM_STDIN=y
CONFIG_WEBSOCKET_URI_FROM_STRING=n
CONFIG_WEBSOCKET_URI_FROM_STDIN=n
CONFIG_WEBSOCKET_URI_FROM_STRING=y
CONFIG_EXAMPLE_CONNECT_ETHERNET=y
CONFIG_EXAMPLE_CONNECT_WIFI=n
CONFIG_EXAMPLE_USE_INTERNAL_ETHERNET=y

View File

@ -0,0 +1,15 @@
CONFIG_IDF_TARGET="esp32"
CONFIG_IDF_TARGET_LINUX=n
CONFIG_WEBSOCKET_URI_FROM_STDIN=y
CONFIG_WEBSOCKET_URI_FROM_STRING=n
CONFIG_EXAMPLE_CONNECT_ETHERNET=y
CONFIG_EXAMPLE_CONNECT_WIFI=n
CONFIG_EXAMPLE_USE_INTERNAL_ETHERNET=y
CONFIG_EXAMPLE_ETH_PHY_IP101=y
CONFIG_EXAMPLE_ETH_MDC_GPIO=23
CONFIG_EXAMPLE_ETH_MDIO_GPIO=18
CONFIG_EXAMPLE_ETH_PHY_RST_GPIO=5
CONFIG_EXAMPLE_ETH_PHY_ADDR=1
CONFIG_EXAMPLE_CONNECT_IPV6=y
CONFIG_WS_OVER_TLS_MUTUAL_AUTH=y
CONFIG_WS_OVER_TLS_SKIP_COMMON_NAME_CHECK=y

View File

@ -0,0 +1,15 @@
CONFIG_IDF_TARGET="esp32"
CONFIG_IDF_TARGET_LINUX=n
CONFIG_WEBSOCKET_URI_FROM_STDIN=y
CONFIG_WEBSOCKET_URI_FROM_STRING=n
CONFIG_EXAMPLE_CONNECT_ETHERNET=y
CONFIG_EXAMPLE_CONNECT_WIFI=n
CONFIG_EXAMPLE_USE_INTERNAL_ETHERNET=y
CONFIG_EXAMPLE_ETH_PHY_IP101=y
CONFIG_EXAMPLE_ETH_MDC_GPIO=23
CONFIG_EXAMPLE_ETH_MDIO_GPIO=18
CONFIG_EXAMPLE_ETH_PHY_RST_GPIO=5
CONFIG_EXAMPLE_ETH_PHY_ADDR=1
CONFIG_EXAMPLE_CONNECT_IPV6=y
CONFIG_WS_OVER_TLS_MUTUAL_AUTH=n
CONFIG_WS_OVER_TLS_SERVER_AUTH=n

View File

@ -3,6 +3,6 @@ commitizen:
bump_message: 'bump(mdns): $current_version -> $new_version'
pre_bump_hooks: python ../../ci/changelog.py mdns
tag_format: mdns-v$version
version: 1.2.3
version: 1.2.4
version_files:
- idf_component.yml

View File

@ -1,5 +1,14 @@
# Changelog
## [1.2.4](https://github.com/espressif/esp-protocols/commits/mdns-v1.2.4)
### Bug Fixes
- Correction on 6d2c475 MDNS_PREDEF_NETIF_ETH fix ([fc59f87c4e](https://github.com/espressif/esp-protocols/commit/fc59f87c4e))
- fix the logic of creating pcb for networking socket ([5000a9a20a](https://github.com/espressif/esp-protocols/commit/5000a9a20a))
- fix compiling issue when disabling IPv4 ([2646dcd23a](https://github.com/espressif/esp-protocols/commit/2646dcd23a))
- Fix compile error when MDNS_PREDEF_NETIF_ETH is defined, but ETH_ENABLED is not (#459) ([6d2c475c20](https://github.com/espressif/esp-protocols/commit/6d2c475c20))
## [1.2.3](https://github.com/espressif/esp-protocols/commits/mdns-v1.2.3)
### Bug Fixes

View File

@ -21,6 +21,12 @@
#include "driver/gpio.h"
#include "netdb.h"
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 1, 0)
/* CONFIG_LWIP_IPV4 was introduced in IDF v5.1, set CONFIG_LWIP_IPV4 to 1 by default for IDF v5.0 */
#ifndef CONFIG_LWIP_IPV4
#define CONFIG_LWIP_IPV4 1
#endif // CONFIG_LWIP_IPV4
#endif // ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 1, 0)
#define EXAMPLE_MDNS_INSTANCE CONFIG_MDNS_INSTANCE
#define EXAMPLE_BUTTON_GPIO CONFIG_MDNS_BUTTON_GPIO
@ -227,7 +233,7 @@ static void query_mdns_hosts_async(const char *host_name)
vTaskDelay(50 / portTICK_PERIOD_MS);
}
}
#ifdef CONFIG_LWIP_IPV4
static void query_mdns_host(const char *host_name)
{
ESP_LOGI(TAG, "Query A: %s.local", host_name);
@ -247,6 +253,7 @@ static void query_mdns_host(const char *host_name)
ESP_LOGI(TAG, "Query A: %s.local resolved to: " IPSTR, host_name, IP2STR(&addr));
}
#endif // CONFIG_LWIP_IPV4
static void initialise_button(void)
{
@ -265,7 +272,9 @@ static void check_button(void)
bool new_level = gpio_get_level(EXAMPLE_BUTTON_GPIO);
if (!new_level && old_level) {
query_mdns_hosts_async("esp32-mdns");
#ifdef CONFIG_LWIP_IPV4
query_mdns_host("esp32");
#endif
query_mdns_service("_arduino", "_tcp");
query_mdns_service("_http", "_tcp");
query_mdns_service("_printer", "_tcp");
@ -286,7 +295,9 @@ static void mdns_example_task(void *pvParameters)
{
#if CONFIG_MDNS_RESOLVE_TEST_SERVICES == 1
/* Send initial queries that are started by CI tester */
#ifdef CONFIG_LWIP_IPV4
query_mdns_host("tinytester");
#endif
query_mdns_host_with_gethostbyname("tinytester-lwip.local");
query_mdns_host_with_getaddrinfo("tinytester-lwip.local");
#endif
@ -362,7 +373,16 @@ static void query_mdns_host_with_gethostbyname(char *host)
if (res) {
unsigned int i = 0;
while (res->h_addr_list[i] != NULL) {
ESP_LOGI(TAG, "gethostbyname: %s resolved to: %s", host, inet_ntoa(*(struct in_addr *) (res->h_addr_list[i])));
ESP_LOGI(TAG, "gethostbyname: %s resolved to: %s", host,
#if defined(CONFIG_LWIP_IPV6) && defined(CONFIG_LWIP_IPV4)
res->h_addrtype == AF_INET ? inet_ntoa(*(struct in_addr *) (res->h_addr_list[i])) :
inet6_ntoa(*(struct in6_addr *) (res->h_addr_list[i]))
#elif defined(CONFIG_LWIP_IPV6)
inet6_ntoa(*(struct in6_addr *) (res->h_addr_list[i]))
#else
inet_ntoa(*(struct in_addr *) (res->h_addr_list[i]))
#endif
);
i++;
}
}
@ -384,10 +404,12 @@ static void query_mdns_host_with_getaddrinfo(char *host)
if (!getaddrinfo(host, NULL, &hints, &res)) {
while (res) {
char *resolved_addr;
#if CONFIG_LWIP_IPV6
#if defined(CONFIG_LWIP_IPV6) && defined(CONFIG_LWIP_IPV4)
resolved_addr = res->ai_family == AF_INET ?
inet_ntoa(((struct sockaddr_in *) res->ai_addr)->sin_addr) :
inet_ntoa(((struct sockaddr_in6 *) res->ai_addr)->sin6_addr);
inet6_ntoa(((struct sockaddr_in6 *) res->ai_addr)->sin6_addr);
#elif defined(CONFIG_LWIP_IPV6)
resolved_addr = inet6_ntoa(((struct sockaddr_in6 *) res->ai_addr)->sin6_addr);
#else
resolved_addr = inet_ntoa(((struct sockaddr_in *) res->ai_addr)->sin_addr);
#endif // CONFIG_LWIP_IPV6

View File

@ -87,26 +87,30 @@ def mdns_server(esp_host, events):
continue
data, addr = sock.recvfrom(1024)
dns = dpkt.dns.DNS(data)
if len(dns.qd) > 0 and dns.qd[0].type == dpkt.dns.DNS_A:
if dns.qd[0].name == TESTER_NAME:
print('Received query: {} '.format(dns.__repr__()))
sock.sendto(get_dns_answer_to_mdns(TESTER_NAME),
(MCAST_GRP, UDP_PORT))
elif dns.qd[0].name == TESTER_NAME_LWIP:
print('Received query: {} '.format(dns.__repr__()))
sock.sendto(
get_dns_answer_to_mdns_lwip(TESTER_NAME_LWIP, dns.id),
addr)
if len(dns.an) > 0 and dns.an[0].type == dpkt.dns.DNS_A:
print('Received answer from {}'.format(dns.an[0].name))
if dns.an[0].name == esp_host + u'.local':
print('Received answer to esp32-mdns query: {}'.format(
dns.__repr__()))
events['esp_answered'].set()
if dns.an[0].name == esp_host + u'-delegated.local':
print('Received answer to esp32-mdns-delegate query: {}'.
format(dns.__repr__()))
events['esp_delegated_answered'].set()
if len(dns.qd) > 0:
for dns_query in dns.qd:
if dns_query.type == dpkt.dns.DNS_A:
if dns_query.name == TESTER_NAME:
print('Received query: {} '.format(dns.__repr__()))
sock.sendto(get_dns_answer_to_mdns(TESTER_NAME),
(MCAST_GRP, UDP_PORT))
elif dns_query.name == TESTER_NAME_LWIP:
print('Received query: {} '.format(dns.__repr__()))
sock.sendto(
get_dns_answer_to_mdns_lwip(TESTER_NAME_LWIP, dns.id),
addr)
if len(dns.an) > 0:
for dns_answer in dns.an:
if dns_answer.type == dpkt.dns.DNS_A:
print('Received answer from {}'.format(dns_answer.name))
if dns_answer.name == esp_host + u'.local':
print('Received answer to esp32-mdns query: {}'.format(
dns.__repr__()))
events['esp_answered'].set()
if dns_answer.name == esp_host + u'-delegated.local':
print('Received answer to esp32-mdns-delegate query: {}'.format(
dns.__repr__()))
events['esp_delegated_answered'].set()
except socket.timeout:
break
except dpkt.UnpackError:
@ -133,62 +137,66 @@ def test_examples_protocol_mdns(dut):
}
mdns_responder = Thread(target=mdns_server,
args=(str(specific_host), mdns_server_events))
ipv4 = dut.expect(r'IPv4 address: (\d+\.\d+\.\d+\.\d+)[^\d]',
timeout=30)[1].decode()
ip_addresses = [ipv4]
ip_addresses = []
if dut.app.sdkconfig.get('LWIP_IPV4') is True:
ipv4 = dut.expect(r'IPv4 address: (\d+\.\d+\.\d+\.\d+)[^\d]',
timeout=30)[1].decode()
ip_addresses.append(ipv4)
if dut.app.sdkconfig.get('LWIP_IPV6') is True:
ipv6_r = r':'.join((r'[0-9a-fA-F]{4}', ) * 8)
ipv6 = dut.expect(ipv6_r, timeout=30)[0].decode()
ip_addresses.append(ipv6)
print('Connected with IP addresses: {}'.format(','.join(ip_addresses)))
try:
# 3. check the mdns name is accessible.
# TODO: Add test for example disabling IPV4
mdns_responder.start()
if not mdns_server_events['esp_answered'].wait(timeout=30):
raise ValueError(
'Test has failed: did not receive mdns answer within timeout')
if not mdns_server_events['esp_delegated_answered'].wait(timeout=30):
raise ValueError(
'Test has failed: did not receive mdns answer for delegated host within timeout'
if dut.app.sdkconfig.get('LWIP_IPV4') is True:
# 3. check the mdns name is accessible.
if not mdns_server_events['esp_answered'].wait(timeout=30):
raise ValueError(
'Test has failed: did not receive mdns answer within timeout')
if not mdns_server_events['esp_delegated_answered'].wait(timeout=30):
raise ValueError(
'Test has failed: did not receive mdns answer for delegated host within timeout'
)
# 4. check DUT output if mdns advertized host is resolved
dut.expect(
re.compile(
b'mdns-test: Query A: tinytester.local resolved to: 127.0.0.1')
)
# 4. check DUT output if mdns advertized host is resolved
dut.expect(
re.compile(
b'mdns-test: Query A: tinytester.local resolved to: 127.0.0.1')
)
dut.expect(
re.compile(
b'mdns-test: gethostbyname: tinytester-lwip.local resolved to: 127.0.0.1'
))
dut.expect(
re.compile(
b'mdns-test: getaddrinfo: tinytester-lwip.local resolved to: 127.0.0.1'
))
# 5. check the DUT answers to `dig` command
dig_output = subprocess.check_output([
'dig', '+short', '-p', '5353', '@224.0.0.251',
'{}.local'.format(specific_host)
])
print('Resolving {} using "dig" succeeded with:\n{}'.format(
specific_host, dig_output))
if not ipv4.encode('utf-8') in dig_output:
raise ValueError(
'Test has failed: Incorrectly resolved DUT hostname using dig'
"Output should've contained DUT's IP address:{}".format(ipv4))
# 6. check the DUT reverse lookup
if dut.app.sdkconfig.get('MDNS_RESPOND_REVERSE_QUERIES') is True:
for ip_address in ip_addresses:
dig_output = subprocess.check_output([
'dig', '+short', '-p', '5353', '@224.0.0.251', '-x',
'{}'.format(ip_address)
])
print('Reverse lookup for {} using "dig" succeeded with:\n{}'.
format(ip_address, dig_output))
if specific_host not in dig_output.decode():
raise ValueError(
'Test has failed: Incorrectly resolved DUT IP address using dig'
"Output should've contained DUT's name:{}".format(
specific_host))
dut.expect(
re.compile(
b'mdns-test: gethostbyname: tinytester-lwip.local resolved to: 127.0.0.1'
))
dut.expect(
re.compile(
b'mdns-test: getaddrinfo: tinytester-lwip.local resolved to: 127.0.0.1'
))
# 5. check the DUT answers to `dig` command
dig_output = subprocess.check_output([
'dig', '+short', '-p', '5353', '@224.0.0.251',
'{}.local'.format(specific_host)
])
print('Resolving {} using "dig" succeeded with:\n{}'.format(
specific_host, dig_output))
if not ipv4.encode('utf-8') in dig_output:
raise ValueError(
'Test has failed: Incorrectly resolved DUT hostname using dig'
"Output should've contained DUT's IP address:{}".format(ipv4))
# 6. check the DUT reverse lookup
if dut.app.sdkconfig.get('MDNS_RESPOND_REVERSE_QUERIES') is True:
for ip_address in ip_addresses:
dig_output = subprocess.check_output([
'dig', '+short', '-p', '5353', '@224.0.0.251', '-x',
'{}'.format(ip_address)
])
print('Reverse lookup for {} using "dig" succeeded with:\n{}'.
format(ip_address, dig_output))
if specific_host not in dig_output.decode():
raise ValueError(
'Test has failed: Incorrectly resolved DUT IP address using dig'
"Output should've contained DUT's name:{}".format(
specific_host))
finally:
mdns_server_events['stop'].set()

View File

@ -0,0 +1,15 @@
CONFIG_IDF_TARGET="esp32"
CONFIG_MDNS_RESOLVE_TEST_SERVICES=y
CONFIG_MDNS_ADD_MAC_TO_HOSTNAME=y
CONFIG_MDNS_PUBLISH_DELEGATE_HOST=y
CONFIG_LWIP_DNS_SUPPORT_MDNS_QUERIES=y
CONFIG_LWIP_IPV4=n
CONFIG_EXAMPLE_CONNECT_ETHERNET=y
CONFIG_EXAMPLE_CONNECT_WIFI=n
CONFIG_EXAMPLE_USE_INTERNAL_ETHERNET=y
CONFIG_EXAMPLE_ETH_PHY_IP101=y
CONFIG_EXAMPLE_ETH_MDC_GPIO=23
CONFIG_EXAMPLE_ETH_MDIO_GPIO=18
CONFIG_EXAMPLE_ETH_PHY_RST_GPIO=5
CONFIG_EXAMPLE_ETH_PHY_ADDR=1
CONFIG_MDNS_BUTTON_GPIO=32

View File

@ -1,4 +1,4 @@
version: "1.2.3"
version: "1.2.4"
description: mDNS
url: https://github.com/espressif/esp-protocols/tree/master/components/mdns
dependencies:

View File

@ -184,6 +184,11 @@ static inline void _mdns_clean_netif_ptr(mdns_if_t tcpip_if)
static mdns_if_t _mdns_get_if_from_esp_netif(esp_netif_t *esp_netif)
{
for (int i = 0; i < MDNS_MAX_INTERFACES; ++i) {
// The predefined netifs in the static array are NULL when firstly calling this function
// if IPv4 is disabled. Set these netifs here.
if (s_esp_netifs[i].netif == NULL && s_esp_netifs[i].predefined) {
s_esp_netifs[i].netif = esp_netif_from_preset_if(s_esp_netifs[i].predef_if);
}
if (esp_netif == s_esp_netifs[i].netif) {
return i;
}
@ -1059,6 +1064,7 @@ static uint16_t _mdns_append_srv_record(uint8_t *packet, uint16_t *index, mdns_s
return record_length;
}
#ifdef CONFIG_LWIP_IPV4
/**
* @brief appends A record to a packet, incrementing the index
*
@ -1108,8 +1114,9 @@ static uint16_t _mdns_append_a_record(uint8_t *packet, uint16_t *index, const ch
record_length += 4;
return record_length;
}
#endif /* CONFIG_LWIP_IPV4 */
#if CONFIG_LWIP_IPV6
#ifdef CONFIG_LWIP_IPV6
/**
* @brief appends AAAA record to a packet, incrementing the index
*
@ -1232,7 +1239,7 @@ static bool _mdns_if_is_dup(mdns_if_t tcpip_if)
return false;
}
#if CONFIG_LWIP_IPV6
#ifdef CONFIG_LWIP_IPV6
/**
* @brief Check if IPv6 address is NULL
*/
@ -1247,7 +1254,7 @@ static bool _ipv6_address_is_zero(esp_ip6_addr_t ip6)
}
return true;
}
#endif
#endif /* CONFIG_LWIP_IPV6 */
static uint8_t _mdns_append_host_answer(uint8_t *packet, uint16_t *index, mdns_host_item_t *host,
uint8_t address_type, bool flush, bool bye)
@ -1257,17 +1264,19 @@ static uint8_t _mdns_append_host_answer(uint8_t *packet, uint16_t *index, mdns_h
while (addr != NULL) {
if (addr->addr.type == address_type) {
#ifdef CONFIG_LWIP_IPV4
if (address_type == ESP_IPADDR_TYPE_V4 &&
_mdns_append_a_record(packet, index, host->hostname, addr->addr.u_addr.ip4.addr, flush, bye) <= 0) {
break;
}
#if CONFIG_LWIP_IPV6
#endif /* CONFIG_LWIP_IPV4 */
#ifdef CONFIG_LWIP_IPV6
if (address_type == ESP_IPADDR_TYPE_V6 &&
_mdns_append_aaaa_record(packet, index, host->hostname, (uint8_t *)addr->addr.u_addr.ip6.addr, flush,
bye) <= 0) {
break;
}
#endif // CONFIG_LWIP_IPV6
#endif /* CONFIG_LWIP_IPV6 */
num_records++;
}
addr = addr->next;
@ -1360,7 +1369,9 @@ static uint8_t _mdns_append_answer(uint8_t *packet, uint16_t *index, mdns_out_an
return _mdns_append_txt_record(packet, index, answer->service, answer->flush, answer->bye) > 0;
} else if (answer->type == MDNS_TYPE_SDPTR) {
return _mdns_append_sdptr_record(packet, index, answer->service, answer->flush, answer->bye) > 0;
} else if (answer->type == MDNS_TYPE_A) {
}
#ifdef CONFIG_LWIP_IPV4
else if (answer->type == MDNS_TYPE_A) {
if (answer->host == &_mdns_self_host) {
esp_netif_ip_info_t if_ip_info;
if (!mdns_is_netif_ready(tcpip_if, MDNS_IP_PROTOCOL_V4) && _mdns_server->interfaces[tcpip_if].pcbs[MDNS_IP_PROTOCOL_V4].state != PCB_DUP) {
@ -1387,7 +1398,8 @@ static uint8_t _mdns_append_answer(uint8_t *packet, uint16_t *index, mdns_out_an
return _mdns_append_host_answer(packet, index, answer->host, ESP_IPADDR_TYPE_V4, answer->flush, answer->bye);
}
}
#if CONFIG_LWIP_IPV6
#endif /* CONFIG_LWIP_IPV4 */
#ifdef CONFIG_LWIP_IPV6
else if (answer->type == MDNS_TYPE_AAAA) {
if (answer->host == &_mdns_self_host) {
struct esp_ip6_addr if_ip6s[NETIF_IPV6_MAX_NUMS];
@ -1425,7 +1437,7 @@ static uint8_t _mdns_append_answer(uint8_t *packet, uint16_t *index, mdns_out_an
answer->bye);
}
}
#endif
#endif /* CONFIG_LWIP_IPV6 */
return 0;
}
@ -1713,12 +1725,14 @@ static mdns_tx_packet_t *_mdns_alloc_packet_default(mdns_if_t tcpip_if, mdns_ip_
packet->tcpip_if = tcpip_if;
packet->ip_protocol = ip_protocol;
packet->port = MDNS_SERVICE_PORT;
#ifdef CONFIG_LWIP_IPV4
if (ip_protocol == MDNS_IP_PROTOCOL_V4) {
esp_ip_addr_t addr = ESP_IP4ADDR_INIT(224, 0, 0, 251);
memcpy(&packet->dst, &addr, sizeof(esp_ip_addr_t));
}
#if CONFIG_LWIP_IPV6
else {
#endif
#ifdef CONFIG_LWIP_IPV6
if (ip_protocol == MDNS_IP_PROTOCOL_V6) {
esp_ip_addr_t addr = ESP_IP6ADDR_INIT(0x000002ff, 0, 0, 0xfb000000);
memcpy(&packet->dst, &addr, sizeof(esp_ip_addr_t));
}
@ -2877,6 +2891,7 @@ static void _mdns_dup_interface(mdns_if_t tcpip_if)
}
}
#ifdef CONFIG_LWIP_IPV4
/**
* @brief Detect IPv4 address collision
*/
@ -2890,7 +2905,6 @@ static int _mdns_check_a_collision(esp_ip4_addr_t *ip, mdns_if_t tcpip_if)
if (esp_netif_get_ip_info(_mdns_get_esp_netif(tcpip_if), &if_ip_info)) {
return 1;//they win
}
int ret = memcmp((uint8_t *)&if_ip_info.ip.addr, (uint8_t *)&ip->addr, sizeof(esp_ip4_addr_t));
if (ret > 0) {
return -1;//we win
@ -2911,8 +2925,9 @@ static int _mdns_check_a_collision(esp_ip4_addr_t *ip, mdns_if_t tcpip_if)
}
return 0;//same
}
#endif /* CONFIG_LWIP_IPV4 */
#if CONFIG_LWIP_IPV6
#ifdef CONFIG_LWIP_IPV6
/**
* @brief Detect IPv6 address collision
*/
@ -2946,7 +2961,7 @@ static int _mdns_check_aaaa_collision(esp_ip6_addr_t *ip, mdns_if_t tcpip_if)
}
return 0;//same
}
#endif
#endif /* CONFIG_LWIP_IPV6 */
static bool _hostname_is_ours(const char *hostname)
{
@ -3509,22 +3524,25 @@ void mdns_parse_packet(mdns_rx_packet_t *packet)
#ifndef CONFIG_MDNS_SKIP_SUPPRESSING_OWN_QUERIES
// Check if the packet wasn't sent by us
#ifdef CONFIG_LWIP_IPV4
if (packet->ip_protocol == MDNS_IP_PROTOCOL_V4) {
esp_netif_ip_info_t if_ip_info;
if (esp_netif_get_ip_info(_mdns_get_esp_netif(packet->tcpip_if), &if_ip_info) == ESP_OK &&
memcmp(&if_ip_info.ip.addr, &packet->src.u_addr.ip4.addr, sizeof(esp_ip4_addr_t)) == 0) {
return;
}
#if CONFIG_LWIP_IPV6
} else {
}
#endif /* CONFIG_LWIP_IPV4 */
#ifdef CONFIG_LWIP_IPV6
if (packet->ip_protocol == MDNS_IP_PROTOCOL_V6) {
struct esp_ip6_addr if_ip6;
if (esp_netif_get_ip6_linklocal(_mdns_get_esp_netif(packet->tcpip_if), &if_ip6) == ESP_OK &&
memcmp(&if_ip6, &packet->src.u_addr.ip6, sizeof(esp_ip6_addr_t)) == 0) {
return;
}
#endif
}
#endif
#endif /* CONFIG_LWIP_IPV6 */
#endif // CONFIG_MDNS_SKIP_SUPPRESSING_OWN_QUERIES
// Check for the minimum size of mdns packet
if (len <= MDNS_HEAD_ADDITIONAL_OFFSET) {
@ -3887,7 +3905,7 @@ void mdns_parse_packet(mdns_rx_packet_t *packet)
}
}
#if CONFIG_LWIP_IPV6
#ifdef CONFIG_LWIP_IPV6
else if (type == MDNS_TYPE_AAAA) {//ipv6
esp_ip_addr_t ip6;
ip6.type = ESP_IPADDR_TYPE_V6;
@ -3940,7 +3958,8 @@ void mdns_parse_packet(mdns_rx_packet_t *packet)
}
}
#endif
#endif /* CONFIG_LWIP_IPV6 */
#ifdef CONFIG_LWIP_IPV4
else if (type == MDNS_TYPE_A) {
esp_ip_addr_t ip;
ip.type = ESP_IPADDR_TYPE_V4;
@ -3993,6 +4012,7 @@ void mdns_parse_packet(mdns_rx_packet_t *packet)
}
}
#endif /* CONFIG_LWIP_IPV4 */
}
//end while
if (parsed_packet->authoritative) {
@ -4094,6 +4114,7 @@ static void perform_event_action(mdns_if_t mdns_if, mdns_event_actions_t action)
}
#ifdef CONFIG_MDNS_RESPOND_REVERSE_QUERIES
#ifdef CONFIG_LWIP_IPV4
if (action & MDNS_EVENT_IP4_REVERSE_LOOKUP) {
esp_netif_ip_info_t if_ip_info;
if (esp_netif_get_ip_info(_mdns_get_esp_netif(mdns_if), &if_ip_info) == ESP_OK) {
@ -4107,7 +4128,7 @@ static void perform_event_action(mdns_if_t mdns_if, mdns_event_actions_t action)
}
}
}
#endif /* CONFIG_LWIP_IPV4 */
#ifdef CONFIG_LWIP_IPV6
if (action & MDNS_EVENT_IP6_REVERSE_LOOKUP) {
esp_ip6_addr_t addr6;
@ -5483,22 +5504,25 @@ esp_err_t mdns_init(void)
#endif
uint8_t i;
#if CONFIG_LWIP_IPV6
#ifdef CONFIG_LWIP_IPV6
esp_ip6_addr_t tmp_addr6;
#endif
#ifdef CONFIG_LWIP_IPV4
esp_netif_ip_info_t if_ip_info;
#endif
for (i = 0; i < MDNS_MAX_INTERFACES; i++) {
#if CONFIG_LWIP_IPV6
#ifdef CONFIG_LWIP_IPV6
if (!esp_netif_get_ip6_linklocal(_mdns_get_esp_netif(i), &tmp_addr6) && !_ipv6_address_is_zero(tmp_addr6)) {
_mdns_enable_pcb(i, MDNS_IP_PROTOCOL_V6);
}
#endif
#ifdef CONFIG_LWIP_IPV4
if (!esp_netif_get_ip_info(_mdns_get_esp_netif(i), &if_ip_info) && if_ip_info.ip.addr) {
_mdns_enable_pcb(i, MDNS_IP_PROTOCOL_V4);
}
#endif
}
if (_mdns_service_task_start()) {
//service start failed!
err = ESP_FAIL;
@ -6468,6 +6492,7 @@ esp_err_t mdns_lookup_selfhosted_service(const char *instance, const char *servi
return ESP_OK;
}
#ifdef CONFIG_LWIP_IPV4
esp_err_t mdns_query_a(const char *name, uint32_t timeout, esp_ip4_addr_t *addr)
{
mdns_result_t *result = NULL;
@ -6504,8 +6529,9 @@ esp_err_t mdns_query_a(const char *name, uint32_t timeout, esp_ip4_addr_t *addr)
mdns_query_results_free(result);
return ESP_ERR_NOT_FOUND;
}
#endif /* CONFIG_LWIP_IPV4 */
#if CONFIG_LWIP_IPV6
#ifdef CONFIG_LWIP_IPV6
esp_err_t mdns_query_aaaa(const char *name, uint32_t timeout, esp_ip6_addr_t *addr)
{
mdns_result_t *result = NULL;
@ -6542,7 +6568,7 @@ esp_err_t mdns_query_aaaa(const char *name, uint32_t timeout, esp_ip6_addr_t *ad
mdns_query_results_free(result);
return ESP_ERR_NOT_FOUND;
}
#endif
#endif /* CONFIG_LWIP_IPV6 */
#ifdef MDNS_ENABLE_DEBUG

View File

@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@ -8,6 +8,7 @@
#include "esp_console.h"
#include "argtable3/argtable3.h"
#include "mdns.h"
#include "mdns_private.h"
static const char *ip_protocol_str[] = {"V4", "V6", "MAX"};
@ -50,6 +51,7 @@ static struct {
struct arg_end *end;
} mdns_query_a_args;
#ifdef CONFIG_LWIP_IPV4
static int cmd_mdns_query_a(int argc, char **argv)
{
int nerrors = arg_parse(argc, argv, (void **) &mdns_query_a_args);
@ -106,8 +108,9 @@ static void register_mdns_query_a(void)
ESP_ERROR_CHECK( esp_console_cmd_register(&cmd_init) );
}
#endif /* CONFIG_LWIP_IPV4 */
#if CONFIG_LWIP_IPV6
#ifdef CONFIG_LWIP_IPV6
static int cmd_mdns_query_aaaa(int argc, char **argv)
{
int nerrors = arg_parse(argc, argv, (void **) &mdns_query_a_args);
@ -164,7 +167,7 @@ static void register_mdns_query_aaaa(void)
ESP_ERROR_CHECK( esp_console_cmd_register(&cmd_init) );
}
#endif
#endif /* CONFIG_LWIP_IPV6 */
static struct {
struct arg_str *instance;
@ -1042,8 +1045,10 @@ void mdns_console_register(void)
register_mdns_service_txt_remove();
register_mdns_service_remove_all();
#ifdef CONFIG_LWIP_IPV4
register_mdns_query_a();
#if CONFIG_LWIP_IPV6
#endif
#ifdef CONFIG_LWIP_IPV6
register_mdns_query_aaaa();
#endif
register_mdns_query_txt();

View File

@ -96,6 +96,7 @@ static esp_err_t _udp_join_group(mdns_if_t if_inx, mdns_ip_protocol_t ip_protoco
netif = esp_netif_get_netif_impl(tcpip_if);
assert(netif);
#if LWIP_IPV4
if (ip_protocol == MDNS_IP_PROTOCOL_V4) {
ip4_addr_t multicast_addr;
IP4_ADDR(&multicast_addr, 224, 0, 0, 251);
@ -110,21 +111,22 @@ static esp_err_t _udp_join_group(mdns_if_t if_inx, mdns_ip_protocol_t ip_protoco
}
}
}
#if CONFIG_LWIP_IPV6
else {
#endif // LWIP_IPV4
#if LWIP_IPV6
if (ip_protocol == MDNS_IP_PROTOCOL_V6) {
ip_addr_t multicast_addr = IPADDR6_INIT(0x000002ff, 0, 0, 0xfb000000);
if (join) {
if (mld6_joingroup_netif(netif, &(multicast_addr.u_addr.ip6))) {
if (mld6_joingroup_netif(netif, ip_2_ip6(&multicast_addr))) {
return ESP_ERR_INVALID_STATE;
}
} else {
if (mld6_leavegroup_netif(netif, &(multicast_addr.u_addr.ip6))) {
if (mld6_leavegroup_netif(netif, ip_2_ip6(&multicast_addr))) {
return ESP_ERR_INVALID_STATE;
}
}
}
#endif
#endif // LWIP_IPV6
return ESP_OK;
}
@ -152,29 +154,34 @@ static void _udp_recv(void *arg, struct udp_pcb *upcb, struct pbuf *pb, const ip
packet->tcpip_if = MDNS_MAX_INTERFACES;
packet->pb = this_pb;
packet->src_port = rport;
#if CONFIG_LWIP_IPV6
#if LWIP_IPV4 && LWIP_IPV6
packet->src.type = raddr->type;
memcpy(&packet->src.u_addr, &raddr->u_addr, sizeof(raddr->u_addr));
#else
#elif LWIP_IPV4
packet->src.type = IPADDR_TYPE_V4;
memcpy(&packet->src.u_addr.ip4, &raddr->addr, sizeof(ip_addr_t));
packet->src.u_addr.ip4.addr = raddr->addr;
#elif LWIP_IPV6
packet->src.type = IPADDR_TYPE_V6;
memcpy(&packet->src.u_addr.ip6, raddr, sizeof(ip_addr_t));
#endif
packet->dest.type = packet->src.type;
#if LWIP_IPV4
if (packet->src.type == IPADDR_TYPE_V4) {
packet->ip_protocol = MDNS_IP_PROTOCOL_V4;
struct ip_hdr *iphdr = (struct ip_hdr *)(((uint8_t *)(packet->pb->payload)) - UDP_HLEN - IP_HLEN);
packet->dest.u_addr.ip4.addr = iphdr->dest.addr;
packet->multicast = ip4_addr_ismulticast(&(packet->dest.u_addr.ip4));
}
#if CONFIG_LWIP_IPV6
else {
#endif // LWIP_IPV4
#if LWIP_IPV6
if (packet->src.type == IPADDR_TYPE_V6) {
packet->ip_protocol = MDNS_IP_PROTOCOL_V6;
struct ip6_hdr *ip6hdr = (struct ip6_hdr *)(((uint8_t *)(packet->pb->payload)) - UDP_HLEN - IP6_HLEN);
memcpy(&packet->dest.u_addr.ip6.addr, (uint8_t *)ip6hdr->dest.addr, 16);
packet->multicast = ip6_addr_ismulticast(&(packet->dest.u_addr.ip6));
}
#endif
#endif // LWIP_IPV6
//lwip does not return the proper pcb if you have more than one for the same multicast address (but different interfaces)
struct netif *netif = NULL;
@ -182,15 +189,14 @@ static void _udp_recv(void *arg, struct udp_pcb *upcb, struct pbuf *pb, const ip
for (i = 0; i < MDNS_MAX_INTERFACES; i++) {
netif = esp_netif_get_netif_impl(_mdns_get_esp_netif(i));
if (s_interfaces[i].proto && netif && netif == ip_current_input_netif ()) {
#if LWIP_IPV4
if (packet->src.type == IPADDR_TYPE_V4) {
#if CONFIG_LWIP_IPV6
if ((packet->src.u_addr.ip4.addr & netif->netmask.u_addr.ip4.addr) != (netif->ip_addr.u_addr.ip4.addr & netif->netmask.u_addr.ip4.addr)) {
#else
if ((packet->src.u_addr.ip4.addr & netif->netmask.addr) != (netif->ip_addr.addr & netif->netmask.addr)) {
#endif //packet source is not in the same subnet
if ((packet->src.u_addr.ip4.addr & ip_2_ip4(&netif->netmask)->addr) != (ip_2_ip4(&netif->ip_addr)->addr & ip_2_ip4(&netif->netmask)->addr)) {
//packet source is not in the same subnet
break;
}
}
#endif // LWIP_IPV4
packet->tcpip_if = i;
found = true;
break;
@ -347,12 +353,17 @@ size_t _mdns_udp_pcb_write(mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol, c
memcpy((uint8_t *)pbt->payload, data, len);
ip_addr_t ip_add_copy;
#if CONFIG_LWIP_IPV6
#if LWIP_IPV6 && LWIP_IPV4
ip_add_copy.type = ip->type;
memcpy(&(ip_add_copy.u_addr), &(ip->u_addr), sizeof(ip_add_copy.u_addr));
#else
memcpy(&(ip_add_copy.addr), &(ip->u_addr), sizeof(ip_add_copy.addr));
#endif // CONFIG_LWIP_IPV6
#elif LWIP_IPV4
ip_add_copy.addr = ip->u_addr.ip4.addr;
#elif LWIP_IPV6
#if LWIP_IPV6_SCOPES
ip_add_copy.zone = ip->u_addr.ip6.zone;
#endif // LWIP_IPV6_SCOPES
memcpy(ip_add_copy.addr, ip->u_addr.ip6.addr, sizeof(ip_add_copy.addr));
#endif
mdns_api_call_t msg = {
.tcpip_if = tcpip_if,

View File

@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@ -138,11 +138,13 @@ static inline char *get_string_address(struct sockaddr_storage *source_addr)
static char address_str[40]; // 40=(8*4+7+term) is the max size of ascii IPv6 addr "XXXX:XX...XX:XXXX"
char *res = NULL;
// Convert ip address to string
#ifdef CONFIG_LWIP_IPV4
if (source_addr->ss_family == PF_INET) {
res = inet_ntoa_r(((struct sockaddr_in *)source_addr)->sin_addr, address_str, sizeof(address_str));
}
#endif
#ifdef CONFIG_LWIP_IPV6
else if (source_addr->ss_family == PF_INET6) {
if (source_addr->ss_family == PF_INET6) {
res = inet6_ntoa_r(((struct sockaddr_in6 *)source_addr)->sin6_addr, address_str, sizeof(address_str));
}
#endif
@ -157,6 +159,7 @@ static inline size_t espaddr_to_inet(const esp_ip_addr_t *addr, const uint16_t p
{
size_t ss_addr_len = 0;
memset(in_addr, 0, sizeof(struct sockaddr_storage));
#ifdef CONFIG_LWIP_IPV4
if (ip_protocol == MDNS_IP_PROTOCOL_V4 && addr->type == ESP_IPADDR_TYPE_V4) {
in_addr->ss_family = PF_INET;
#if !defined(CONFIG_IDF_TARGET_LINUX)
@ -167,8 +170,9 @@ static inline size_t espaddr_to_inet(const esp_ip_addr_t *addr, const uint16_t p
in_addr_ip4->sin_port = port;
in_addr_ip4->sin_addr.s_addr = addr->u_addr.ip4.addr;
}
#if CONFIG_LWIP_IPV6
else if (ip_protocol == MDNS_IP_PROTOCOL_V6 && addr->type == ESP_IPADDR_TYPE_V6) {
#endif // CONFIG_LWIP_IPV4
#ifdef CONFIG_LWIP_IPV6
if (ip_protocol == MDNS_IP_PROTOCOL_V6 && addr->type == ESP_IPADDR_TYPE_V6) {
memset(in_addr, 0, sizeof(struct sockaddr_storage));
in_addr->ss_family = PF_INET6;
#if !defined(CONFIG_IDF_TARGET_LINUX)
@ -212,6 +216,7 @@ size_t _mdns_udp_pcb_write(mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol, c
static inline void inet_to_espaddr(const struct sockaddr_storage *in_addr, esp_ip_addr_t *addr, uint16_t *port)
{
#ifdef CONFIG_LWIP_IPV4
if (in_addr->ss_family == PF_INET) {
struct sockaddr_in *in_addr_ip4 = (struct sockaddr_in *)in_addr;
memset(addr, 0, sizeof(esp_ip_addr_t));
@ -219,8 +224,9 @@ static inline void inet_to_espaddr(const struct sockaddr_storage *in_addr, esp_i
addr->u_addr.ip4.addr = in_addr_ip4->sin_addr.s_addr;
addr->type = ESP_IPADDR_TYPE_V4;
}
#if CONFIG_LWIP_IPV6
else if (in_addr->ss_family == PF_INET6) {
#endif /* CONFIG_LWIP_IPV4 */
#ifdef CONFIG_LWIP_IPV6
if (in_addr->ss_family == PF_INET6) {
struct sockaddr_in6 *in_addr_ip6 = (struct sockaddr_in6 *)in_addr;
memset(addr, 0, sizeof(esp_ip_addr_t));
*port = in_addr_ip6->sin6_port;
@ -346,17 +352,9 @@ static bool create_pcb(mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol)
}
int sock = s_interfaces[tcpip_if].sock;
esp_netif_t *netif = _mdns_get_esp_netif(tcpip_if);
if (sock >= 0) {
mdns_ip_protocol_t other_ip_proto = ip_protocol == MDNS_IP_PROTOCOL_V4 ? MDNS_IP_PROTOCOL_V6 : MDNS_IP_PROTOCOL_V4;
int err = join_mdns_multicast_group(sock, netif, other_ip_proto);
if (err < 0) {
ESP_LOGE(TAG, "Failed to add ipv6 multicast group for protocol %d", ip_protocol);
return false;
}
s_interfaces[tcpip_if].proto |= (other_ip_proto == MDNS_IP_PROTOCOL_V4 ? PROTO_IPV4 : PROTO_IPV6);
return true;
if (sock < 0) {
sock = create_socket(netif);
}
sock = create_socket(netif);
if (sock < 0) {
ESP_LOGE(TAG, "Failed to create the socket!");
return false;
@ -383,7 +381,7 @@ esp_err_t _mdns_pcb_init(mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol)
static int create_socket(esp_netif_t *netif)
{
#if CONFIG_LWIP_IPV6
#ifdef CONFIG_LWIP_IPV6
int sock = socket(PF_INET6, SOCK_DGRAM, 0);
#else
int sock = socket(PF_INET, SOCK_DGRAM, 0);
@ -398,7 +396,7 @@ static int create_socket(esp_netif_t *netif)
ESP_LOGE(TAG, "Failed setsockopt() to set SO_REUSEADDR. errno=%d: %s\n", errno, strerror(errno));
}
// Bind the socket to any address
#if CONFIG_LWIP_IPV6
#ifdef CONFIG_LWIP_IPV6
struct sockaddr_in6 saddr = { INADDR_ANY };
saddr.sin6_family = AF_INET6;
saddr.sin6_port = htons(5353);
@ -434,7 +432,7 @@ err:
return -1;
}
#if CONFIG_LWIP_IPV6
#ifdef CONFIG_LWIP_IPV6
static int socket_add_ipv6_multicast_group(int sock, esp_netif_t *netif)
{
int ifindex = esp_netif_get_netif_impl_index(netif);
@ -457,6 +455,7 @@ static int socket_add_ipv6_multicast_group(int sock, esp_netif_t *netif)
}
#endif // CONFIG_LWIP_IPV6
#ifdef CONFIG_LWIP_IPV4
static int socket_add_ipv4_multicast_group(int sock, esp_netif_t *netif)
{
struct ip_mreq imreq = { 0 };
@ -481,13 +480,16 @@ static int socket_add_ipv4_multicast_group(int sock, esp_netif_t *netif)
err:
return err;
}
#endif // CONFIG_LWIP_IPV4
static int join_mdns_multicast_group(int sock, esp_netif_t *netif, mdns_ip_protocol_t ip_protocol)
{
#ifdef CONFIG_LWIP_IPV4
if (ip_protocol == MDNS_IP_PROTOCOL_V4) {
return socket_add_ipv4_multicast_group(sock, netif);
}
#if CONFIG_LWIP_IPV6
#endif // CONFIG_LWIP_IPV4
#ifdef CONFIG_LWIP_IPV6
if (ip_protocol == MDNS_IP_PROTOCOL_V6) {
return socket_add_ipv6_multicast_group(sock, netif);
}

View File

@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@ -39,6 +39,14 @@
#define NETIF_IPV6_MAX_NUMS 3
#endif
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 1, 0)
/* CONFIG_LWIP_IPV4 was introduced in IDF v5.1 */
/* For IDF v5.0, set CONFIG_LWIP_IPV4 to 1 by default */
#ifndef CONFIG_LWIP_IPV4
#define CONFIG_LWIP_IPV4 1
#endif // CONFIG_LWIP_IPV4
#endif // ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 1, 0)
/** Number of configured interfaces */
#if MDNS_MAX_PREDEF_INTERFACES > CONFIG_MDNS_MAX_INTERFACES
#warning Number of configured interfaces is less then number of predefined interfaces. Please update CONFIG_MDNS_MAX_INTERFACES.

View File

@ -6,4 +6,10 @@ menu "LWIP-MOCK-CONFIG"
help
Enable/disable IPv6
config LWIP_IPV4
bool "Enable IPv4"
default y
help
Enable/disable IPv4
endmenu

22
examples/README.md Normal file
View File

@ -0,0 +1,22 @@
# Examples
## Overview
This directory showcases a variety of examples, illustrating the use of different interfaces and functionalities. Navigate through these examples to understand how to implement and integrate various components and features in your projects.
## Detailed Examples
1. **Multiple Network Interfaces Example**: Demonstrates the integration and management of multiple network interfaces.
Location: [multiple_netifs](esp_netif/multiple_netifs)
2. **SLIP Device Client**: Provides a detailed example of a SLIP (Serial Line Internet Protocol) device client implementation.
Location: [slip_custom_netif](esp_netif/slip_custom_netif/)
3. **MQTT Demo on Linux**: A comprehensive demonstration of an MQTT (Message Queuing Telemetry Transport) application designed to run on a Linux environment.
Location: [mqtt](mqtt)
## Additional Resources and Examples
For an extensive collection of additional examples, especially those related to specific components, please visit the upper layer directory and navigate to the respective component's example directory.
**Important**: To explore more, check the path: `components/*component_name/examples`