From 67b8c718fbe36bff0bd24e6b6120866523d89dd4 Mon Sep 17 00:00:00 2001 From: DrDoof <8052722+DrDoof@users.noreply.github.com> Date: Wed, 23 Sep 2026 22:29:25 +0200 Subject: [PATCH 01/18] dns: resolve host names A small resolver for IPv4 address records, so that clients such as NTP can be given a host name: "dns server []", "dns lookup " and "dns". Without a configured server it uses the one DHCP handed out. A query goes to the servers in turn every two seconds and gives up after six tries; answers with compressed names and CNAME records ahead of the address are understood. A name that already is a dotted address is taken without a query. The resolver sits in BANK3, the command line in BANK2 next to the helpers it uses. The System page shows and sets the servers. --- Makefile | 2 + cmd_parser.c | 3 + dns.c | 226 ++++++++++++++++++++++++++++++++++++++++++++++++ dns.h | 36 ++++++++ dns_cli.c | 63 ++++++++++++++ doc/dns.md | 19 ++++ html/app.js | 19 +++- html/index.html | 3 + rtlplayground.c | 2 + udp_apps.c | 1 + udp_apps.h | 1 + 11 files changed, 372 insertions(+), 3 deletions(-) create mode 100644 dns.c create mode 100644 dns.h create mode 100644 dns_cli.c create mode 100644 doc/dns.md diff --git a/Makefile b/Makefile index aa1839c4..d7fbf131 100644 --- a/Makefile +++ b/Makefile @@ -69,6 +69,8 @@ SRCS = \ rtlplayground.c \ boot.c \ sfp.c \ + dns.c \ + dns_cli.c \ syslog.c \ udp_apps.c diff --git a/cmd_parser.c b/cmd_parser.c index cb6b6c00..93a5af7c 100644 --- a/cmd_parser.c +++ b/cmd_parser.c @@ -19,6 +19,7 @@ #include "sfp.h" #include "dhcp.h" #include "syslog.h" +#include "dns.h" #include "uip/uip.h" #include "version.h" @@ -1802,6 +1803,8 @@ void cmd_parser(void) __banked parse_mtu(); } else if (cmd_compare(0, "syslog")) { parse_syslog(); + } else if (cmd_compare(0, "dns")) { + dns_parse(); } else if (cmd_compare(0, "ip")) { if (cmd_compare(1, "dhcp")) { dhcp_start(); diff --git a/dns.c b/dns.c new file mode 100644 index 00000000..2dc7e575 --- /dev/null +++ b/dns.c @@ -0,0 +1,226 @@ +/* + * DNS resolver for the RTL837x platform + * This code is in the Public Domain + */ + +#include +#include "rtl837x_common.h" +#include "cmd_parser.h" +#include "uip/uip.h" +#include "dhcp.h" +#include "dns.h" + +#pragma codeseg BANK3 +#pragma constseg BANK3 + +#define DNS_PORT 53 +#define DNS_TRIES 6 +#define DNS_TIMEOUT (2 * SYS_TICK_HZ) + +extern __xdata struct dhcp_state dhcp_state; +extern volatile __xdata uint32_t ticks; + +__xdata struct dns_state dns_state; +__xdata uip_ipaddr_t dns_ip; +uint8_t * __xdata dns_cand[3]; + + +static uint8_t dns_used(__xdata uint8_t *a) __reentrant +{ + return a[0] | a[1] | a[2] | a[3]; +} + + +static uint8_t dns_name_is_ip(void) __reentrant +{ + __xdata char *s = dns_state.name; + uint8_t i, n; + uint16_t v; + + for (i = 0; i < 4; i++) { + v = 0; + n = 0; + while (*s >= '0' && *s <= '9') { + v = v * 10 + (*s++ - '0'); + if (++n > 3 || v > 255) + return 0; + } + if (!n) + return 0; + dns_state.addr[i] = v; + if (i < 3 && *s++ != '.') + return 0; + } + return !*s; +} + + +static void dns_next(void) __reentrant +{ + uint8_t k = 0; + + if (dns_state.conn) { + uip_udp_remove(dns_state.conn); + dns_state.conn = 0; + } + if (dns_used(dns_state.server[0])) + dns_cand[k++] = dns_state.server[0]; + if (dns_used(dns_state.server[1])) + dns_cand[k++] = dns_state.server[1]; + if (dns_used(dhcp_state.dns)) + dns_cand[k++] = dhcp_state.dns; + if (!k || dns_state.tries >= DNS_TRIES) { + dns_state.status = DNS_FAILED; + if (dns_state.verbose) + print_string(k ? "DNS: no answer\n" : "DNS: no server\n"); + return; + } + k = dns_state.tries % k; + uip_ipaddr(dns_ip, dns_cand[k][0], dns_cand[k][1], dns_cand[k][2], dns_cand[k][3]); + dns_state.conn = uip_udp_new(&dns_ip, HTONS(DNS_PORT)); + if (!dns_state.conn) { + dns_state.status = DNS_FAILED; + print_string("DNS: no free UDP connection\n"); + return; + } + dns_state.send = 1; +} + + +static void dns_query(void) __reentrant +{ + __xdata uint8_t *p = (__xdata uint8_t *)uip_appdata; + __xdata char *n = dns_state.name; + __xdata uint8_t *label; + uint16_t len = 12; + + dns_state.id++; + p[0] = dns_state.id >> 8; + p[1] = dns_state.id; + p[2] = 0x01; + p[3] = 0x00; + p[4] = 0; + p[5] = 1; + p[6] = p[7] = p[8] = p[9] = p[10] = p[11] = 0; + while (*n) { + label = p + len++; + *label = 0; + while (*n && *n != '.') { + p[len++] = *n++; + (*label)++; + } + if (*n) + n++; + } + p[len++] = 0; + p[len++] = 0; + p[len++] = 1; + p[len++] = 0; + p[len++] = 1; + uip_udp_send(len); +} + + +static uint16_t dns_skip_name(__xdata uint8_t *p, uint16_t i, uint16_t len) __reentrant +{ + while (i < len) { + if (!p[i]) + return i + 1; + if ((p[i] & 0xc0) == 0xc0) + return i + 2; + i += p[i] + 1; + } + return len; +} + + +static void dns_answer(void) __reentrant +{ + __xdata uint8_t *p = (__xdata uint8_t *)uip_appdata; + uint16_t len = uip_datalen(); + uint16_t i, an; + + if (dns_state.status != DNS_PENDING || len < 12 || !(p[2] & 0x80) + || p[0] != (uint8_t)(dns_state.id >> 8) || p[1] != (uint8_t)dns_state.id) + return; + an = ((uint16_t)p[6] << 8) | p[7]; + i = dns_skip_name(p, 12, len) + 4; + while (!(p[3] & 0x0f) && an-- && i + 10 <= len) { + i = dns_skip_name(p, i, len); + if (i + 10 > len) + break; + if (!p[i] && p[i + 1] == 1 && !p[i + 8] && p[i + 9] == 4 && i + 14 <= len) { + memcpy(dns_state.addr, p + i + 10, 4); + dns_state.status = DNS_DONE; + uip_udp_remove(dns_state.conn); + dns_state.conn = 0; + if (dns_state.verbose) { + print_string("DNS: "); + print_string_x(dns_state.name); + print_string(" is "); + print_ip(dns_state.addr); + write_char('\n'); + } + return; + } + i += 10 + (((uint16_t)p[i + 8] << 8) | p[i + 9]); + } + dns_state.tries = DNS_TRIES; + dns_next(); +} + + +void dns_init(void) __banked +{ + memset(dns_state.server, 0, sizeof(dns_state.server)); + dns_state.name[0] = 0; + dns_state.status = DNS_IDLE; + dns_state.conn = 0; +} + + +void dns_lookup(void) __banked +{ + if (dns_name_is_ip()) { + dns_state.status = DNS_DONE; + return; + } + dns_state.tries = 0; + dns_state.status = DNS_PENDING; + dns_next(); +} + + +void dns_show(void) __banked +{ + print_string("DNS servers "); + print_ip(dns_state.server[0]); + write_char(' '); + print_ip(dns_state.server[1]); + print_string(", from DHCP "); + print_ip(dhcp_state.dns); + write_char('\n'); +} + + +void dns_callback(uint16_t lport) __banked __reentrant +{ + if (!dns_state.conn || lport != dns_state.conn->lport) + return; + if (uip_newdata()) { + dns_answer(); + return; + } + if (dns_state.status != DNS_PENDING) + return; + if (dns_state.send) { + dns_state.send = 0; + dns_state.sent = ticks; + dns_query(); + return; + } + if (ticks - dns_state.sent >= DNS_TIMEOUT) { + dns_state.tries++; + dns_next(); + } +} diff --git a/dns.h b/dns.h new file mode 100644 index 00000000..3407c09d --- /dev/null +++ b/dns.h @@ -0,0 +1,36 @@ +#ifndef _DNS_H_ +#define _DNS_H_ + +#include + +#define DNS_NAME_LEN 48 + +#define DNS_IDLE 0 +#define DNS_PENDING 1 +#define DNS_DONE 2 +#define DNS_FAILED 3 + +struct uip_udp_conn; + +struct dns_state { + uint8_t server[2][4]; /* configured servers, 0.0.0.0 = unused */ + char name[DNS_NAME_LEN]; /* name of the current or last lookup */ + uint8_t addr[4]; /* address found by the last lookup */ + uint8_t status; /* DNS_* */ + uint8_t verbose; /* print the result of the lookup */ + uint8_t tries; /* queries sent for the current lookup */ + uint8_t send; /* a query is due on the connection */ + uint16_t id; + uint32_t sent; /* ticks when the last query went out */ + struct uip_udp_conn *conn; +}; + +extern __xdata struct dns_state dns_state; + +void dns_init(void) __banked; +void dns_lookup(void) __banked; +void dns_show(void) __banked; +void dns_callback(uint16_t lport) __banked __reentrant; +void dns_parse(void) __banked __reentrant; + +#endif diff --git a/dns_cli.c b/dns_cli.c new file mode 100644 index 00000000..f3f4bb38 --- /dev/null +++ b/dns_cli.c @@ -0,0 +1,63 @@ +/* + * Command line interface of the DNS resolver for the RTL837x platform + * This code is in the Public Domain + */ + +#include +#include "rtl837x_common.h" +#include "cmd_parser.h" +#include "dns.h" + +#pragma codeseg BANK2 +#pragma constseg BANK2 + +extern __xdata uint8_t cmd_buffer[CMD_BUF_SIZE]; +extern __xdata uint8_t cmd_words_len; +extern __xdata uint8_t cmd_words_b[15]; +extern __xdata uint8_t ip[4]; +uint8_t cmd_compare(uint8_t start, __code const uint8_t * cmd); +uint8_t parse_ip(uint8_t idx); + + +void dns_parse(void) __banked __reentrant +{ + __xdata uint8_t *s; + uint8_t n; + + if (cmd_words_len < 2) { + dns_show(); + return; + } + if (cmd_compare(1, "server") && cmd_words_len >= 3 && parse_ip(cmd_words_b[2])) { + memcpy(dns_state.server[0], ip, 4); + memset(dns_state.server[1], 0, 4); + if (cmd_words_len >= 4) { + if (!parse_ip(cmd_words_b[3])) + goto err; + memcpy(dns_state.server[1], ip, 4); + } + return; + } + if (cmd_compare(1, "lookup") && cmd_words_len >= 3) { + if (dns_state.status == DNS_PENDING) { + print_string("DNS: a lookup is already running\n"); + return; + } + s = &cmd_buffer[cmd_words_b[2]]; + for (n = 0; s[n] && s[n] != ' '; n++) { + if (n == DNS_NAME_LEN - 1) + goto err; + dns_state.name[n] = s[n]; + } + dns_state.name[n] = 0; + dns_state.verbose = 1; + dns_lookup(); + if (dns_state.status == DNS_DONE) { + print_ip(dns_state.addr); + write_char('\n'); + } + return; + } +err: + print_string("Error: dns [server []|lookup ]\n"); +} diff --git a/doc/dns.md b/doc/dns.md new file mode 100644 index 00000000..3763a404 --- /dev/null +++ b/doc/dns.md @@ -0,0 +1,19 @@ +# DNS + +The switch can resolve host names to IPv4 addresses, for the NTP client and +for anything else that is given a name instead of an address. + +``` +dns server [] # one or two servers, 0.0.0.0 removes them +dns lookup # resolve a name and print the address +dns # show the servers +``` + +Without a configured server the resolver uses the one handed out by DHCP. +Only address records are looked up; a query goes to the servers in turn, +every two seconds, and gives up after six tries. A name that already is a +dotted address is taken as it is, without a query. + +The DNS servers are set in the Network card of the System page as well. +Settings made there or on the console are kept only once the configuration is +saved. diff --git a/html/app.js b/html/app.js index 021ebb22..da8374c1 100644 --- a/html/app.js +++ b/html/app.js @@ -83,7 +83,7 @@ sy_ip_err:"Invalid IP / netmask / gateway",sy_host_err:"Hostname: 1-23 printable sy_net_q:"Apply network settings?",sy_net_d:"The management IP changes to {ip}: this page will need to be reopened there.", sy_ip_changed:"IP changed, reconnect at http://{ip}/",sy_dhcp_q:"Switch to DHCP?", sy_dhcp_d:"The switch requests an address via DHCP. You must find its new IP to reconnect.", -sy_sysip_err:"Invalid syslog server IP",sy_sysport_err:"Syslog port must be 1-65535", +sy_sysip_err:"Invalid syslog server IP",sy_dns1:"DNS server",sy_dns2:"Second DNS",sy_dns_err:"Invalid DNS server IP",sy_dns_dhcp:"from DHCP: {ip}",sy_sysport_err:"Syslog port must be 1-65535", sy_pw_len:"Password: 1-20 characters",sy_pw_space:"Password cannot contain spaces",sy_pw_match:"Passwords do not match", sy_pw_q:"Change admin password?",sy_pw_d:"Takes effect immediately for new logins. Save to flash to persist.", sy_reboot_q:"Reboot the switch?",sy_reboot_d:"There are UNSAVED changes and they will be lost. Save to flash first if you want to keep them.", @@ -658,6 +658,7 @@ var CONF_CMDS=[ /^gw\s+(\d{1,3}\.){3}\d{1,3}$/,/^netmask\s+(\d{1,3}\.){3}\d{1,3}$/, /^syslog\s+(on|off)$/,/^syslog\s+ip\s+(\d{1,3}\.){3}\d{1,3}$/,/^syslog\s+port\s+\d{1,5}$/, /^session\s+\d{1,5}$/, + /^dns\s+server\s+(\d{1,3}\.){3}\d{1,3}(\s+(\d{1,3}\.){3}\d{1,3})?$/, /^passwd\s+\S+$/,/^hostname\s+\S{1,23}$/, /^vlan\s+\d{1,4}\s+d$/,/^vlan\s+\d{1,4}\s+mgmt$/, /^vlan\s+\d{1,4}(\s+[a-zA-Z]\w*)?(\s+\d{1,2}t?)+$/, @@ -1744,6 +1745,16 @@ function sysLoad(){ if(S.info.session_timeout)$("sy-sesstmo").value=S.info.session_timeout; }).catch(function(){}); cfgReload(); + dnsLoad(); +} +function dnsLoad(){ + return api("/cmd",{method:"POST",body:"dns"}).then(function(r){ + var m=r.body.match(/DNS servers ([\d.]+) ([\d.]+), from DHCP ([\d.]+)/); + if(!m)return; + function v(x){return x==="0.0.0.0"?"":x} + $("sy-dns1").value=v(m[1]);$("sy-dns2").value=v(m[2]); + $("sy-dnsdhcp").textContent=m[3]==="0.0.0.0"?"":t("sy_dns_dhcp",{ip:m[3]}); + }).catch(function(){}); } function cfgParseKnown(txt){ var igmp=false,syslog=false; @@ -1760,13 +1771,15 @@ function cfgParseKnown(txt){ $("sy-apply").addEventListener("click",function(){ var ip=$("sy-ip").value.trim(),mask=$("sy-mask").value.trim(),gw=$("sy-gw").value.trim(); if(!okIp(ip)||!okIp(mask)||!okIp(gw)){toast(t("sy_ip_err"),"err");return;} + var d1=$("sy-dns1").value.trim(),d2=$("sy-dns2").value.trim(); + if((d1&&!okIp(d1))||(d2&&!okIp(d2))){toast(t("sy_dns_err"),"err");return;} var cmds=[]; var hn=$("sy-host").value.trim(); if(hn&&hn!==S.info.hostname){ if(!/^[\x21-\x7e]{1,23}$/.test(hn)||/["\\]/.test(hn)){toast(t("sy_host_err"),"err");return;} cmds.push("hostname "+hn); } - cmds.push("ip "+ip+"\nnetmask "+mask+"\ngw "+gw); + cmds.push("ip "+ip+"\nnetmask "+mask+"\ngw "+gw+"\ndns server "+(d1||"0.0.0.0")+(d2?" "+d2:"")); var changingIp=ip!==S.info.ip_address; confirmModal(t("sy_net_q"),changingIp?t("sy_net_d",{ip:ip}):"",function(){ postCmds(cmds).then(function(){ @@ -1862,7 +1875,7 @@ tabHooks.system={enter:sysLoad}; var CONF_OVERWRITE=[ /^ip\b/,/^gw\b/,/^netmask\b/,/^hostname\b/, - /^syslog\s+ip\b/,/^syslog\s+port\b/,/^passwd\b/,/^session\b/, + /^syslog\s+ip\b/,/^syslog\s+port\b/,/^dns\s+server\b/,/^passwd\b/,/^session\b/, /^vlan\s+\d{1,4}\s+mgmt$/,/^vlan\s+\d{1,4}(?!\s+mgmt\b)/, /^pvid\s+\d{1,2}\b/, /^port\s+\d{1,2}(?!\s+name\b)/,/^port\s+\d{1,2}\s+name\b/, diff --git a/html/index.html b/html/index.html index 36f3650d..0afb30bc 100644 --- a/html/index.html +++ b/html/index.html @@ -360,6 +360,9 @@ button.danger{background:var(--bad);color:#fff;border-color:transparent} + + +

