From 7c11142a04f370134e83b65b62bd261e2ea06a9c Mon Sep 17 00:00:00 2001 From: d00f <8052722+DrDoof@users.noreply.github.com> Date: Mon, 14 Sep 2026 22:42:49 +0200 Subject: [PATCH] isolate: configure port isolation from the web interface A matrix of the ports, one row per ingress port, showing which ports a frame entering there may leave through. The port number of a row turns red when the port is not fully open, and the cell for the port itself is marked since it is always allowed. Apply sends one "isolate" command per changed row, and Allow all ticks the whole matrix. The switch side is a new /isolation.json, which reports each port's allowance mask read back from the hardware. The page is an Isolation section of the single-page UI. --- html/app.js | 76 +++++++++++++++++++++++++++++++++++++++++++- html/index.html | 12 +++++++ httpd/httpd.c | 2 ++ httpd/page_impl.c | 23 ++++++++++++++ httpd/page_impl.h | 1 + test/test_httpd_tx.c | 1 + 6 files changed, 114 insertions(+), 1 deletion(-) diff --git a/html/app.js b/html/app.js index 1d29bf08..494a8463 100644 --- a/html/app.js +++ b/html/app.js @@ -2,7 +2,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_lag:"LAG",nav_eee:"EEE", +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", 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", @@ -62,6 +62,9 @@ l2_learned:"learned",l2_loading:"loading...",l2_failed:"load failed",l2_entries: l2_del_t:"Delete entry",l2_flush_q:"Flush all learned MAC entries?", m_title:"Port mirroring",m_active:"active",m_monitor:"Monitor port",m_mirror:"Mirror",m_both:"Both", m_note:"Both = mirror RX and TX of the port to the monitor port.",m_none:"Select at least one mirrored port", +iso_title:"Port isolation",iso_from:"From port",iso_to:"To port",iso_all:"Allow all", +iso_intro:"A frame entering a port may only leave through the ports ticked in its row. Isolation is decided before forwarding, so a port that is not ticked is unreachable directly, whatever the address table says.", +iso_legend:"The cell of the port itself is always allowed. A port number in red marks a port that is not fully open.", lag_hash:"Hash:",lag_note:"A LAG needs at least one member to be saved in the startup config; applying an empty group clears it.", lag_clear_q:"Clear LAG {n}?",lag_clear_d:"All member ports return to normal operation.", e_title:"Energy Efficient Ethernet",e_adv:"Advertised",e_lp:"Link partner",e_active:"Active",e_enable:"Enable", @@ -855,6 +858,7 @@ var TABS=[ {id:"vlan", icon:"M12 3v6M12 9l-7 5M12 9l7 5M5 14v5M19 14v5M3 21h4M17 21h4"}, {id:"l2", icon:"M4 5h16M4 12h16M4 19h10"}, {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:"eee", icon:"M13 2L4 14h6l-1 8 9-12h-6z"}, {id:"bw", icon:"M4 18a8 8 0 0116 0M12 18l4-6"}, @@ -1710,6 +1714,76 @@ $("moff").addEventListener("click",function(){ }); tabHooks.mirror={enter:function(){needPorts(function(){buildMirror();mirrorLoad().catch(function(){})})}}; +var isoAllow=[]; +function isoBox(r,c){return $("iso"+r+"_"+c)} +function isoMark(){ + for(var r=1;r<=S.n;r++){ + var full=true; + for(var c=1;c<=S.n+1;c++){var b=isoBox(r,c);if(b&&!b.checked)full=false;} + $("isoh"+r).style.color=full?"":"var(--bad)"; + } +} +function isoTouch(){$("isotbl").dataset.dirty="1";isoMark()} +function isoTh(row,txt,attrs){var e=h("th",attrs);e.textContent=txt;row.appendChild(e);return e} +function buildIso(){ + var tb=$("isotbl"); + if(tb.tBodies[0].rows.length)return; + var h1=tb.tHead.insertRow(),h2=tb.tHead.insertRow(); + isoTh(h1,t("iso_from"),{rowspan:"2"});isoTh(h1,t("iso_to"),{colspan:String(S.n),style:"text-align:center"});isoTh(h1,"CPU",{rowspan:"2"}); + for(var c=1;c<=S.n;c++)isoTh(h2,String(c),{style:"text-align:center"}); + for(var r=1;r<=S.n;r++){ + var tr=tb.tBodies[0].insertRow(); + isoTh(tr,String(r),{id:"isoh"+r}); + for(c=1;c<=S.n+1;c++){ + var td=tr.insertCell(); + td.style.textAlign="center"; + if(r===c){td.className="mut";td.textContent="\u2022";continue;} + td.appendChild(h("input",{type:"checkbox",id:"iso"+r+"_"+c,onchange:isoTouch})); + } + } +} +function isoLoad(){ + return getJSON("/isolation.json").then(function(s){ + isoAllow=s; + if($("isotbl").dataset.dirty)return; + s.forEach(function(e){ + var m=parseInt(e.allow,16); + for(var c=1;c<=S.n;c++){var b=isoBox(e.portNum,c);if(b)b.checked=!!((m>>S.physToLog[c-1])&1);} + var cpu=isoBox(e.portNum,S.n+1); + if(cpu)cpu.checked=!!(m&0x200); + }); + isoMark(); + }); +} +function isoMask(r){ + var m=0; + for(var c=1;c<=S.n;c++){var b=isoBox(r,c);if(r===c||(b&&b.checked))m|=1< +
+

