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.
This commit is contained in:
d00f authored and DrDoof committed 2026-10-04 22:11:19 +02:00
1 parent 957a5b0e23
commit 7c11142a04
6 files changed
+114 -1

No files matched your search

+75 -1
View File
@@ -2,7 +2,7 @@
var LANG={ var LANG={
en:{ en:{
nav_dash:"Dashboard",nav_ports:"Ports",nav_stp:"Spanning tree",nav_stats:"Statistics", 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", nav_bw:"Bandwidth",nav_system:"System",nav_fw:"Firmware",
hdr_dirty:"unsaved changes",hdr_dirty_t:"Running config differs from startup config", 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", 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?", 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_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", 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_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.", 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", 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:"vlan", icon:"M12 3v6M12 9l-7 5M12 9l7 5M5 14v5M19 14v5M3 21h4M17 21h4"},
{id:"l2", icon:"M4 5h16M4 12h16M4 19h10"}, {id:"l2", icon:"M4 5h16M4 12h16M4 19h10"},
{id:"mirror",icon:"M12 3v18M7 8l-4 4 4 4M17 8l4 4-4 4"}, {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:"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:"eee", icon:"M13 2L4 14h6l-1 8 9-12h-6z"},
{id:"bw", icon:"M4 18a8 8 0 0116 0M12 18l4-6"}, {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(){})})}}; 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<<S.physToLog[c-1];}
var cpu=isoBox(r,S.n+1);
if(cpu&&cpu.checked)m|=0x200;
return m;
}
$("isoapply").addEventListener("click",function(){
var cmds=[];
for(var r=1;r<=S.n;r++){
var cur=-1;
isoAllow.forEach(function(e){if(e.portNum===r)cur=parseInt(e.allow,16)});
if(isoMask(r)===cur)continue;
var cmd="isolate "+r;
for(var c=1;c<=S.n;c++){var b=isoBox(r,c);if(r===c||(b&&b.checked))cmd+=" "+c;}
var cpu=isoBox(r,S.n+1);
if(cpu&&cpu.checked)cmd+=" 0";
cmds.push(cmd);
}
delete $("isotbl").dataset.dirty;
if(!cmds.length){isoLoad().catch(function(){});return;}
postCmds(cmds).then(isoLoad).catch(function(){});
});
$("isoall").addEventListener("click",function(){
for(var r=1;r<=S.n;r++)for(var c=1;c<=S.n+1;c++){var b=isoBox(r,c);if(b)b.checked=true;}
isoTouch();
});
tabHooks.isolate={enter:function(){needPorts(function(){buildIso();isoLoad().catch(function(){})})}};
var HASHF=["spa","smac","dmac","sip","dip","sport","dport"]; var HASHF=["spa","smac","dmac","sip","dip","sport","dport"];
function buildLag(){ function buildLag(){
var w=$("lagwrap"); var w=$("lagwrap");
+12
View File
@@ -331,6 +331,18 @@ button.danger{background:var(--bad);color:#fff;border-color:transparent}
</div> </div>
</section> </section>
<section class="tab" id="tab-isolate">
<div class="card"><h2 data-i18n="iso_title">Port isolation</h2>
<p class="small mut" style="margin-bottom:10px" data-i18n="iso_intro">A frame entering a port may only leave through the ports ticked in its row.</p>
<div class="scrollx"><table class="t" id="isotbl" style="width:auto"><thead></thead><tbody></tbody></table></div>
<p class="small mut" style="margin-top:6px" data-i18n="iso_legend">The cell of the port itself is always allowed.</p>
<div style="display:flex;gap:12px;margin-top:10px">
<button class="ctl pri" id="isoapply" data-i18n="c_apply">Apply</button>
<button class="ctl" id="isoall" data-i18n="iso_all">Allow all</button>
</div>
</div>
</section>
<section class="tab" id="tab-lag"> <section class="tab" id="tab-lag">
<div id="lagwrap" class="grid2"></div> <div id="lagwrap" class="grid2"></div>
<p class="small mut" style="margin-top:10px" data-i18n="lag_note">A LAG needs at least one member to be saved in the startup config; applying an empty group clears it.</p> <p class="small mut" style="margin-top:10px" data-i18n="lag_note">A LAG needs at least one member to be saved in the startup config; applying an empty group clears it.</p>
+2
View File
@@ -1026,6 +1026,8 @@ void httpd_appcall(void)
l2_delete(short_parsed); l2_delete(short_parsed);
} else if (is_word(q, "/mirror.json")) { } else if (is_word(q, "/mirror.json")) {
send_mirror(); send_mirror();
} else if (is_word(q, "/isolation.json")) {
send_isolation();
} else if (is_word(q, "/mtu.json")) { } else if (is_word(q, "/mtu.json")) {
send_mtu(); send_mtu();
} else if (is_word(q, "/lag.json")) { } else if (is_word(q, "/lag.json")) {
+23
View File
@@ -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) void send_mtu(void)
{ {
dbg_string("send_mtu called\n"); dbg_string("send_mtu called\n");
+1
View File
@@ -18,6 +18,7 @@ void send_l2(uint16_t idx);
void l2_delete(uint16_t idx); void l2_delete(uint16_t idx);
void send_mirror(void); void send_mirror(void);
void send_mtu(void); void send_mtu(void);
void send_isolation(void);
void send_config(void); void send_config(void);
void send_cmd_log(void); void send_cmd_log(void);
void send_lag(void); void send_lag(void);
+1
View File
@@ -140,6 +140,7 @@ void send_vlan(uint16_t vlan) { (void)vlan; }
void send_basic_info(void) { } void send_basic_info(void) { }
void send_bandwidth(void) { } void send_bandwidth(void) { }
void send_storm(void) { } void send_storm(void) { }
void send_isolation(void) { }
void send_eee(void) { } void send_eee(void) { }
void send_l2(uint16_t idx) { (void)idx; } void send_l2(uint16_t idx) { (void)idx; }
void l2_delete(uint16_t idx) { (void)idx; } void l2_delete(uint16_t idx) { (void)idx; }