diff --git a/rtlplayground.c b/rtlplayground.c index dbe39ab4..604abfae 100644 --- a/rtlplayground.c +++ b/rtlplayground.c @@ -25,6 +25,7 @@ #include "machine.h" #include "phy.h" #include "syslog.h" +#include "dns.h" #include "httpd/page_impl.h" #include "boot.h" #include "sfp.h" @@ -1756,6 +1757,7 @@ void main(void) check_and_flash_update_image(); syslog_init(); + dns_init(); #ifdef DEBUG // This register seems to work on the RTL8373 only if also the SDS diff --git a/udp_apps.c b/udp_apps.c index f2c9c55b..4292a105 100644 --- a/udp_apps.c +++ b/udp_apps.c @@ -6,4 +6,5 @@ void udp_callbacks(void) { dhcp_callback(uip_udp_conn->lport); // let the application decide if this is for it or not syslog_callback(uip_udp_conn->lport); // let the application decide if this is for it or not + dns_callback(uip_udp_conn->lport); } \ No newline at end of file diff --git a/udp_apps.h b/udp_apps.h index 1f842ce6..de9d6a18 100644 --- a/udp_apps.h +++ b/udp_apps.h @@ -3,6 +3,7 @@ #include "dhcp.h" #include "syslog.h" +#include "dns.h" void udp_callbacks(void); From bb90e0c9ba618c1705153ab19c3ed6c805ff0324 Mon Sep 17 00:00:00 2001 From: Brunoz78 Date: Sun, 4 Oct 2026 10:41:47 +0200 Subject: [PATCH 02/18] web: warn when a firmware image was built for another machine The firmware page accepted any valid image, also one built for a different board whose SFP, LED and button pins differ. Every image carries its machine name as a NUL-terminated string, the same one the switch reports as hw_ver; when the selected image lacks it, the page now warns below the check result and in the confirmation dialog. The upload stays possible on purpose. Co-Authored-By: Claude Opus 5.5 --- html/app.js | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/html/app.js b/html/app.js index 1d29bf08..c8d04330 100644 --- a/html/app.js +++ b/html/app.js @@ -105,6 +105,7 @@ fw_intro:"Upload an RTLPlayground image (512 KiB .bin). The image is staged to f fw_upload:"Upload",fw_checking:"{f}: {n} bytes. Checking...",fw_size_err:"size is {n}, expected 524288 (512 KiB)", fw_magic_err:"missing bank header / LJMP magic: not a firmware image",fw_crc_err:"CRC16 check failed: corrupt image", fw_valid:"valid RTLPlayground image (size, magic and CRC16 OK)",fw_q:"Upload firmware?", +fw_other:"This image was not built for this switch ({m}): its SFP, LED and button pins may differ. Upload it only if you are sure.", fw_d:"On a good checksum the switch resets itself and applies the image during boot (the startup config is preserved). Do not power it off until it comes back.", fw_finishing:"finishing flash write... {s} s",fw_uploading:"uploading... {p}% / {s} s", fw_verified:"checksum verified, the switch is rebooting...", @@ -672,6 +673,7 @@ fw_intro:"Ein RTLPlayground-Image hochladen (.bin, 512 KiB). Das Image wird im F fw_upload:"Hochladen",fw_checking:"{f}: {n} Bytes. Wird geprüft...",fw_size_err:"Grösse ist {n}, erwartet 524288 (512 KiB)", fw_magic_err:"Bank-Header / LJMP-Kennung fehlt: kein Firmware-Image",fw_crc_err:"CRC16-Prüfung fehlgeschlagen: Image beschädigt", fw_valid:"gültiges RTLPlayground-Image (Grösse, Kennung und CRC16 in Ordnung)",fw_q:"Firmware hochladen?", +fw_other:"Dieses Image wurde nicht für diesen Switch ({m}) gebaut: SFP-, LED- und Tasten-Pins können abweichen. Nur hochladen, wenn du sicher bist.", fw_d:"Ist die Prüfsumme korrekt, startet der Switch neu und übernimmt das Image beim Hochfahren (die Startkonfiguration bleibt erhalten). Nicht vom Strom trennen, bis er wieder erreichbar ist.", fw_finishing:"Flash wird fertig beschrieben... {s} s",fw_uploading:"wird hochgeladen... {p}% / {s} s", fw_verified:"Prüfsumme bestätigt, der Switch startet neu...", @@ -2157,10 +2159,26 @@ $("saveBtn").addEventListener("click",function(){ }); }); -var fwBuf=null; +var fwBuf=null,fwOther=false; +// Every image carries its machine name as a NUL-terminated string, the same +// one the running switch reports as hw_ver: an image without it was built +// for another board (SFP, LED and button pins differ between machines). +function fwForThisSwitch(u){ + var name=S.info.hw_ver; + if(!name)return true; + var n=[]; + for(var i=0;i"; + fwOther=!fwForThisSwitch(u); + info.innerHTML='\u2713 '+esc(t("fw_valid"))+""+ + (fwOther?'
\u26a0 '+esc(t("fw_other",{m:S.info.hw_ver}))+"":""); $("fwup").disabled=false; }); }); $("fwup").addEventListener("click",function(){ if(!fwBuf)return; - confirmModal(t("fw_q"),t("fw_d"),function(){ + confirmModal(t("fw_q"),(fwOther?t("fw_other",{m:S.info.hw_ver})+" ":"")+t("fw_d"),function(){ var form=new FormData(); form.append("uploadedfile",fwBuf,fwBuf.name); var xhr=new XMLHttpRequest(); From 4f009539b0ed2926883a731bf1122731ec076b65 Mon Sep 17 00:00:00 2001 From: Brunoz78 Date: Sun, 4 Oct 2026 18:33:23 +0200 Subject: [PATCH 03/18] web: name the browser tab after the switch hostname With several switches open, every tab was called "Switch". The dashboard tab now shows the hostname, and the login page shows the hostname seen at the last login on that address (kept in localStorage, which is per switch), or the address itself before the first login. Co-Authored-By: Claude Opus 5.5 --- html/app.js | 6 +++++- html/login.html | 8 ++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/html/app.js b/html/app.js index 1d29bf08..c638f31b 100644 --- a/html/app.js +++ b/html/app.js @@ -1044,7 +1044,11 @@ function renderInfo(){ if(v==null||v==="")return; tb.appendChild(h("tr",null,[h("td",{class:"mut",text:t(r[0])}),h("td",{class:"mono",text:String(v)})])); }); - if(S.info.hostname)$("brandname").textContent=$("brandname").title=S.info.hostname; + if(S.info.hostname){ + $("brandname").textContent=$("brandname").title=document.title=S.info.hostname; + // Remembered per switch (each has its own origin) for the login page tab + try{localStorage.setItem("rtl_host",S.info.hostname);}catch(e){} + } if(S.info.sw_ver)$("fver").textContent="RTLPlayground "+S.info.sw_ver; } function pollInfo(){ diff --git a/html/login.html b/html/login.html index 9490f77a..042a5c45 100644 --- a/html/login.html +++ b/html/login.html @@ -92,8 +92,12 @@ button:hover{filter:brightness(1.1)} try{l=localStorage.getItem("rtl_lang");}catch(e){} if(!L[l])l=(navigator.language||"en").slice(0,2); var s=L[l]||L.en; - document.title=s[0]; - document.getElementById("hn").textContent=s[0]; + // Name the tab after the switch: the hostname seen at the last login here, + // otherwise the address, so several open switches can be told apart + var host=null; + try{host=localStorage.getItem("rtl_host");}catch(e){} + document.title=(host||location.hostname)+" – "+s[0]; + document.getElementById("hn").textContent=host||s[0]; document.getElementById("sub").textContent=s[1]; document.getElementById("lp").textContent=s[2]; document.getElementById("lb").textContent=s[3]; From 1ebee4d6e9f50cd6279a2d5597e063db5a390f7a Mon Sep 17 00:00:00 2001 From: Rob Emery Date: Fri, 28 Aug 2026 01:53:29 +0100 Subject: [PATCH 04/18] Implementing basic LLDP server, should send port_id message to each port once every 30 seconds. * Additionally fixes the on-ward passing of LLDP frames through the switch The ports on the case are marked 1-8 so this should reflect that rather than being 0-based We can send the hostname as well, this can be dynamically allocated Let's send the model as the system description as well --- Makefile | 1 + cmd_parser.c | 10 +++ cmd_parser.h | 1 + rtl837x_lldp.c | 181 ++++++++++++++++++++++++++++++++++++++++++++++++ rtl837x_lldp.h | 36 ++++++++++ rtlplayground.c | 12 ++++ 6 files changed, 241 insertions(+) create mode 100644 rtl837x_lldp.c create mode 100644 rtl837x_lldp.h diff --git a/Makefile b/Makefile index bb914904..15d3f10c 100644 --- a/Makefile +++ b/Makefile @@ -83,6 +83,7 @@ SRCS += \ rtl837x_pins.c\ rtl837x_port.c \ rtl837x_stp.c \ + rtl837x_lldp.c \ rtl837x_storm.c SRCS += \ httpd/httpd.c \ diff --git a/cmd_parser.c b/cmd_parser.c index c493c9e9..de7e1d49 100644 --- a/cmd_parser.c +++ b/cmd_parser.c @@ -16,6 +16,7 @@ #include "rtl837x_stp.h" #include "rtl837x_igmp.h" #include "rtl837x_bandwidth.h" +#include "rtl837x_lldp.h" #include "rtl837x_storm.h" #include "sfp.h" #include "dhcp.h" @@ -32,6 +33,7 @@ extern __code const struct machine machine; extern __xdata bool stp_enabled; extern __code const uint8_t log_to_phys_port[9]; +extern __xdata uint8_t lldp_enabled; extern volatile __xdata uint32_t ticks; extern volatile __xdata uint8_t sfr_data[4]; @@ -2024,6 +2026,14 @@ void cmd_parser(void) __banked parse_ingress(); } else if (cmd_compare(0, "session")) { parse_session(); + } else if (cmd_compare(0, "lldp")) { + if (cmd_compare(1, "on")) { + print_string("LLDP enabled\n"); + lldp_enabled = 1; + } else { + print_string("LLDP disabled\n"); + lldp_enabled = 0; + } } else { cmd_error("Unknown command\n"); diff --git a/cmd_parser.h b/cmd_parser.h index f96c72ae..2359eb16 100644 --- a/cmd_parser.h +++ b/cmd_parser.h @@ -20,6 +20,7 @@ extern __xdata uint8_t err_status; #define HEALTH_PH_STP 4 #define HEALTH_PH_CMD 5 #define HEALTH_PHASES 6 +#define HEALTH_LLDP 7 extern __xdata uint16_t health_rx_frames; #define health_rx_frame() health_rx_frames++ diff --git a/rtl837x_lldp.c b/rtl837x_lldp.c new file mode 100644 index 00000000..24e774c6 --- /dev/null +++ b/rtl837x_lldp.c @@ -0,0 +1,181 @@ +#pragma codeseg BANK2 +#pragma constseg BANK2 + +#include "rtl837x_common.h" +#include "rtl837x_lldp.h" +#include "rtl837x_sfr.h" +#include "rtl837x_common.h" +#include "dhcp.h" +#include "uip.h" +#include "uip/uip.h" +#include "machine.h" + +extern __xdata uint8_t lldp_enabled; +extern __code const struct machine machine; + +uint8_t lldp_seconds; + +void lldp_init(void) __banked +{ + lldp_seconds = 0; +} + +void lldp_tick(void) __banked +{ + lldp_seconds++; + + if (lldp_seconds < LLDP_TX_INTERVAL_SEC) + return; + + lldp_seconds = 0; + + if(lldp_enabled == 1) + lldp_send(); +} + +//Outgoing LLDP packet. + +struct lldp_pkt { + struct uip_eth_addr dst; + struct uip_eth_addr src; + struct rtl_tag rtl_tag; + uint16_t ether_type; + + uint8_t payload[64]; +}; + +#define LLDP_O ((__xdata struct lldp_pkt *)&uip_buf[RTL_FRAME_DESC_SIZE]) + +// The type is 7 bits and the length is 9 bits +// we can represent this as the 7 most significant +// bits of the type bit and leave the least significant +// bit as 0 always as we're not going to need 2^9 for +// the length at any point +#define LLDP_TYPE(value) (value << 1) + +void lldp_send(void) __banked __reentrant +{ + uint8_t *p; + uint16_t len = 0; + uint8_t port_position; + + lldp_set_addresses(); + lldp_set_rtl_wrapper(); + + p = LLDP_O->payload; + + //Chassis ID + p[len++] = LLDP_TYPE(LLDP_CHASSIS_ID_TLV_TYPE); + p[len++] = LLDP_CHASSIS_ID_TLV_LENGTH; + p[len++] = LLDP_CHASSIS_ID_TLV_SUBTYPE; + + for (uint8_t i = 0; i < LLDP_MAC_ADDR_LEN; i++) + p[len + i] = uip_ethaddr.addr[i]; + + len += LLDP_MAC_ADDR_LEN; + + //Port ID + p[len++] = LLDP_TYPE(LLDP_PORT_ID_TLV_TYPE); + p[len++] = LLDP_PORT_ID_TLV_LENGTH; + p[len++] = LLDP_PORT_ID_TLV_SUBTYPE; + port_position = len; + p[len++] = '0'; // filled in per port below + + //TTL + p[len++] = LLDP_TYPE(LLDP_TTL_TLV_TYPE); + p[len++] = LLDP_TTL_TLV_LENGTH; + p[len++] = 0x00; //padding + p[len++] = LLDP_TTL_TTL_SECONDS; + + lldp_sysname(p, &len); + lldp_sysdesc(p, &len); + + // End of LLDPDU TLV + p[len++] = 0x00; //padding + p[len++] = 0x00; //padding + + while (len < LLDP_MIN_ETHERNET_PAYLOAD_LENGTH) + p[len++] = 0x00; + + /* + * uip_len is the Ethernet frame length excluding FCS. + * + * The frame consists of: + * + * dst mac + * src mac + * rtl_tag sizeof(struct rtl_tag) + * EtherType (2 bytes) + * payload len + */ + uip_len = LLDP_MAC_ADDR_LEN + LLDP_MAC_ADDR_LEN + sizeof(struct rtl_tag) + LLDP_ETHERTYPE_LENGTH + len; + + for (uint8_t port = machine.min_port; port <= machine.max_port; port++) { + + LLDP_O->payload[port_position] = '1' + port; + LLDP_O->rtl_tag.pmask = HTONS((uint16_t)1 << port); + + tcpip_output(); + } +} + +void lldp_set_addresses(void) __banked { + // LLDP destination multicast addr: 01:80:c2:00:00:0e + LLDP_O->dst.addr[0] = 0x01; + LLDP_O->dst.addr[1] = 0x80; + LLDP_O->dst.addr[2] = 0xc2; + LLDP_O->dst.addr[3] = 0x00; + LLDP_O->dst.addr[4] = 0x00; + LLDP_O->dst.addr[5] = 0x0e; + + for (uint8_t i = 0; i < LLDP_MAC_ADDR_LEN; i++) + LLDP_O->src.addr[i] = uip_ethaddr.addr[i]; +} + +void lldp_set_rtl_wrapper(void) __banked { + /* + * This is the RTL CPU tag, not the Ethernet EtherType. + * port 0 -> 0x0001 + * port 1 -> 0x0002 + * port 2 -> 0x0004 + */ + LLDP_O->rtl_tag.tag = HTONS(RTL_FRAME_TAG_ID); + LLDP_O->rtl_tag.version = RTL_FRAME_TAG_VERSION; + LLDP_O->rtl_tag.reason = 0x00; + LLDP_O->rtl_tag.flags = HTONS(RTL_TAG_LEARN_DIS); + + LLDP_O->ether_type = HTONS(LLDP_ETHERTYPE); +} + +void lldp_sysname(uint8_t *p, uint16_t *len) __banked +{ + uint16_t hostname_length = 0; + __xdata char *hp = hostname; + + p[(*len)++] = LLDP_TYPE(LLDP_SYSNAME_TLV_TYPE); + p[(*len)++] = 0; + + while (*hp) + { + hostname_length++; + p[(*len)++] = *hp++; + } + p[(*len)-hostname_length-1] = hostname_length; + +} + +void lldp_sysdesc(uint8_t *p, uint16_t *len) __banked +{ + uint16_t machine_name_length = 0; + const char *mp = machine.machine_name; + + p[(*len)++] = LLDP_TYPE(LLDP_SYSDESC_TLV_TYPE); + p[(*len)++] = 0; + + while (*mp) + { + machine_name_length++; + p[(*len)++] = *mp++; + } + p[(*len)-machine_name_length-1] = machine_name_length; +} \ No newline at end of file diff --git a/rtl837x_lldp.h b/rtl837x_lldp.h new file mode 100644 index 00000000..038d005d --- /dev/null +++ b/rtl837x_lldp.h @@ -0,0 +1,36 @@ +#ifndef _LLDP_H_ +#define _LLDP_H_ + +#include + +#define LLDP_MIN_ETHERNET_PAYLOAD_LENGTH 46 +#define LLDP_ETHERTYPE 0x88cc +#define LLDP_ETHERTYPE_LENGTH 2 +#define LLDP_MAX_FRAME 256 +#define LLDP_MAC_ADDR_LEN 6 +#define LLDP_TX_INTERVAL_SEC 30 + +#define LLDP_PORT_ID_TLV_TYPE 2 +#define LLDP_PORT_ID_TLV_LENGTH 0x02 +#define LLDP_PORT_ID_TLV_SUBTYPE 0x07 + +#define LLDP_TTL_TLV_TYPE 3 +#define LLDP_TTL_TLV_LENGTH 2 +#define LLDP_TTL_TTL_SECONDS 120 + +#define LLDP_CHASSIS_ID_TLV_TYPE 1 +#define LLDP_CHASSIS_ID_TLV_LENGTH 7 +#define LLDP_CHASSIS_ID_TLV_SUBTYPE 4 + +#define LLDP_SYSNAME_TLV_TYPE 5 +#define LLDP_SYSDESC_TLV_TYPE 6 + +void lldp_init(void) __banked; +void lldp_tick(void) __banked; +void lldp_send(void) __banked; +void lldp_set_addresses(void) __banked; +void lldp_set_rtl_wrapper(void) __banked; +void lldp_sysname(uint8_t *p, uint16_t *len) __banked; +void lldp_sysdesc(uint8_t *p, uint16_t *len) __banked; + +#endif diff --git a/rtlplayground.c b/rtlplayground.c index 916984f1..738b6ab2 100644 --- a/rtlplayground.c +++ b/rtlplayground.c @@ -16,6 +16,7 @@ #include "rtl837x_leds.h" #include "rtl837x_bandwidth.h" #include "rtl837x_init.h" +#include "rtl837x_lldp.h" #include "dhcp.h" #include "cmd_parser.h" #include "cmd_editor.h" @@ -127,6 +128,7 @@ __xdata uint8_t tx_seq; __xdata bool stp_enabled; __xdata uint8_t igmpEnabled; +__xdata uint8_t lldp_enabled; __xdata char hostname[24]; /* device hostname, default set at boot, see rtl837x_common.h */ __code const uint16_t bit_mask[16] = { @@ -1066,6 +1068,13 @@ void handle_rx(void) if (uip_len) { tcpip_output(); } + } else if (lldp_enabled && uip_buf[0] == 0x01 && uip_buf[1] == 0x80 && uip_buf[2] == 0xc2 + && uip_buf[3] == 0x00 && uip_buf[4] == 0x00 && uip_buf[5] == 0x0e && uip_buf[12] == 0x88 + && uip_buf[13] == 0xcc) { //LLDP Mac + EtherType check + // LLDP packets shouldn't be passed elsewhere + if(uip_len){ + print_string("LLDP received\n"); + } } else if (ETH_IN->ether_type == HTONS(0x0806)) { // ARP uip_arp_arpin(); if (uip_len) { @@ -1286,6 +1295,8 @@ static void handle_tick(void) } } health_phase(HEALTH_PH_STP); + lldp_tick(); + health_phase(HEALTH_LLDP); } @@ -1794,6 +1805,7 @@ void main(void) uip_init(); uip_arp_init(); httpd_init(); + lldp_init(); management_vlan = 1; // Default management VLAN is 1 From 735672ccf1ff8e1f2de0113969fbb833db901b48 Mon Sep 17 00:00:00 2001 From: Rob Emery Date: Sun, 13 Sep 2026 12:16:30 +0100 Subject: [PATCH 05/18] Moving to Bank3 --- cmd_parser.c | 2 +- rtl837x_lldp.c | 8 ++++---- rtlplayground.c | 5 +---- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/cmd_parser.c b/cmd_parser.c index de7e1d49..8c21d9f8 100644 --- a/cmd_parser.c +++ b/cmd_parser.c @@ -33,7 +33,7 @@ extern __code const struct machine machine; extern __xdata bool stp_enabled; extern __code const uint8_t log_to_phys_port[9]; -extern __xdata uint8_t lldp_enabled; +extern __xdata bool lldp_enabled; extern volatile __xdata uint32_t ticks; extern volatile __xdata uint8_t sfr_data[4]; diff --git a/rtl837x_lldp.c b/rtl837x_lldp.c index 24e774c6..032d4746 100644 --- a/rtl837x_lldp.c +++ b/rtl837x_lldp.c @@ -1,5 +1,5 @@ -#pragma codeseg BANK2 -#pragma constseg BANK2 +#pragma codeseg BANK3 +#pragma constseg BANK3 #include "rtl837x_common.h" #include "rtl837x_lldp.h" @@ -10,7 +10,7 @@ #include "uip/uip.h" #include "machine.h" -extern __xdata uint8_t lldp_enabled; +extern __xdata bool lldp_enabled; extern __code const struct machine machine; uint8_t lldp_seconds; @@ -178,4 +178,4 @@ void lldp_sysdesc(uint8_t *p, uint16_t *len) __banked p[(*len)++] = *mp++; } p[(*len)-machine_name_length-1] = machine_name_length; -} \ No newline at end of file +} diff --git a/rtlplayground.c b/rtlplayground.c index 738b6ab2..4fb22009 100644 --- a/rtlplayground.c +++ b/rtlplayground.c @@ -128,7 +128,7 @@ __xdata uint8_t tx_seq; __xdata bool stp_enabled; __xdata uint8_t igmpEnabled; -__xdata uint8_t lldp_enabled; +__xdata bool lldp_enabled; __xdata char hostname[24]; /* device hostname, default set at boot, see rtl837x_common.h */ __code const uint16_t bit_mask[16] = { @@ -1072,9 +1072,6 @@ void handle_rx(void) && uip_buf[3] == 0x00 && uip_buf[4] == 0x00 && uip_buf[5] == 0x0e && uip_buf[12] == 0x88 && uip_buf[13] == 0xcc) { //LLDP Mac + EtherType check // LLDP packets shouldn't be passed elsewhere - if(uip_len){ - print_string("LLDP received\n"); - } } else if (ETH_IN->ether_type == HTONS(0x0806)) { // ARP uip_arp_arpin(); if (uip_len) { From 3eacc9c8a5f102cda0d1156647a03dd871d5f86d Mon Sep 17 00:00:00 2001 From: Rob Emery Date: Sun, 13 Sep 2026 20:41:48 +0100 Subject: [PATCH 06/18] LLPD_MAC_ADDR_LEN -> MAC_ADDR_LEN and memcopy instead of manually setting bytes --- rtl837x_lldp.c | 17 +++++------------ rtl837x_lldp.h | 2 +- 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/rtl837x_lldp.c b/rtl837x_lldp.c index 032d4746..163bf598 100644 --- a/rtl837x_lldp.c +++ b/rtl837x_lldp.c @@ -69,10 +69,10 @@ void lldp_send(void) __banked __reentrant p[len++] = LLDP_CHASSIS_ID_TLV_LENGTH; p[len++] = LLDP_CHASSIS_ID_TLV_SUBTYPE; - for (uint8_t i = 0; i < LLDP_MAC_ADDR_LEN; i++) + for (uint8_t i = 0; i < MAC_ADDR_LEN; i++) p[len + i] = uip_ethaddr.addr[i]; - len += LLDP_MAC_ADDR_LEN; + len += MAC_ADDR_LEN; //Port ID p[len++] = LLDP_TYPE(LLDP_PORT_ID_TLV_TYPE); @@ -108,7 +108,7 @@ void lldp_send(void) __banked __reentrant * EtherType (2 bytes) * payload len */ - uip_len = LLDP_MAC_ADDR_LEN + LLDP_MAC_ADDR_LEN + sizeof(struct rtl_tag) + LLDP_ETHERTYPE_LENGTH + len; + uip_len = MAC_ADDR_LEN + MAC_ADDR_LEN + sizeof(struct rtl_tag) + LLDP_ETHERTYPE_LENGTH + len; for (uint8_t port = machine.min_port; port <= machine.max_port; port++) { @@ -121,15 +121,8 @@ void lldp_send(void) __banked __reentrant void lldp_set_addresses(void) __banked { // LLDP destination multicast addr: 01:80:c2:00:00:0e - LLDP_O->dst.addr[0] = 0x01; - LLDP_O->dst.addr[1] = 0x80; - LLDP_O->dst.addr[2] = 0xc2; - LLDP_O->dst.addr[3] = 0x00; - LLDP_O->dst.addr[4] = 0x00; - LLDP_O->dst.addr[5] = 0x0e; - - for (uint8_t i = 0; i < LLDP_MAC_ADDR_LEN; i++) - LLDP_O->src.addr[i] = uip_ethaddr.addr[i]; + memcpyc(LLDP_O->dst.addr, "\x01\x80\xc2\x00\x00\x0e", MAC_ADDR_LEN); + memcpy(LLDP_O->src.addr, uip_ethaddr.addr, MAC_ADDR_LEN); } void lldp_set_rtl_wrapper(void) __banked { diff --git a/rtl837x_lldp.h b/rtl837x_lldp.h index 038d005d..519cfdb8 100644 --- a/rtl837x_lldp.h +++ b/rtl837x_lldp.h @@ -7,7 +7,7 @@ #define LLDP_ETHERTYPE 0x88cc #define LLDP_ETHERTYPE_LENGTH 2 #define LLDP_MAX_FRAME 256 -#define LLDP_MAC_ADDR_LEN 6 +#define MAC_ADDR_LEN 6 #define LLDP_TX_INTERVAL_SEC 30 #define LLDP_PORT_ID_TLV_TYPE 2 From d5124df89ecc25213df7e5937ebc980956a5d900 Mon Sep 17 00:00:00 2001 From: Rob Emery Date: Sun, 13 Sep 2026 20:57:03 +0100 Subject: [PATCH 07/18] avoid SRAM --- rtl837x_lldp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rtl837x_lldp.c b/rtl837x_lldp.c index 163bf598..dc1e6b55 100644 --- a/rtl837x_lldp.c +++ b/rtl837x_lldp.c @@ -13,7 +13,7 @@ extern __xdata bool lldp_enabled; extern __code const struct machine machine; -uint8_t lldp_seconds; +__xdata uint8_t lldp_seconds; void lldp_init(void) __banked { From ccf541a06bc59f8c22e1b33caa11ea0a4e586634 Mon Sep 17 00:00:00 2001 From: Rob Emery Date: Sun, 13 Sep 2026 21:12:32 +0100 Subject: [PATCH 08/18] Refactor and golfing from https://github.com/logicog/RTLPlayground/pull/398#issuecomment-5654087271 I've tweaked it a little to keep the port_position in as I think that is clearer than a random "12" in the loop later on --- rtl837x_lldp.c | 90 +++++++++++++++++++++----------------------------- rtl837x_lldp.h | 4 +-- 2 files changed, 39 insertions(+), 55 deletions(-) diff --git a/rtl837x_lldp.c b/rtl837x_lldp.c index dc1e6b55..e426fdef 100644 --- a/rtl837x_lldp.c +++ b/rtl837x_lldp.c @@ -55,9 +55,8 @@ struct lldp_pkt { void lldp_send(void) __banked __reentrant { - uint8_t *p; - uint16_t len = 0; - uint8_t port_position; + __xdata uint8_t *p; + uint8_t port_position; lldp_set_addresses(); lldp_set_rtl_wrapper(); @@ -65,37 +64,35 @@ void lldp_send(void) __banked __reentrant p = LLDP_O->payload; //Chassis ID - p[len++] = LLDP_TYPE(LLDP_CHASSIS_ID_TLV_TYPE); - p[len++] = LLDP_CHASSIS_ID_TLV_LENGTH; - p[len++] = LLDP_CHASSIS_ID_TLV_SUBTYPE; + *p++ = LLDP_TYPE(LLDP_CHASSIS_ID_TLV_TYPE); + *p++ = LLDP_CHASSIS_ID_TLV_LENGTH; + *p++ = LLDP_CHASSIS_ID_TLV_SUBTYPE; - for (uint8_t i = 0; i < MAC_ADDR_LEN; i++) - p[len + i] = uip_ethaddr.addr[i]; - - len += MAC_ADDR_LEN; + memcpy(p, uip_ethaddr.addr, MAC_ADDR_LEN); + p += MAC_ADDR_LEN; //Port ID - p[len++] = LLDP_TYPE(LLDP_PORT_ID_TLV_TYPE); - p[len++] = LLDP_PORT_ID_TLV_LENGTH; - p[len++] = LLDP_PORT_ID_TLV_SUBTYPE; - port_position = len; - p[len++] = '0'; // filled in per port below + *p++ = LLDP_TYPE(LLDP_PORT_ID_TLV_TYPE); + *p++ = LLDP_PORT_ID_TLV_LENGTH; + *p++ = LLDP_PORT_ID_TLV_SUBTYPE; + port_position = (p-LLDP_O->payload); + *p++ = '0'; // filled in per port below //TTL - p[len++] = LLDP_TYPE(LLDP_TTL_TLV_TYPE); - p[len++] = LLDP_TTL_TLV_LENGTH; - p[len++] = 0x00; //padding - p[len++] = LLDP_TTL_TTL_SECONDS; + *p++ = LLDP_TYPE(LLDP_TTL_TLV_TYPE); + *p++ = LLDP_TTL_TLV_LENGTH; + *p++ = 0x00; //padding + *p++ = LLDP_TTL_TTL_SECONDS; - lldp_sysname(p, &len); - lldp_sysdesc(p, &len); + p += lldp_sysname(p); + p += lldp_sysdesc(p); // End of LLDPDU TLV - p[len++] = 0x00; //padding - p[len++] = 0x00; //padding + *p++ = 0x00; //padding + *p++ = 0x00; //padding - while (len < LLDP_MIN_ETHERNET_PAYLOAD_LENGTH) - p[len++] = 0x00; + while (p < (LLDP_O->payload + LLDP_MIN_ETHERNET_PAYLOAD_LENGTH)) + *p++ = 0x00; /* * uip_len is the Ethernet frame length excluding FCS. @@ -106,13 +103,13 @@ void lldp_send(void) __banked __reentrant * src mac * rtl_tag sizeof(struct rtl_tag) * EtherType (2 bytes) - * payload len + * payload (how far we've moved p pointer) */ - uip_len = MAC_ADDR_LEN + MAC_ADDR_LEN + sizeof(struct rtl_tag) + LLDP_ETHERTYPE_LENGTH + len; + uip_len = MAC_ADDR_LEN + MAC_ADDR_LEN + sizeof(struct rtl_tag) + LLDP_ETHERTYPE_LENGTH + (p - LLDP_O->payload); for (uint8_t port = machine.min_port; port <= machine.max_port; port++) { - LLDP_O->payload[port_position] = '1' + port; + LLDP_O->payload[port_position] = '0' + machine.log_to_phys_port[port]; LLDP_O->rtl_tag.pmask = HTONS((uint16_t)1 << port); tcpip_output(); @@ -140,35 +137,22 @@ void lldp_set_rtl_wrapper(void) __banked { LLDP_O->ether_type = HTONS(LLDP_ETHERTYPE); } -void lldp_sysname(uint8_t *p, uint16_t *len) __banked +uint8_t lldp_sysname(__xdata uint8_t *p) __banked { - uint16_t hostname_length = 0; - __xdata char *hp = hostname; - - p[(*len)++] = LLDP_TYPE(LLDP_SYSNAME_TLV_TYPE); - p[(*len)++] = 0; - - while (*hp) - { - hostname_length++; - p[(*len)++] = *hp++; - } - p[(*len)-hostname_length-1] = hostname_length; + uint8_t hostname_length = strlen_x(hostname); + *p++ = LLDP_TYPE(LLDP_SYSNAME_TLV_TYPE); + *p++ = hostname_length; + memcpy(p, hostname, hostname_length); + return hostname_length + 2; } -void lldp_sysdesc(uint8_t *p, uint16_t *len) __banked +uint8_t lldp_sysdesc(__xdata uint8_t *p) __banked { - uint16_t machine_name_length = 0; - const char *mp = machine.machine_name; + uint16_t machine_name_length = strlen(machine.machine_name); - p[(*len)++] = LLDP_TYPE(LLDP_SYSDESC_TLV_TYPE); - p[(*len)++] = 0; - - while (*mp) - { - machine_name_length++; - p[(*len)++] = *mp++; - } - p[(*len)-machine_name_length-1] = machine_name_length; + *p++ = LLDP_TYPE(LLDP_SYSDESC_TLV_TYPE); + *p++ = machine_name_length; + memcpyc(p, machine.machine_name, machine_name_length); + return machine_name_length + 2; } diff --git a/rtl837x_lldp.h b/rtl837x_lldp.h index 519cfdb8..b441d8c4 100644 --- a/rtl837x_lldp.h +++ b/rtl837x_lldp.h @@ -30,7 +30,7 @@ void lldp_tick(void) __banked; void lldp_send(void) __banked; void lldp_set_addresses(void) __banked; void lldp_set_rtl_wrapper(void) __banked; -void lldp_sysname(uint8_t *p, uint16_t *len) __banked; -void lldp_sysdesc(uint8_t *p, uint16_t *len) __banked; +uint8_t lldp_sysname(__xdata uint8_t *p) __banked; +uint8_t lldp_sysdesc(__xdata uint8_t *p) __banked; #endif From cedc795b827b426b6c1e0296681342cbc89af977 Mon Sep 17 00:00:00 2001 From: Rob Emery Date: Sat, 12 Sep 2026 19:31:57 +0100 Subject: [PATCH 09/18] Adding UI options to enable/disable LLDP --- html/app.js | 11 +++++++++-- html/index.html | 2 ++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/html/app.js b/html/app.js index 494a8463..9bbafcc9 100644 --- a/html/app.js +++ b/html/app.js @@ -77,7 +77,7 @@ sc_title:"Storm control",sc_h:"per port and traffic type, empty = off",sc_bcast: sc_ucast:"Unknown unicast",sc_umcast:"Unknown multicast",sc_err:"Storm limit must be 1-1048575 pps or 1-10000000 kbit/s", sc_note:"Frames of a type above its limit are dropped where they enter the switch.", sy_network:"Network",sy_dhcp:"Use DHCP",sy_dhcp_t:"Request address via DHCP",sy_services:"Services", -sy_igmp:"IGMP snooping",sy_sysip:"server IP",sy_server:"Server",sy_port:"Port", +sy_igmp:"IGMP snooping",sy_sysip:"server IP",sy_server:"Server",sy_port:"Port",sy_lldp:"LLDP", sy_sesstmo:"Session timeout (s)",sy_sesstmo_err:"Session timeout must be 1-65535 s", sy_services_note:"Service state reflects the startup config; runtime state is not readable.", sy_password:"Admin password",sy_newpw:"New password",sy_repeat:"Repeat",sy_pwapply:"Change password", @@ -1995,15 +1995,18 @@ function sysLoad(){ cfgReload(); } function cfgParseKnown(txt){ - var igmp=false,syslog=false; + var igmp=false,lldp=false,syslog=false; txt.split(/\r?\n/).forEach(function(l){ l=l.trim(); if(/^igmp on$/.test(l))igmp=true; if(/^igmp off$/.test(l))igmp=false; + if(/^lldp on$/.test(l))lldp=true; + if(/^lldp off$/.test(l))lldp=false; if(/^syslog on$/.test(l))syslog=true; if(/^syslog off$/.test(l))syslog=false; }); $("sy-igmp").checked=igmp; + $("sy-lldp").checked=lldp; $("sy-syslog").checked=syslog; } $("sy-apply").addEventListener("click",function(){ @@ -2031,6 +2034,10 @@ $("sy-igmp").addEventListener("change",function(){ var el=this; postCmd("igmp "+(el.checked?"on":"off")).catch(function(){el.checked=!el.checked}); }); +$("sy-lldp").addEventListener("change",function(){ + var el=this; + postCmd("lldp "+(el.checked?"on":"off")).catch(function(){el.checked=!el.checked}); +}); $("sy-syslog").addEventListener("change",function(){ var cmds=[],el=this; if(el.checked){ diff --git a/html/index.html b/html/index.html index 54ab11d2..48e430ac 100644 --- a/html/index.html +++ b/html/index.html @@ -386,6 +386,8 @@ button.danger{background:var(--bad);color:#fff;border-color:transparent}

