Files
aes67-ESP32-P4/components/aes67_net/lldp.c
T
bsncubed 30c32e702b Step 2a (2/2): LLDP transmit, switch shows name and IP
- aes67_net/lldp.c: IEEE 802.1AB LLDPDU sent as a raw frame with
  esp_eth_transmit every 30 s (TTL 120 s) and immediately on a new IP
  or hostname change. TLVs: chassis/port ID (MAC), port description,
  system name (net.hostname), system description (project + version),
  capabilities station-only, IPv4 management address.
- Docs: LLDP in the core Network section; step 2a ticked.
- Verified on an HP 2530-8G-PoE+ (show lldp info remote-device 2): all
  fields shown, management address 192.168.192.244.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 23:14:09 +10:00

126 lines
3.7 KiB
C

// LLDP (IEEE 802.1AB) transmit only: tells the switch our name and management IP.
#include "lldp.h"
#include <string.h>
#include "aes67_cfg.h"
#include "esp_app_desc.h"
#include "esp_eth.h"
#include "esp_log.h"
#include "esp_timer.h"
#define LLDP_INTERVAL_S 30
#define LLDP_TTL_S (4 * LLDP_INTERVAL_S)
#define FRAME_MAX 512
#define FRAME_MIN 60 // without FCS
enum { TLV_END, TLV_CHASSIS_ID, TLV_PORT_ID, TLV_TTL, TLV_PORT_DESC, TLV_SYS_NAME, TLV_SYS_DESC,
TLV_SYS_CAP, TLV_MGMT_ADDR };
static const char *TAG = "lldp";
static const uint8_t LLDP_MCAST[6] = { 0x01, 0x80, 0xc2, 0x00, 0x00, 0x0e };
static esp_eth_handle_t s_eth;
static esp_netif_t *s_netif;
static esp_timer_handle_t s_timer;
static bool s_logged;
static size_t tlv(uint8_t *p, int type, const void *val, size_t len)
{
p[0] = (type << 1) | ((len >> 8) & 1);
p[1] = len & 0xff;
memcpy(p + 2, val, len);
return 2 + len;
}
static size_t tlv_str(uint8_t *p, int type, const char *s)
{
size_t len = strlen(s);
return tlv(p, type, s, len > 255 ? 255 : len);
}
void lldp_send(void)
{
if (!s_eth) {
return;
}
uint8_t mac[6];
if (esp_eth_ioctl(s_eth, ETH_CMD_G_MAC_ADDR, mac) != ESP_OK) {
return;
}
cJSON *net = cfg_get("net");
const char *host = cJSON_GetObjectItemCaseSensitive(net, "hostname")->valuestring;
const esp_app_desc_t *app = esp_app_get_description();
uint8_t f[FRAME_MAX] = { 0 };
size_t n = 0;
memcpy(f, LLDP_MCAST, 6);
memcpy(f + 6, mac, 6);
f[12] = 0x88;
f[13] = 0xcc;
n = 14;
uint8_t id[7] = { 4 }; // chassis ID subtype 4: MAC address
memcpy(id + 1, mac, 6);
n += tlv(f + n, TLV_CHASSIS_ID, id, sizeof(id));
id[0] = 3; // port ID subtype 3: MAC address
n += tlv(f + n, TLV_PORT_ID, id, sizeof(id));
uint8_t ttl[2] = { LLDP_TTL_S >> 8, LLDP_TTL_S & 0xff };
n += tlv(f + n, TLV_TTL, ttl, sizeof(ttl));
n += tlv_str(f + n, TLV_PORT_DESC, "eth0");
n += tlv_str(f + n, TLV_SYS_NAME, host);
char desc[128];
snprintf(desc, sizeof(desc), "AES67 device, %s %s (ESP-IDF %s)", app->project_name, app->version, app->idf_ver);
n += tlv_str(f + n, TLV_SYS_DESC, desc);
uint8_t cap[4] = { 0x00, 0x80, 0x00, 0x80 }; // capabilities: station only (supported, enabled)
n += tlv(f + n, TLV_SYS_CAP, cap, sizeof(cap));
esp_netif_ip_info_t ip = { 0 };
bool have_ip = esp_netif_get_ip_info(s_netif, &ip) == ESP_OK && ip.ip.addr;
if (have_ip) {
// addr len 5, subtype IPv4, address, interface subtype ifIndex, ifIndex 1, OID len 0
uint8_t m[12] = { 5, 1 };
memcpy(m + 2, &ip.ip.addr, 4); // already network byte order
m[6] = 2;
m[10] = 1;
n += tlv(f + n, TLV_MGMT_ADDR, m, sizeof(m));
}
n += tlv(f + n, TLV_END, NULL, 0);
if (n < FRAME_MIN) {
n = FRAME_MIN;
}
esp_err_t err = esp_eth_transmit(s_eth, f, n);
if (err != ESP_OK) {
ESP_LOGW(TAG, "transmit failed: %s", esp_err_to_name(err));
} else if (!s_logged || !have_ip) {
// Log once per name/IP change, not every 30 s.
ESP_LOGI(TAG, "sent: name %s, mgmt " IPSTR ", %u bytes", host, IP2STR(&ip.ip), (unsigned)n);
s_logged = have_ip;
}
cJSON_Delete(net);
}
void lldp_changed(void)
{
s_logged = false;
lldp_send();
}
static void timer_cb(void *arg)
{
lldp_send();
}
esp_err_t lldp_start(esp_eth_handle_t eth, esp_netif_t *netif)
{
s_eth = eth;
s_netif = netif;
const esp_timer_create_args_t args = { .callback = timer_cb, .name = "lldp" };
esp_err_t err = esp_timer_create(&args, &s_timer);
if (err == ESP_OK) {
err = esp_timer_start_periodic(s_timer, LLDP_INTERVAL_S * 1000000ULL);
}
return err;
}