// LLDP (IEEE 802.1AB) transmit only: tells the switch our name and management IP. #include "lldp.h" #include #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; }