feat(esp_eth): Added destination MAC address filter configuration interface

This commit is contained in:
Ondrej Kosta
2025-02-13 12:46:44 +01:00
parent 7141b9558b
commit e941910c0f
27 changed files with 1251 additions and 257 deletions

View File

@@ -3,7 +3,7 @@
*
* SPDX-License-Identifier: Apache-2.0
*
* SPDX-FileContributor: 2024 Espressif Systems (Shanghai) CO LTD
* SPDX-FileContributor: 2024-2025 Espressif Systems (Shanghai) CO LTD
*/
/****************************************************************************
@@ -83,6 +83,11 @@
#define ETH_TYPE_PTP 0x88F7
#define SET_MAC_ADDR(addr, a, b, c, d, e, f) do { \
addr[0] = a; addr[1] = b; addr[2] = c; \
addr[3] = d; addr[4] = e; addr[5] = f; \
} while(0)
#define ERROR ESP_FAIL
#define OK ESP_OK
@@ -651,6 +656,13 @@ static int ptp_initialize_state(FAR struct ptp_state_s *state,
// get HW address
esp_eth_ioctl(eth_handle, ETH_CMD_G_MAC_ADDR, &state->intf_hw_addr);
// Add well-known PTP multicast destination MAC addresses to the filter
uint8_t dest_addr[ETH_ADDR_LEN];
SET_MAC_ADDR(dest_addr, 0x01, 0x1B, 0x19, 0x00, 0x00, 0x00);
esp_eth_ioctl(eth_handle, ETH_CMD_ADD_MAC_FILTER, dest_addr);
SET_MAC_ADDR(dest_addr, 0x01, 0x80, 0xC2, 0x00, 0x00, 0x0E);
esp_eth_ioctl(eth_handle, ETH_CMD_ADD_MAC_FILTER, dest_addr);
state->remote_time_ns_prev = 0;
state->local_time_ns_prev = 0;
@@ -845,6 +857,19 @@ static int ptp_initialize_state(FAR struct ptp_state_s *state,
static int ptp_destroy_state(FAR struct ptp_state_s *state)
{
#ifdef ESP_PTP
// Remove well-known PTP multicast destination MAC addresses from the filter
esp_eth_handle_t eth_handle;
if (ioctl(state->ptp_socket, L2TAP_G_DEVICE_DRV_HNDL, &eth_handle) < 0)
{
ptperr("failed to get socket eth_handle %d\n", errno);
return ERROR;
}
uint8_t dest_addr[ETH_ADDR_LEN];
SET_MAC_ADDR(dest_addr, 0x01, 0x1B, 0x19, 0x00, 0x00, 0x00);
esp_eth_ioctl(eth_handle, ETH_CMD_DEL_MAC_FILTER, dest_addr);
SET_MAC_ADDR(dest_addr, 0x01, 0x80, 0xC2, 0x00, 0x00, 0x0E);
esp_eth_ioctl(eth_handle, ETH_CMD_DEL_MAC_FILTER, dest_addr);
if (state->ptp_socket > 0)
{
close(state->ptp_socket);

View File

@@ -252,6 +252,11 @@ examples/protocols/sockets/tcp_client:
- if: SOC_WIFI_SUPPORTED != 1
# linux target won't work with CONFIG_EXAMPLE_SOCKET_IP_INPUT_STDIN=y
examples/protocols/sockets/udp_multicast:
<<: *default_rules
disable_test:
- if: IDF_TARGET not in ["esp32", "esp32p4"]
examples/protocols/static_ip:
<<: *default_rules
disable_test:

View File

@@ -0,0 +1,166 @@
# SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
import ipaddress
import logging
import os
import socket
import struct
from typing import Any
import netifaces
import pytest
from common_test_methods import get_host_ip4_by_dest_ip
from pytest_embedded import Dut
from pytest_embedded_idf.utils import idf_parametrize
PORT = 3333
IPV6_REGEX = (
r'(?<![0-9a-fA-F:])('
r'(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|'
r'(([0-9a-fA-F]{1,4}:){1,7}:)|'
r'(([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4})|'
r'(([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2})|'
r'(([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3})|'
r'(([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4})|'
r'(([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5})|'
r'([0-9a-fA-F]{1,4}:)((:[0-9a-fA-F]{1,4}){1,6})|'
r'(:)((:[0-9a-fA-F]{1,4}){1,7}|:)'
r'))(?![0-9a-fA-F:])[^\w]'
)
def find_target_if(my_if: str = '') -> str:
# try to determine which interface to use
netifs = os.listdir('/sys/class/net/')
# order matters - ETH NIC with the highest number is connected to DUT on CI runner
netifs.sort(reverse=True)
logging.info('detected interfaces: %s', str(netifs))
for netif in netifs:
# if no interface defined, try to find it automatically
if my_if == '':
if netif.find('eth') == 0 or netif.find('enp') == 0 or netif.find('eno') == 0:
return netif
else:
if netif.find(my_if) == 0:
return my_if
raise RuntimeError('network interface not found')
def get_link_local_for_interface(if_name: str) -> Any:
addrs = netifaces.ifaddresses(if_name)
if netifaces.AF_INET6 in addrs:
for addr in addrs[netifaces.AF_INET6]:
if addr['addr'].startswith('fe80:'):
return addr['addr'].split('%')[0]
raise RuntimeError(f'No link-local address found on interface {if_name}')
def test_examples_udp_multicast_proto(dut: Dut, ip_version: str = 'ipv4', nic: str = '') -> None:
"""
Test UDP multicast functionality for both IPv4 and IPv6.
Args:
dut: Device under test
ip_version: 'ipv4', ipv4_mapped or 'ipv6'
"""
# Create multicast socket based on IP version
if ip_version == 'ipv4' or ip_version == 'ipv4_mapped':
# IPv4 socket
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 32)
# Get ESP32's IPv4 address and multicast address
ip_addr = dut.expect(r'IPv4 address: (\d+\.\d+\.\d+\.\d+)[^\d]', timeout=30)[1].decode()
multicast_addr = dut.expect(r'Configured IPV4 Multicast address (\d+\.\d+\.\d+\.\d+)[^\d]')[1].decode()
# Get host interface IP to bind to
host_ip = get_host_ip4_by_dest_ip(ip_addr)
# Bind to all interfaces
sock.bind(('0.0.0.0', PORT))
# Set socket options for multicast
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
# Specify the outgoing interface for multicast
sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_IF, socket.inet_aton(host_ip))
# Join multicast group
sock.setsockopt(
socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, socket.inet_aton(multicast_addr) + socket.inet_aton(host_ip)
)
elif ip_version == 'ipv6':
net_if = find_target_if(nic)
# IPv6 socket
sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_MULTICAST_HOPS, 32)
# Get ESP32's IPv6 address and multicast address
ip_addr = dut.expect(rf'IPv6 address: {IPV6_REGEX}', timeout=30)[1].decode()
ip_addr = str(ipaddress.IPv6Address(ip_addr))
multicast_addr = dut.expect(rf'Configured IPV6 Multicast address {IPV6_REGEX}')[1].decode()
# Bind to all interfaces
sock.bind(('::', PORT))
# Set socket options for multicast
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
# Specify the outgoing interface for multicast
sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_MULTICAST_IF, socket.if_nametoindex(net_if))
# Get host interface IPv6
host_ip = get_link_local_for_interface(net_if)
# Join multicast group
mreq = struct.pack('16si', socket.inet_pton(socket.AF_INET6, multicast_addr), socket.if_nametoindex(net_if))
sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_JOIN_GROUP, mreq)
else:
raise RuntimeError(f'Invalid IP version: {ip_version}')
# Set socket timeout
sock.settimeout(5.0)
if ip_version == 'ipv4_mapped':
dut.expect(r'Sending to IPV6 \(V4 mapped\) multicast address', timeout=5)
else:
dut.expect(f'Sending to {ip_version.upper()} multicast address', timeout=5)
try:
data, recv_addr = sock.recvfrom(1024)
logging.info(f'Received {len(data)} bytes from {recv_addr}')
except socket.timeout:
raise RuntimeError(f'Timeout waiting for {ip_version} multicast message from ESP32')
# Check if received from expected source
if recv_addr[0] != ip_addr or recv_addr[1] != PORT:
raise RuntimeError(f'Received {ip_version} multicast message from unexpected source')
# Send multicast message
message = '!!! Multicast test message from host !!!'.encode()
logging.info(f'Sending {ip_version} multicast message to {multicast_addr}:{PORT}')
sock.sendto(message, (multicast_addr, PORT))
if ip_version == 'ipv4_mapped':
dut.expect(f'received {len(message)} bytes from ::FFFF:{host_ip}', timeout=1)
else:
dut.expect(f'received {len(message)} bytes from {host_ip.upper()}', timeout=1)
sock.close()
@pytest.mark.eth_ip101
@idf_parametrize(
'target',
['esp32', 'esp32p4'],
indirect=['target'],
)
def test_examples_udp_multicast(dut: Dut) -> None:
test_examples_udp_multicast_proto(dut, 'ipv4_mapped')
dut.serial.hard_reset()
test_examples_udp_multicast_proto(dut, 'ipv6')

View File

@@ -0,0 +1,14 @@
CONFIG_IDF_TARGET="esp32"
CONFIG_EXAMPLE_IPV4_V6=y
CONFIG_EXAMPLE_LOOPBACK=n
CONFIG_EXAMPLE_CONNECT_IPV6=y
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

View File

@@ -0,0 +1,14 @@
CONFIG_IDF_TARGET="esp32p4"
CONFIG_EXAMPLE_IPV4_V6=y
CONFIG_EXAMPLE_LOOPBACK=n
CONFIG_EXAMPLE_CONNECT_IPV6=y
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=31
CONFIG_EXAMPLE_ETH_MDIO_GPIO=52
CONFIG_EXAMPLE_ETH_PHY_RST_GPIO=51
CONFIG_EXAMPLE_ETH_PHY_ADDR=1