mirror of
https://github.com/logicog/RTLPlayground.git
synced 2026-10-29 17:54:14 +08:00
lacp: implement IEEE 802.3ad link aggregation
Dynamic link aggregation on top of the static LAGG, for RTL8372/8373: receive, selection and mux state machines (coupled mux, one aggregator per group), with "lacp show" to look inside. Each of the four trunk groups runs its own aggregator on the candidate ports assigned with "lag <n> lacp <ports>"; the engine starts with the first such group and stops with the last. LACPDUs reach the CPU through the reserved-multicast trap, so delivery needs no L2 entry and nothing written per VLAN or per PVID. The trap acts on the address before the VLAN lookup and before the ingress filters, so a LACPDU also arrives on a port that admits tagged frames only and on a port STP holds in blocking, neither of which a forwarded one reaches. A trapped frame carries no VLAN tag, so lacp_in() takes the offset of the PDU body from TRAP_RX_BODY(). The action is set with trap_rma_set(), which leaves the flags sharing that register alone, and "lacp off" puts the slow-protocols drop default back. doc/lacp.md and doc/link_aggregation.md record what was measured, including why the trap looked like a dead end at first. The web UI gets a Static/LACP mode per group on the LAG page with live status, and the startup-config validator knows the per-group syntax. A host-side sandbox for the state machines runs in the test harness together with the LAG hash check. It drives both forms of an incoming LACPDU, so the body offset of a trapped one is covered as well. The L2 table page iterated with the "next unicast" read method, so static multicast entries such as an IGMP group were invisible. It now iterates with "next address", skips L3 entries (their layout carries IP addresses where a MAC is expected), and reports the lowest member of a multicast entry's port mask, so such an entry shows up as a static entry on that port. A port taken out of LACP forgets its partner, so "lacp show" after "lacp off" no longer reports one. Applying the same candidate set to a group again, as the LAG page does on every Apply, leaves the trunk alone instead of tearing it down for a second or two. lacp_in() checks the frame length and ethertype before reading a LACPDU, so a runt or a foreign frame on the slow-protocols address cannot leave stale buffer bytes as a partner. The sandbox covers these three in T20-T22. On the LAG page, LACP mode with no ports checked sends "lag N lacp off", and the startup-config validator wants at least one port after "lacp".
This commit is contained in:
1 parent
b83b67d234
commit
c3b4cc893b
31 files changed
+1885
-23
No files matched your search
+44
-8
@@ -67,6 +67,9 @@ iso_title:"Port isolation",iso_from:"From port",iso_to:"To port",iso_all:"Allow
|
||||
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.",
|
||||
lacp_heading:"LACP (802.3ad)",lag_mode:"Mode",lag_static:"Static",lacp_neg:"negotiating",lacp_agg:"aggregator",
|
||||
lacp_members:"active members",lacp_hint:"LACP: select candidate ports and press Apply to start negotiation",
|
||||
lacp_actor:"Actor",lacp_partner:"Partner",lacp_rxstate:"RX state",lacp_rx:"LACPDUs received",lacp_psys:"Partner system",
|
||||
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_note:"Advertised and link-partner flags per speed: 100M, 1G, 2.5G. SFP ports do not support EEE.",
|
||||
@@ -808,7 +811,7 @@ var CONF_CMDS=[
|
||||
/^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+)+$/,
|
||||
/^lag\s+[1-4](\s+\d{1,2})+$/,/^lag\s+[1-4]\s+d$/,/^lag\s+[1-4]\s+lacp(\s+\d{1,2})+$/,/^lag\s+[1-4]\s+lacp\s+off$/,/^laghash\s+[1-4](\s+\w+)+$/,
|
||||
/^isolate\s+\d{1,2}(\s+(off|\d{1,2}))+$/,
|
||||
/^stp\s+(on|off)$/,/^stp\s+(prio|hello|maxage|fwd|txhold)\s+\d{1,2}$/,
|
||||
/^stp\s+version\s+(rstp|stp)$/,
|
||||
@@ -1814,6 +1817,15 @@ function buildLag(){
|
||||
for(var g=1;g<=4;g++)(function(g){
|
||||
var card=h("div",{class:"card"});
|
||||
card.appendChild(h("h2",{text:"LAG "+g}));
|
||||
var mr=h("div",{style:"display:flex;gap:10px;flex-wrap:wrap;align-items:center;margin-bottom:10px",class:"small"});
|
||||
mr.appendChild(h("span",{class:"mut",text:t("lag_mode")}));
|
||||
var ms=h("select",{class:"in",id:"lgm"+g});
|
||||
ms.appendChild(h("option",{value:"static",text:t("lag_static")}));
|
||||
ms.appendChild(h("option",{value:"lacp",text:"LACP"}));
|
||||
ms.addEventListener("change",function(){$("lgs"+g).textContent=ms.value==="lacp"?t("lacp_hint"):""});
|
||||
mr.appendChild(ms);
|
||||
card.appendChild(mr);
|
||||
card.appendChild(h("p",{class:"small mono",id:"lgs"+g,style:"margin-bottom:10px"}));
|
||||
var pr=h("div",{style:"display:flex;gap:10px;flex-wrap:wrap;margin-bottom:10px"});
|
||||
for(var p=1;p<=S.n;p++)pr.appendChild(h("label",null,[
|
||||
h("input",{type:"checkbox",id:"lg"+g+"p"+p}),document.createTextNode(" "+p+" "),
|
||||
@@ -1826,14 +1838,33 @@ function buildLag(){
|
||||
});
|
||||
card.appendChild(hr);
|
||||
card.appendChild(h("button",{class:"ctl pri",text:t("c_apply"),onclick:function(){lagApply(g)}}));
|
||||
card.addEventListener("change",function(){card.dataset.dirty="1"});
|
||||
w.appendChild(card);
|
||||
})(g);
|
||||
}
|
||||
var lacpCfg=[0,0,0,0,0];
|
||||
function lagLoad(){
|
||||
return getJSON("/lag.json").then(function(s){
|
||||
return getJSON("/lacp.json").then(function(c){
|
||||
var tb=$("lacptbl").tBodies[0];
|
||||
tb.innerHTML="";
|
||||
c.lags.forEach(function(lg,i){
|
||||
var g=i+1;
|
||||
lacpCfg[g]=parseInt(lg.cfg,16);
|
||||
if($("lgm"+g).closest(".card").dataset.dirty)return;
|
||||
$("lgm"+g).value=lacpCfg[g]?"lacp":"static";
|
||||
$("lgs"+g).textContent=lacpCfg[g]?"LACP: "+t("lacp_agg")+" "+(lg.aggValid?lg.agg:"("+t("lacp_neg")+")")+", "+t("lacp_members")+" 0x"+lg.members:"";
|
||||
});
|
||||
if(c.on)c.ports.forEach(function(p){
|
||||
if(p.lag===255)return;
|
||||
var tr=tb.insertRow();
|
||||
[p.p,p.lag+1,p.a,p.pt,p.rs,parseInt(p.rx,16),p.psys].forEach(function(v){tr.insertCell().textContent=v});
|
||||
});
|
||||
return getJSON("/lag.json");
|
||||
}).then(function(s){
|
||||
s.forEach(function(l){
|
||||
var g=l.lagNum+1;
|
||||
var members=parseInt(l.members,2);
|
||||
if($("lgm"+g).closest(".card").dataset.dirty)return;
|
||||
var members=lacpCfg[g]||parseInt(l.members,2);
|
||||
for(var p=1;p<=S.n;p++)
|
||||
$("lg"+g+"p"+p).checked=!!((members>>S.physToLog[p-1])&1);
|
||||
var hash=parseInt(l.hash,16);
|
||||
@@ -1842,21 +1873,25 @@ function lagLoad(){
|
||||
});
|
||||
}
|
||||
function lagApply(g){
|
||||
var cmd="lag "+g,n=0;
|
||||
var lacp=$("lgm"+g).value==="lacp",cmd="lag "+g+(lacp?" lacp":""),n=0,pre=[];
|
||||
delete $("lgm"+g).closest(".card").dataset.dirty;
|
||||
for(var p=1;p<=S.n;p++)if($("lg"+g+"p"+p).checked){cmd+=" "+p;n++;}
|
||||
if(!n){
|
||||
if(!lacp&&lacpCfg[g])pre.push("lag "+g+" lacp off");
|
||||
if(lacp&&!n)cmd="lag "+g+" lacp off";
|
||||
if(!n&&!lacp){
|
||||
confirmModal(t("lag_clear_q",{n:g}),t("lag_clear_d"),function(){
|
||||
postCmd("lag "+g+" d").then(lagLoad).catch(function(){});
|
||||
postCmds(pre.concat(["lag "+g+" d"])).then(lagLoad).catch(function(){});
|
||||
});
|
||||
return;
|
||||
}
|
||||
var hcmd="laghash "+g,nh=0;
|
||||
HASHF.forEach(function(f){if($("lg"+g+"h"+f).checked){hcmd+=" "+f;nh++;}});
|
||||
var cmds=[cmd];
|
||||
var cmds=pre.concat([cmd]);
|
||||
if(nh)cmds.push(hcmd);
|
||||
postCmds(cmds).then(lagLoad).catch(function(){});
|
||||
}
|
||||
tabHooks.lag={enter:function(){needPorts(function(){buildLag();lagLoad().catch(function(){})})}};
|
||||
var lagPoller=new Poller(function(){return lagLoad().catch(function(){})},3000);
|
||||
tabHooks.lag={enter:function(){needPorts(function(){buildLag();lagPoller.start()})},leave:function(){lagPoller.stop()}};
|
||||
|
||||
|
||||
function lldpLoad(){
|
||||
@@ -2245,6 +2280,7 @@ function mergeConf(base,texts){
|
||||
else drop(/^eee /);
|
||||
conf.push(line);return;
|
||||
}
|
||||
if((m=line.match(/^lag (\d) lacp off$/))){drop(new RegExp("^lag "+m[1]+" lacp( |$)"));return;}
|
||||
if(line==="mirror off"){drop(/^mirror /);return;}
|
||||
if(!isConfCmd(line))return;
|
||||
if((m=line.match(/^ingress (.+)$/))){
|
||||
|
||||
Reference in new issue
Block a user