Port isolation

+

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

+
+

The cell of the port itself is always allowed.

+
+ + +
+
+
+

A LAG needs at least one member to be saved in the startup config; applying an empty group clears it.

diff --git a/httpd/httpd.c b/httpd/httpd.c index b31ec320..770eaa39 100644 --- a/httpd/httpd.c +++ b/httpd/httpd.c @@ -1026,6 +1026,8 @@ void httpd_appcall(void) l2_delete(short_parsed); } else if (is_word(q, "/mirror.json")) { send_mirror(); + } else if (is_word(q, "/isolation.json")) { + send_isolation(); } else if (is_word(q, "/mtu.json")) { send_mtu(); } else if (is_word(q, "/lag.json")) { diff --git a/httpd/page_impl.c b/httpd/page_impl.c index 6b108895..fe5ccced 100644 --- a/httpd/page_impl.c +++ b/httpd/page_impl.c @@ -863,6 +863,29 @@ void send_storm(void) } +void send_isolation(void) +{ + __xdata uint16_t allow; + + dbg_string("send_isolation called\n"); + slen = strtox(outbuf, HTTP_RESPONCE_JSON); + char_to_html('['); + for (uint8_t i = machine.min_port; i <= machine.max_port; i++) { + slen += strtox(outbuf + slen, "{\"portNum\":"); + itoa_html(machine.log_to_phys_port[i]); + slen += strtox(outbuf + slen, ",\"allow\":\""); + allow = port_isolation_get(i); + byte_to_html(allow >> 8); + byte_to_html(allow & 0xff); + char_to_html('"'); + char_to_html('}'); + if (i < machine.max_port) + char_to_html(','); + } + char_to_html(']'); +} + + void send_mtu(void) { dbg_string("send_mtu called\n"); diff --git a/httpd/page_impl.h b/httpd/page_impl.h index b8df7af9..d6690dc6 100644 --- a/httpd/page_impl.h +++ b/httpd/page_impl.h @@ -18,6 +18,7 @@ void send_l2(uint16_t idx); void l2_delete(uint16_t idx); void send_mirror(void); void send_mtu(void); +void send_isolation(void); void send_config(void); void send_cmd_log(void); void send_lag(void); diff --git a/test/test_httpd_tx.c b/test/test_httpd_tx.c index f64cf5fc..c7ccf09c 100644 --- a/test/test_httpd_tx.c +++ b/test/test_httpd_tx.c @@ -140,6 +140,7 @@ void send_vlan(uint16_t vlan) { (void)vlan; } void send_basic_info(void) { } void send_bandwidth(void) { } void send_storm(void) { } +void send_isolation(void) { } void send_eee(void) { } void send_l2(uint16_t idx) { (void)idx; } void l2_delete(uint16_t idx) { (void)idx; }