Services

+
From 4f1a2f04d8a844051b6a5944488e247146947b9f Mon Sep 17 00:00:00 2001 From: Rob Emery Date: Tue, 15 Sep 2026 15:00:23 +0100 Subject: [PATCH 10/18] Allowing commands like `port 1 lldp block` and `port 2 lldp permit` so we are able to enable/disable lldp transmission on a specific port. --- cmd_parser.c | 9 +++++++++ rtl837x_common.h | 1 + rtl837x_lldp.c | 9 ++++++--- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/cmd_parser.c b/cmd_parser.c index 8c21d9f8..9c52e1f9 100644 --- a/cmd_parser.c +++ b/cmd_parser.c @@ -881,6 +881,7 @@ void parse_port(void) cmd_error("\nUsage:" \ "\nport [show|on|off]" \ "\nport [10m|100m|1g|2g5|duplex] [half|full]" \ + "\nport lldp [block|permit]" \ "\nport name [custom port name]\n"); return; } @@ -966,6 +967,14 @@ void parse_port(void) else phy_settings.duplex = PHY_DUPLEX_HALF; phy_set_duplex(); + } else if (cmd_compare(2, "lldp")) { + if (cmd_compare(3, "block")){ + lldp_port_status &= ~(1 << phy_settings.port); + } else if (cmd_compare(3, "permit")) { + lldp_port_status |= 1 << phy_settings.port; + } else { + print_string ("Unknown port lldp [block|permit] command\n"); + } } else { cmd_error("Unknown port command\n"); } diff --git a/rtl837x_common.h b/rtl837x_common.h index 9b601730..8fa68477 100644 --- a/rtl837x_common.h +++ b/rtl837x_common.h @@ -131,6 +131,7 @@ struct flash_region_t { }; extern __xdata char port_names[9][PORT_NAME_SIZE]; +extern __xdata uint16_t lldp_port_status; extern __xdata bool stp_enabled; diff --git a/rtl837x_lldp.c b/rtl837x_lldp.c index e426fdef..57f27150 100644 --- a/rtl837x_lldp.c +++ b/rtl837x_lldp.c @@ -14,6 +14,7 @@ extern __xdata bool lldp_enabled; extern __code const struct machine machine; __xdata uint8_t lldp_seconds; +__xdata uint16_t lldp_port_status = 0xffff; void lldp_init(void) __banked { @@ -109,10 +110,12 @@ void lldp_send(void) __banked __reentrant for (uint8_t port = machine.min_port; port <= machine.max_port; port++) { - LLDP_O->payload[port_position] = '0' + machine.log_to_phys_port[port]; - LLDP_O->rtl_tag.pmask = HTONS((uint16_t)1 << port); + if ((1 << machine.log_to_phys_port[port]) & lldp_port_status) { + LLDP_O->payload[port_position] = '0' + machine.log_to_phys_port[port]; + LLDP_O->rtl_tag.pmask = HTONS((uint16_t)1 << port); - tcpip_output(); + tcpip_output(); + } } } From 58fd1a3cccc4e13445feac9b2591f40699b51d4b Mon Sep 17 00:00:00 2001 From: Rob Emery Date: Tue, 15 Sep 2026 15:43:16 +0100 Subject: [PATCH 11/18] Adding lldp.json to the simulator Moving the global on/off onto the LLDP page Building a table of LLDP status from the S ports. Later on, if we wish to implement an lldp receiver we can display the remote details here Adding support to the cmd parser for the lldp commands --- cmd_parser.c | 4 ++-- html/app.js | 54 ++++++++++++++++++++++++++++++++++++++--------- html/index.html | 13 ++++++++++-- httpd/httpd.c | 2 ++ httpd/page_impl.c | 20 +++++++++++++++++- httpd/page_impl.h | 1 + rtl837x_common.h | 2 +- rtl837x_lldp.c | 6 +++--- tools/httpd_sim.c | 18 +++++++++++++++- 9 files changed, 100 insertions(+), 20 deletions(-) diff --git a/cmd_parser.c b/cmd_parser.c index 9c52e1f9..8262463b 100644 --- a/cmd_parser.c +++ b/cmd_parser.c @@ -969,9 +969,9 @@ void parse_port(void) phy_set_duplex(); } else if (cmd_compare(2, "lldp")) { if (cmd_compare(3, "block")){ - lldp_port_status &= ~(1 << phy_settings.port); + lldp_physical_port_status &= ~(1 << phy_settings.port); } else if (cmd_compare(3, "permit")) { - lldp_port_status |= 1 << phy_settings.port; + lldp_physical_port_status |= 1 << phy_settings.port; } else { print_string ("Unknown port lldp [block|permit] command\n"); } diff --git a/html/app.js b/html/app.js index 9bbafcc9..d9c9a1e8 100644 --- a/html/app.js +++ b/html/app.js @@ -3,7 +3,7 @@ var LANG={ en:{ nav_dash:"Dashboard",nav_ports:"Ports",nav_stp:"Spanning tree",nav_stats:"Statistics", nav_vlan:"VLANs",nav_l2:"MAC table",nav_mirror:"Mirroring",nav_isolate:"Isolation",nav_lag:"LAG",nav_eee:"EEE", -nav_bw:"Bandwidth",nav_system:"System",nav_fw:"Firmware", +nav_bw:"Bandwidth",nav_system:"System",nav_fw:"Firmware",nav_lldp:"LLDP", hdr_dirty:"unsaved changes",hdr_dirty_t:"Running config differs from startup config", hdr_save:"Save to flash",hdr_save_t:"Persist running configuration to flash", th_auto:"System",th_auto_sel:"System (Selenized)",sy_display:"Display",sy_theme:"Theme",sy_display_note:"Stored in this browser only.",th_light:"Light",th_dark:"Dark", @@ -40,6 +40,7 @@ stp_en_q:"Enable spanning tree?", stp_en_d:"Ports start blocked and take up to twice the forward delay to reach forwarding; edge ports recover immediately.", stp_dis_q:"Disable spanning tree?",stp_dis_d:"All ports go straight to forwarding; loop protection is lost.", stp_cost_err:"Path cost must be 0-200000000", +lldp_enabled: "LLDP Enabled",lldp_title:"Service Status",lldp_portname:"Name",lldp_enable:"Send Frames", st_title:"Port statistics",st_h:"totals since boot",st_txg:"TX good",st_txb:"TX bad",st_rxg:"RX good", st_rxb:"RX bad",st_details:"Details",st_counters:"MIB counters",st_nonzero:"non-zero only", st_autoref:"auto-refresh",st_counter:"Counter",st_value:"Value",st_fail:"failed to load counters", @@ -77,7 +78,7 @@ sc_title:"Storm control",sc_h:"per port and traffic type, empty = off",sc_bcast: sc_ucast:"Unknown unicast",sc_umcast:"Unknown multicast",sc_err:"Storm limit must be 1-1048575 pps or 1-10000000 kbit/s", sc_note:"Frames of a type above its limit are dropped where they enter the switch.", sy_network:"Network",sy_dhcp:"Use DHCP",sy_dhcp_t:"Request address via DHCP",sy_services:"Services", -sy_igmp:"IGMP snooping",sy_sysip:"server IP",sy_server:"Server",sy_port:"Port",sy_lldp:"LLDP", +sy_igmp:"IGMP snooping",sy_sysip:"server IP",sy_server:"Server",sy_port:"Port", sy_sesstmo:"Session timeout (s)",sy_sesstmo_err:"Session timeout must be 1-65535 s", sy_services_note:"Service state reflects the startup config; runtime state is not readable.", sy_password:"Admin password",sy_newpw:"New password",sy_repeat:"Repeat",sy_pwapply:"Change password", @@ -788,6 +789,7 @@ var CONF_CMDS=[ /^ingress(\s+\d{1,2}[tua])+$/,/^ingress\s+[tua]$/, /^port\s+\d{1,2}\s+(10m|100m|1g|2g5|5g|10g|auto|on|off)(\s+(half|full))?$/, /^port\s+\d{1,2}\s+name\s+\S+$/, + /^port\s+\d{1,2}\s+lldp\s+(permit|block)+$/, /^eee\s+(on|off)(\s+\d{1,2})?(\s+(100m|1g|2g5))?$/, /^mirror(\s+\d{1,2})(\s+\d{1,2}[tr]?)+$/,/^mirror\s+off$/, /^lag\s+[1-4](\s+\d{1,2})+$/,/^lag\s+[1-4]\s+d$/,/^laghash\s+[1-4](\s+\w+)+$/, @@ -798,7 +800,7 @@ var CONF_CMDS=[ /^stp\s+(port\s+\d{1,2}|lag\s+[1-4])\s+cost\s+\d{1,9}$/,/^stp\s+(port\s+\d{1,2}|lag\s+[1-4])\s+prio\s+\d{1,3}$/, /^stp\s+(port\s+\d{1,2}|lag\s+[1-4])\s+guard\s+(none|bpdu|root)$/,/^stp\s+(port\s+\d{1,2}|lag\s+[1-4])\s+filter\s+(on|off)$/, /^stp\s+(port\s+\d{1,2}|lag\s+[1-4])\s+p2p\s+(auto|on|off)$/, - /^igmp\s+(on|off)$/,/^mtu\s+\d{1,2}\s+\d+$/, + /^igmp\s+(on|off)$/,/^mtu\s+\d{1,2}\s+\d+$/,/^lldp\s+(on|off)$/, /^bw\s+(in|out)\s+\d{1,2}\s+\S+$/, /^storm\s+\d{1,2}\s+(bcast|mcast|ucast|umcast)\s+(off|\d{1,8}\s+(pps|kbps))$/, ]; @@ -860,6 +862,7 @@ var TABS=[ {id:"mirror",icon:"M12 3v18M7 8l-4 4 4 4M17 8l4 4-4 4"}, {id:"isolate",icon:"M4 4h6v6H4zM14 14h6v6h-6zM14 4h6v6h-6zM4 14h6v6H4z"}, {id:"lag", icon:"M7 8a4 4 0 100 8h3M17 8a4 4 0 110 8h-3M9 12h6"}, + {id:"lldp", icon:"M7 12h6"}, {id:"eee", icon:"M13 2L4 14h6l-1 8 9-12h-6z"}, {id:"bw", icon:"M4 18a8 8 0 0116 0M12 18l4-6"}, {id:"system",icon:"M12 8a4 4 0 100 8 4 4 0 000-8zM4 12h2M18 12h2M12 4v2M12 18v2M6 6l1.5 1.5M16.5 16.5L18 18M18 6l-1.5 1.5M7.5 16.5L6 18"}, @@ -1835,6 +1838,44 @@ function lagApply(g){ } tabHooks.lag={enter:function(){needPorts(function(){buildLag();lagLoad().catch(function(){})})}}; + +function lldpLoad(){ + return getJSON("/lldp.json").then(function(s){ + $("lldp_en").checked=s.on; + var tb=$("lldp_table").tBodies[0];tb.innerHTML=""; + if(tb.rows.length!==S.n){ + tb.innerHTML=""; + for(var i=0;i +
+

LLDP

+ +
+ +
Port NumberNameSend Frames
+
+
+

Port isolation

A frame entering a port may only leave through the ports ticked in its row.

@@ -386,8 +397,6 @@ button.danger{background:var(--bad);color:#fff;border-color:transparent}

Services

-
diff --git a/httpd/httpd.c b/httpd/httpd.c index 770eaa39..aec2eaf1 100644 --- a/httpd/httpd.c +++ b/httpd/httpd.c @@ -1034,6 +1034,8 @@ void httpd_appcall(void) send_lag(); } else if (is_word(q, "/stp.json")) { send_stp(); + } else if (is_word(q, "/lldp.json")) { + send_lldp(); } else if (is_word(q, "/vlanlist")) { send_vlanlist(); } else if (is_word(q, "/config")) { diff --git a/httpd/page_impl.c b/httpd/page_impl.c index fe5ccced..f9c55b83 100644 --- a/httpd/page_impl.c +++ b/httpd/page_impl.c @@ -49,6 +49,9 @@ extern __xdata char sfp_module_model[2][17]; extern __xdata char sfp_module_serial[2][17]; extern __xdata uint8_t sfp_options[2]; +extern __xdata uint16_t lldp_physical_port_status; +extern __xdata bool lldp_enabled; + __code const uint8_t * __code const HTTP_RESPONCE_JSON = "HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Type: application/json\r\n\r\n"; __code const uint8_t * __code const HTTP_RESPONCE_TXT = "HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Type: text/plain\r\n\r\n"; @@ -619,7 +622,6 @@ static void bridge_to_html(void) byte_to_html(pi_mac[i]); } - void send_stp(void) { uint8_t i, j, st, dsg; @@ -734,6 +736,22 @@ void send_stp(void) slen += strtox(outbuf + slen, "]}"); } +void send_lldp(void) +{ + dbg_string("send_lldp called\n"); + slen = strtox(outbuf, HTTP_RESPONCE_JSON); + + slen += strtox(outbuf + slen, "{\"on\":"); + bool_to_html(lldp_enabled); + slen += strtox(outbuf + slen, ",\"port_status\": ["); + + for (uint8_t port = machine.min_port; port <= machine.max_port; port++) { + bool_to_html((1 << machine.log_to_phys_port[port]) & lldp_physical_port_status); + slen += strtox(outbuf + slen, ","); + } + + slen += strtox(outbuf + slen, "]}"); +} diff --git a/httpd/page_impl.h b/httpd/page_impl.h index d6690dc6..fd51c8c6 100644 --- a/httpd/page_impl.h +++ b/httpd/page_impl.h @@ -23,6 +23,7 @@ void send_config(void); void send_cmd_log(void); void send_lag(void); void send_stp(void); +void send_lldp(void); void send_vlanlist(void); /* Convert only the lower nibble to ascii HEX char. diff --git a/rtl837x_common.h b/rtl837x_common.h index 8fa68477..74425fbc 100644 --- a/rtl837x_common.h +++ b/rtl837x_common.h @@ -131,7 +131,7 @@ struct flash_region_t { }; extern __xdata char port_names[9][PORT_NAME_SIZE]; -extern __xdata uint16_t lldp_port_status; +extern __xdata uint16_t lldp_physical_port_status; extern __xdata bool stp_enabled; diff --git a/rtl837x_lldp.c b/rtl837x_lldp.c index 57f27150..3e484cbe 100644 --- a/rtl837x_lldp.c +++ b/rtl837x_lldp.c @@ -14,7 +14,7 @@ extern __xdata bool lldp_enabled; extern __code const struct machine machine; __xdata uint8_t lldp_seconds; -__xdata uint16_t lldp_port_status = 0xffff; +__xdata uint16_t lldp_physical_port_status = 0xffff; void lldp_init(void) __banked { @@ -110,9 +110,9 @@ void lldp_send(void) __banked __reentrant for (uint8_t port = machine.min_port; port <= machine.max_port; port++) { - if ((1 << machine.log_to_phys_port[port]) & lldp_port_status) { + if ((1 << machine.log_to_phys_port[port]) & lldp_physical_port_status) { LLDP_O->payload[port_position] = '0' + machine.log_to_phys_port[port]; - LLDP_O->rtl_tag.pmask = HTONS((uint16_t)1 << port); + LLDP_O->rtl_tag.pmask = HTONS((uint16_t)1 << machine.log_to_phys_port[port]); tcpip_output(); } diff --git a/tools/httpd_sim.c b/tools/httpd_sim.c index 76c088c3..efa367c6 100644 --- a/tools/httpd_sim.c +++ b/tools/httpd_sim.c @@ -454,6 +454,16 @@ void send_mtu(int s) json_object_put(mtus); } +void send_lldp(int s) +{ + char *header = "HTTP/1.1 200 OK\r\n" + "Content-Type: application/json; charset=UTF-8\r\n\r\n"; + + char *body = "{\"on\": true, \"port_status\":[true,true,false,true,true,true,true,false,false]}"; + + write(s, header, strlen(header)); + write(s, body, strlen(body)); +} void send_cmd_log(int s) { @@ -468,7 +478,6 @@ void send_cmd_log(int s) } } - void send_config(int s) { char *header = "HTTP/1.1 200 OK\r\n" @@ -701,6 +710,13 @@ void launch(struct Server *server) else send_vlan(new_socket, vlan); goto done; + } else if (!strncmp(&buffer[4], "/lldp.json", 10)) { + printf("LLDP request"); + if (!authenticated) + send_unauthorized(new_socket); + else + send_lldp(new_socket); + goto done; } else if (!strncmp(&buffer[4], "/cmd_log", 8)) { printf("Request cmd_log\n"); if (!authenticated) From 72c2625121769bd52f6f64aa4891c33d73e8dea2 Mon Sep 17 00:00:00 2001 From: Rob Emery Date: Tue, 15 Sep 2026 21:21:23 +0100 Subject: [PATCH 12/18] Icon from https://github.com/logicog/RTLPlayground/pull/429#issuecomment-5687458129 --- html/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/html/app.js b/html/app.js index d9c9a1e8..3da29b5a 100644 --- a/html/app.js +++ b/html/app.js @@ -862,7 +862,7 @@ var TABS=[ {id:"mirror",icon:"M12 3v18M7 8l-4 4 4 4M17 8l4 4-4 4"}, {id:"isolate",icon:"M4 4h6v6H4zM14 14h6v6h-6zM14 4h6v6h-6zM4 14h6v6H4z"}, {id:"lag", icon:"M7 8a4 4 0 100 8h3M17 8a4 4 0 110 8h-3M9 12h6"}, - {id:"lldp", icon:"M7 12h6"}, + {id:"lldp", icon:"M11 12a1 1 0 002 0 1 1 0 00-2 0M8.5 8.5a5 5 0 000 7M15.5 8.5a5 5 0 010 7M5.6 5.6a9 9 0 000 12.8M18.4 5.6a9 9 0 010 12.8"}, {id:"eee", icon:"M13 2L4 14h6l-1 8 9-12h-6z"}, {id:"bw", icon:"M4 18a8 8 0 0116 0M12 18l4-6"}, {id:"system",icon:"M12 8a4 4 0 100 8 4 4 0 000-8zM4 12h2M18 12h2M12 4v2M12 18v2M6 6l1.5 1.5M16.5 16.5L18 18M18 6l-1.5 1.5M7.5 16.5L6 18"}, From 2dfe531fe00d9904930ba69f0ea7c9cedddb2789 Mon Sep 17 00:00:00 2001 From: Rob Emery Date: Fri, 25 Sep 2026 19:34:47 +0100 Subject: [PATCH 13/18] Fixing missing parts to make the tests pass boot_to_html outputs 1 and 0 not true and false, making the sim match the real implementation. lldp.json has a hanging , in the array; so overwriting it --- httpd/page_impl.c | 6 ++---- test/env_tables.c | 3 +++ test/test_httpd_tx.c | 1 + tools/httpd_sim.c | 2 +- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/httpd/page_impl.c b/httpd/page_impl.c index f9c55b83..0d054be9 100644 --- a/httpd/page_impl.c +++ b/httpd/page_impl.c @@ -743,18 +743,16 @@ void send_lldp(void) slen += strtox(outbuf + slen, "{\"on\":"); bool_to_html(lldp_enabled); - slen += strtox(outbuf + slen, ",\"port_status\": ["); + slen += strtox(outbuf + slen, ",\"port_status\":["); for (uint8_t port = machine.min_port; port <= machine.max_port; port++) { bool_to_html((1 << machine.log_to_phys_port[port]) & lldp_physical_port_status); slen += strtox(outbuf + slen, ","); } - + slen--; //overwrite the last comma slen += strtox(outbuf + slen, "]}"); } - - void send_eee(void) { dbg_string("send_eee called\nsending EEE status\n"); diff --git a/test/env_tables.c b/test/env_tables.c index 274a67d5..3b246948 100644 --- a/test/env_tables.c +++ b/test/env_tables.c @@ -84,6 +84,9 @@ char port_names[9][PORT_NAME_SIZE]; struct flash_region_t flash_region; struct syslog_state syslog_state; bool stp_enabled; +bool lldp_enabled; +uint16_t lldp_physical_port_status; + uip_ipaddr_t uip_hostaddr, uip_draddr, uip_netmask; struct uip_eth_addr uip_ethaddr = { .addr = { 0x02, 0x11, 0x22, 0x33, 0x44, 0x55 } }; diff --git a/test/test_httpd_tx.c b/test/test_httpd_tx.c index c7ccf09c..4df05472 100644 --- a/test/test_httpd_tx.c +++ b/test/test_httpd_tx.c @@ -152,6 +152,7 @@ void send_lag(void) { } void send_stp(void) { } void send_stp_counters(void) { } void send_vlanlist(void) { } +void send_lldp(void) {} extern uint8_t authenticated; diff --git a/tools/httpd_sim.c b/tools/httpd_sim.c index efa367c6..53a4d403 100644 --- a/tools/httpd_sim.c +++ b/tools/httpd_sim.c @@ -459,7 +459,7 @@ void send_lldp(int s) char *header = "HTTP/1.1 200 OK\r\n" "Content-Type: application/json; charset=UTF-8\r\n\r\n"; - char *body = "{\"on\": true, \"port_status\":[true,true,false,true,true,true,true,false,false]}"; + char *body = "{\"on\": 1,\"port_status\":[1,1,0,1,1,1,1,0,0]}"; write(s, header, strlen(header)); write(s, body, strlen(body)); From 9b2bd7f1b82efc113471ead52a861af973d0dff5 Mon Sep 17 00:00:00 2001 From: Rob Emery Date: Thu, 1 Oct 2026 11:37:13 +0100 Subject: [PATCH 14/18] The tick rate has been changed now so we'll have to approximate timing with ticks Fixing up the log_to_phys_port assignment 2000 is about every 10 seconds, so scaling up to once every 30ish Removing the attempt to block lldp frames, This does not work however the hard work has been done for us in the #514 so we can borrow that work later on --- cmd_parser.c | 4 ++-- html/app.js | 4 +--- httpd/page_impl.c | 4 ++-- rtl837x_common.h | 2 +- rtl837x_lldp.c | 16 ++++++++-------- rtl837x_lldp.h | 2 +- rtlplayground.c | 4 ---- test/env_tables.c | 2 +- 8 files changed, 16 insertions(+), 22 deletions(-) diff --git a/cmd_parser.c b/cmd_parser.c index 8262463b..3cb7724c 100644 --- a/cmd_parser.c +++ b/cmd_parser.c @@ -969,9 +969,9 @@ void parse_port(void) phy_set_duplex(); } else if (cmd_compare(2, "lldp")) { if (cmd_compare(3, "block")){ - lldp_physical_port_status &= ~(1 << phy_settings.port); + lldp_logical_port_status &= ~(1 << phy_settings.port); } else if (cmd_compare(3, "permit")) { - lldp_physical_port_status |= 1 << phy_settings.port; + lldp_logical_port_status |= 1 << phy_settings.port; } else { print_string ("Unknown port lldp [block|permit] command\n"); } diff --git a/html/app.js b/html/app.js index 3da29b5a..00af6434 100644 --- a/html/app.js +++ b/html/app.js @@ -1872,10 +1872,8 @@ $("lldp_en").addEventListener("change",function(){ var el=this; postCmd("lldp "+(el.checked?"on":"off")).catch(function(){el.checked=!el.checked}); }); - tabHooks.lldp={enter:needPorts(lldpLoad)}; - function eeeFlags(bits){ var b=parseInt(bits,2); return["100M","1G","2.5G"].map(function(s,i){ @@ -2036,7 +2034,7 @@ function sysLoad(){ cfgReload(); } function cfgParseKnown(txt){ - var igmp=false,lldp=false,syslog=false; + var igmp=false,syslog=false; txt.split(/\r?\n/).forEach(function(l){ l=l.trim(); if(/^igmp on$/.test(l))igmp=true; diff --git a/httpd/page_impl.c b/httpd/page_impl.c index 0d054be9..9a818dcf 100644 --- a/httpd/page_impl.c +++ b/httpd/page_impl.c @@ -49,7 +49,7 @@ extern __xdata char sfp_module_model[2][17]; extern __xdata char sfp_module_serial[2][17]; extern __xdata uint8_t sfp_options[2]; -extern __xdata uint16_t lldp_physical_port_status; +extern __xdata uint16_t lldp_logical_port_status; extern __xdata bool lldp_enabled; __code const uint8_t * __code const HTTP_RESPONCE_JSON = "HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Type: application/json\r\n\r\n"; @@ -746,7 +746,7 @@ void send_lldp(void) slen += strtox(outbuf + slen, ",\"port_status\":["); for (uint8_t port = machine.min_port; port <= machine.max_port; port++) { - bool_to_html((1 << machine.log_to_phys_port[port]) & lldp_physical_port_status); + bool_to_html((1 << port) & lldp_logical_port_status); slen += strtox(outbuf + slen, ","); } slen--; //overwrite the last comma diff --git a/rtl837x_common.h b/rtl837x_common.h index 74425fbc..a36d41fc 100644 --- a/rtl837x_common.h +++ b/rtl837x_common.h @@ -131,7 +131,7 @@ struct flash_region_t { }; extern __xdata char port_names[9][PORT_NAME_SIZE]; -extern __xdata uint16_t lldp_physical_port_status; +extern __xdata uint16_t lldp_logical_port_status; extern __xdata bool stp_enabled; diff --git a/rtl837x_lldp.c b/rtl837x_lldp.c index 3e484cbe..84208433 100644 --- a/rtl837x_lldp.c +++ b/rtl837x_lldp.c @@ -13,22 +13,22 @@ extern __xdata bool lldp_enabled; extern __code const struct machine machine; -__xdata uint8_t lldp_seconds; -__xdata uint16_t lldp_physical_port_status = 0xffff; +__xdata uint16_t lldp_ticks; +__xdata uint16_t lldp_logical_port_status = 0xffff; void lldp_init(void) __banked { - lldp_seconds = 0; + lldp_ticks = 0; } void lldp_tick(void) __banked { - lldp_seconds++; + lldp_ticks++; - if (lldp_seconds < LLDP_TX_INTERVAL_SEC) + if (lldp_ticks < LLDP_TX_INTERVAL_TICKS) return; - lldp_seconds = 0; + lldp_ticks = 0; if(lldp_enabled == 1) lldp_send(); @@ -110,9 +110,9 @@ void lldp_send(void) __banked __reentrant for (uint8_t port = machine.min_port; port <= machine.max_port; port++) { - if ((1 << machine.log_to_phys_port[port]) & lldp_physical_port_status) { + if ((1 << port) & lldp_logical_port_status) { LLDP_O->payload[port_position] = '0' + machine.log_to_phys_port[port]; - LLDP_O->rtl_tag.pmask = HTONS((uint16_t)1 << machine.log_to_phys_port[port]); + LLDP_O->rtl_tag.pmask = HTONS((uint16_t)1 << port); tcpip_output(); } diff --git a/rtl837x_lldp.h b/rtl837x_lldp.h index b441d8c4..298775c6 100644 --- a/rtl837x_lldp.h +++ b/rtl837x_lldp.h @@ -8,7 +8,7 @@ #define LLDP_ETHERTYPE_LENGTH 2 #define LLDP_MAX_FRAME 256 #define MAC_ADDR_LEN 6 -#define LLDP_TX_INTERVAL_SEC 30 +#define LLDP_TX_INTERVAL_TICKS 6000 #define LLDP_PORT_ID_TLV_TYPE 2 #define LLDP_PORT_ID_TLV_LENGTH 0x02 diff --git a/rtlplayground.c b/rtlplayground.c index 4fb22009..43ff83f0 100644 --- a/rtlplayground.c +++ b/rtlplayground.c @@ -1068,10 +1068,6 @@ void handle_rx(void) if (uip_len) { tcpip_output(); } - } else if (lldp_enabled && uip_buf[0] == 0x01 && uip_buf[1] == 0x80 && uip_buf[2] == 0xc2 - && uip_buf[3] == 0x00 && uip_buf[4] == 0x00 && uip_buf[5] == 0x0e && uip_buf[12] == 0x88 - && uip_buf[13] == 0xcc) { //LLDP Mac + EtherType check - // LLDP packets shouldn't be passed elsewhere } else if (ETH_IN->ether_type == HTONS(0x0806)) { // ARP uip_arp_arpin(); if (uip_len) { diff --git a/test/env_tables.c b/test/env_tables.c index 3b246948..41694b72 100644 --- a/test/env_tables.c +++ b/test/env_tables.c @@ -85,7 +85,7 @@ struct flash_region_t flash_region; struct syslog_state syslog_state; bool stp_enabled; bool lldp_enabled; -uint16_t lldp_physical_port_status; +uint16_t lldp_logical_port_status; uip_ipaddr_t uip_hostaddr, uip_draddr, uip_netmask; From dda873a633498aa4c27737714b49943c09cc14a6 Mon Sep 17 00:00:00 2001 From: Rob Emery Date: Tue, 6 Oct 2026 07:58:48 +0100 Subject: [PATCH 15/18] Moving lldp cmd parsing to be above the is_sfp check to take precedence Needed to increase buffer size for this to send app.js The logical port status is what is returned in the array, for non-sequential systems we need to map through physToLog rather than just subtracting 1 physToLog is 1 based! not 0; also fixing up typecasts Reimplementing using an array of objects to represent ports. The previous approach of a bitmask of logical ports, was just too simple on devices such as the SWGT024_V2_0_MANAGED which has gaps in the port assignments. --- cmd_parser.c | 16 ++++++++-------- html/app.js | 26 ++++++++------------------ httpd/page_impl.c | 30 +++++++++++++++++++++++------- tools/httpd_sim.c | 14 +++++++++++++- tools/httpd_sim.h | 2 +- 5 files changed, 53 insertions(+), 35 deletions(-) diff --git a/cmd_parser.c b/cmd_parser.c index 3cb7724c..8cfc7d4b 100644 --- a/cmd_parser.c +++ b/cmd_parser.c @@ -914,6 +914,14 @@ void parse_port(void) print_string("\nName set to: \""); print_string_x(port_names[phy_settings.port]); print_string("\"\n"); + } else if (cmd_compare(2, "lldp")) { + if (cmd_compare(3, "block")){ + lldp_logical_port_status &= ~((uint16_t)1 << phy_settings.port); + } else if (cmd_compare(3, "permit")) { + lldp_logical_port_status |= (uint16_t)1 << phy_settings.port; + } else { + print_string ("Unknown port lldp [block|permit] command\n"); + } } else if (machine.is_sfp[phy_settings.port]) { print_string(" is SFP no PHY information available.\n"); } else if (cmd_compare(2, "10m")) { @@ -967,14 +975,6 @@ void parse_port(void) else phy_settings.duplex = PHY_DUPLEX_HALF; phy_set_duplex(); - } else if (cmd_compare(2, "lldp")) { - if (cmd_compare(3, "block")){ - lldp_logical_port_status &= ~(1 << phy_settings.port); - } else if (cmd_compare(3, "permit")) { - lldp_logical_port_status |= 1 << phy_settings.port; - } else { - print_string ("Unknown port lldp [block|permit] command\n"); - } } else { cmd_error("Unknown port command\n"); } diff --git a/html/app.js b/html/app.js index 00af6434..d5391274 100644 --- a/html/app.js +++ b/html/app.js @@ -1843,36 +1843,26 @@ function lldpLoad(){ return getJSON("/lldp.json").then(function(s){ $("lldp_en").checked=s.on; var tb=$("lldp_table").tBodies[0];tb.innerHTML=""; - if(tb.rows.length!==S.n){ - tb.innerHTML=""; - for(var i=0;i> i)); + slen += strtox(outbuf + slen, "\"}"); + + if (i < machine.max_port) + char_to_html(','); + else + char_to_html(']'); + } + char_to_html('}'); } void send_eee(void) diff --git a/tools/httpd_sim.c b/tools/httpd_sim.c index 53a4d403..fba9ffae 100644 --- a/tools/httpd_sim.c +++ b/tools/httpd_sim.c @@ -459,7 +459,19 @@ void send_lldp(int s) char *header = "HTTP/1.1 200 OK\r\n" "Content-Type: application/json; charset=UTF-8\r\n\r\n"; - char *body = "{\"on\": 1,\"port_status\":[1,1,0,1,1,1,1,0,0]}"; + //portNum represents physical ports + char *body = "{\"on\": 1,\"portStatus\":[\ + {\"portNum\":1,\"logPort\":1,\"name\":\"PC\",\"enabled\":1},\ + {\"portNum\":2,\"logPort\":2,\"name\":\"\",\"enabled\":1},\ + {\"portNum\":3,\"logPort\":3,\"name\":\"\",\"enabled\":1},\ + {\"portNum\":4,\"logPort\":4,\"name\":\"\",\"enabled\":0},\ + {\"portNum\":5,\"logPort\":5,\"name\":\"\",\"enabled\":1},\ + {\"portNum\":6,\"logPort\":6,\"name\":\"TV\",\"enabled\":1},\ + {\"portNum\":7,\"logPort\":7,\"name\":\"\",\"enabled\":0},\ + {\"portNum\":8,\"logPort\":8,\"name\":\"Router\",\"enabled\":1},\ + {\"portNum\":9,\"logPort\":9,\"name\":\"\",\"enabled\":1},\ + {\"portNum\":10,\"logPort\":10,\"name\":\"Uplink\",\"enabled\":1}\ + ]}"; write(s, header, strlen(header)); write(s, body, strlen(body)); diff --git a/tools/httpd_sim.h b/tools/httpd_sim.h index 49c078aa..df59ff19 100644 --- a/tools/httpd_sim.h +++ b/tools/httpd_sim.h @@ -3,7 +3,7 @@ #include -#define BUFFER_SIZE 131072 +#define BUFFER_SIZE 262144 struct Server { int domain; From eff3f42ae1d6556adf2174faac6088e1290bb5aa Mon Sep 17 00:00:00 2001 From: Rob Emery Date: Fri, 9 Oct 2026 12:43:15 +0100 Subject: [PATCH 16/18] Rewriting sim implementation to be data driven The value for enabled should be an int, not a string --- html/app.js | 2 +- httpd/page_impl.c | 5 ++--- tools/httpd_sim.c | 36 +++++++++++++++++++++--------------- 3 files changed, 24 insertions(+), 19 deletions(-) diff --git a/html/app.js b/html/app.js index d5391274..3e95e0e7 100644 --- a/html/app.js +++ b/html/app.js @@ -1852,7 +1852,7 @@ function lldpLoad(){ postCmd("port "+p.portNum+" lldp "+(this.checked?"permit":"block")) .then(function(){setTimeout(lldpLoad,300)}).catch(function(){}); }}),h("i")]); - sw.firstChild.checked=p.enabled; + sw.firstChild.checked=p.permitted; tr.insertCell().appendChild(sw); }); }); diff --git a/httpd/page_impl.c b/httpd/page_impl.c index 11110ef1..1a51580f 100644 --- a/httpd/page_impl.c +++ b/httpd/page_impl.c @@ -755,11 +755,10 @@ void send_lldp(void) for (uint8_t j = 0; j < PORT_NAME_SIZE && port_names[i][j]; j++) { json_char_to_html(port_names[i][j]); } - slen += strtox(outbuf + slen, "\""); - slen += strtox(outbuf + slen, ",\"enabled\":\""); + slen += strtox(outbuf + slen, "\",\"permitted\":"); bool_to_html(1 & (lldp_logical_port_status >> i)); - slen += strtox(outbuf + slen, "\"}"); + slen += strtox(outbuf + slen, "}"); if (i < machine.max_port) char_to_html(','); diff --git a/tools/httpd_sim.c b/tools/httpd_sim.c index fba9ffae..5fea029f 100644 --- a/tools/httpd_sim.c +++ b/tools/httpd_sim.c @@ -447,7 +447,7 @@ void send_mtu(int s) json_object_object_add(v, "mtu", json_object_new_string(mtu)); json_object_array_add(mtus, v); } - write(s, header, strlen(header)); + write(s, header, strlen(header)); jstring = json_object_to_json_string_ext(mtus, JSON_C_TO_STRING_PLAIN); write(s, jstring, strlen(jstring)); @@ -456,25 +456,31 @@ void send_mtu(int s) void send_lldp(int s) { + struct json_object *c, *portStatuses, *p; + const char *jstring; char *header = "HTTP/1.1 200 OK\r\n" "Content-Type: application/json; charset=UTF-8\r\n\r\n"; - //portNum represents physical ports - char *body = "{\"on\": 1,\"portStatus\":[\ - {\"portNum\":1,\"logPort\":1,\"name\":\"PC\",\"enabled\":1},\ - {\"portNum\":2,\"logPort\":2,\"name\":\"\",\"enabled\":1},\ - {\"portNum\":3,\"logPort\":3,\"name\":\"\",\"enabled\":1},\ - {\"portNum\":4,\"logPort\":4,\"name\":\"\",\"enabled\":0},\ - {\"portNum\":5,\"logPort\":5,\"name\":\"\",\"enabled\":1},\ - {\"portNum\":6,\"logPort\":6,\"name\":\"TV\",\"enabled\":1},\ - {\"portNum\":7,\"logPort\":7,\"name\":\"\",\"enabled\":0},\ - {\"portNum\":8,\"logPort\":8,\"name\":\"Router\",\"enabled\":1},\ - {\"portNum\":9,\"logPort\":9,\"name\":\"\",\"enabled\":1},\ - {\"portNum\":10,\"logPort\":10,\"name\":\"Uplink\",\"enabled\":1}\ - ]}"; + c = json_object_new_object(); + json_object_object_add(c, "on", json_object_new_int(1)); + portStatuses = json_object_new_array_ext(PORTS); + + for (int i = 1; i <= PORTS; i++) { + p = json_object_new_object(); + json_object_object_add(p, "portNum", json_object_new_int(i)); + json_object_object_add(p, "logPort", json_object_new_int(i-1)); + json_object_object_add(p, "name", json_object_new_string("")); + json_object_object_add(p, "permitted", json_object_new_int(1)); + json_object_array_add(portStatuses, p); + } + + json_object_object_add(c, "portStatus", portStatuses); write(s, header, strlen(header)); - write(s, body, strlen(body)); + + jstring = json_object_to_json_string_ext(c, JSON_C_TO_STRING_PLAIN); + write(s, jstring, strlen(jstring)); + json_object_put(c); } void send_cmd_log(int s) From de0d413c0c6641580520ff5bee4bbbbeff30db8f Mon Sep 17 00:00:00 2001 From: d00f <8052722+DrDoof@users.noreply.github.com> Date: Sat, 10 Oct 2026 17:54:55 +0200 Subject: [PATCH 17/18] dns: show the last lookup with the servers The answer to a lookup arrives after the command has returned, so the console of the web interface never shows it. Plain "dns" now prints the name and the address, or whether it is still pending or failed. --- dns.c | 12 ++++++++++++ doc/dns.md | 6 +++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/dns.c b/dns.c index 2dc7e575..68776c72 100644 --- a/dns.c +++ b/dns.c @@ -200,6 +200,18 @@ void dns_show(void) __banked print_string(", from DHCP "); print_ip(dhcp_state.dns); write_char('\n'); + if (!dns_state.name[0]) + return; + print_string("Last lookup "); + print_string_x(dns_state.name); + print_string(": "); + if (dns_state.status == DNS_DONE) + print_ip(dns_state.addr); + else if (dns_state.status == DNS_PENDING) + print_string("pending"); + else + print_string("failed"); + write_char('\n'); } diff --git a/doc/dns.md b/doc/dns.md index 3763a404..8a86418d 100644 --- a/doc/dns.md +++ b/doc/dns.md @@ -6,9 +6,13 @@ for anything else that is given a name instead of an address. ``` dns server [] # one or two servers, 0.0.0.0 removes them dns lookup # resolve a name and print the address -dns # show the servers +dns # show the servers and the last lookup ``` +The answer to a lookup arrives after the command has returned, so it is +printed on the serial console and in the syslog. In the console of the web +interface, run `dns` afterwards to see it. + Without a configured server the resolver uses the one handed out by DHCP. Only address records are looked up; a query goes to the servers in turn, every two seconds, and gives up after six tries. A name that already is a From a27118a42877deac36c3a742be0232eecea709d8 Mon Sep 17 00:00:00 2001 From: d00f <8052722+DrDoof@users.noreply.github.com> Date: Sun, 11 Oct 2026 02:47:05 +0200 Subject: [PATCH 18/18] flash: add the dummy cycles required by the security register read Opcode 0x48 outputs 8 dummy clocks between the address and the first data byte. flash_configure_sio() sets DUMMYCYCLES to 0 and flash_read_security() never raises it, so the controller captures the dummy slot as the first of its four data bytes while the address still advances by four. The printed output therefore shows 0xff in place of every fourth byte and skips one content byte per transfer. flash_read_uid() already sets DUMMYCYCLES for opcode 0x4b, which needs the same allowance. --- rtl837x_flash.c | 1 + 1 file changed, 1 insertion(+) diff --git a/rtl837x_flash.c b/rtl837x_flash.c index c1357b04..1c2f65fc 100644 --- a/rtl837x_flash.c +++ b/rtl837x_flash.c @@ -269,6 +269,7 @@ void flash_read_security(void) // Set slow read mode SFR_FLASH_CMD_R = CMD_READ_SECURITY_REGS; // read security register + SFR_FLASH_DUMMYCYCLES = 8; // Transfer 4 bytes (command + 3byte address) SFR_FLASH_TCONF = 4;