From aff6ac0923bf7a3c82d4fbe371d4617ac24e4cc7 Mon Sep 17 00:00:00 2001 From: DrDoof <8052722+DrDoof@users.noreply.github.com> Date: Wed, 23 Sep 2026 21:25:01 +0200 Subject: [PATCH] stp: full RSTP and a reworked Spanning Tree page * stp: select the root port by cost and block redundant links * stp: move the protocol to BANK4 and keep the command line in BANK2 * stp: implement the RSTP state machines of 802.1D-2004 clause 17 * stp: counters, more settings and a fuller Spanning Tree page * stp: make a link aggregation group one port of the RSTP tree * web: serve the Spanning Tree JSON from BANK4 as well Kept in BANK1, the larger Spanning Tree page would leave under 1 kB there for the rest of the web server and every page still to come, so send_stp() and send_stp_counters() move to httpd/page_stp.c in BANK4 with the protocol, as banked calls, with their own copies of the few output helpers and of the JSON header, so nothing in BANK4 calls into BANK1 directly or hands it a pointer into its code space. BANK4 takes the free flash between BANK3 and the web files. KP_9000_6XHML_X2: BANK1 44336, BANK2 36969, BANK3 11942, BANK4 20965 bytes of 49152. --- Makefile | 8 +- doc/stp.md | 192 +++- html/app.js | 54 +- html/index.html | 9 +- httpd/httpd.c | 2 + httpd/page_impl.c | 138 +-- httpd/page_impl.h | 3 +- httpd/page_stp.c | 195 ++++ rtl837x_stp.c | 1355 ++++++++++++++++---------- rtl837x_stp.h | 38 +- rtl837x_stp_cli.c | 228 +++++ test/env_tables.c | 6 + test/stp/Makefile | 21 + test/stp/shim/machine.h | 12 + test/stp/shim/rtl837x_common.h | 50 + test/stp/shim/rtl837x_port.h | 11 + test/stp/shim/rtl837x_regs.h | 9 + test/stp/shim/rtl837x_sfr.h | 6 + test/stp/shim/uip.h | 8 + test/stp/stp_sim.c | 1633 ++++++++++++++++++++++++++++++++ 20 files changed, 3282 insertions(+), 696 deletions(-) create mode 100644 httpd/page_stp.c create mode 100644 rtl837x_stp_cli.c create mode 100644 test/stp/Makefile create mode 100644 test/stp/shim/machine.h create mode 100644 test/stp/shim/rtl837x_common.h create mode 100644 test/stp/shim/rtl837x_port.h create mode 100644 test/stp/shim/rtl837x_regs.h create mode 100644 test/stp/shim/rtl837x_sfr.h create mode 100644 test/stp/shim/uip.h create mode 100644 test/stp/stp_sim.c diff --git a/Makefile b/Makefile index 46568abe..af14d987 100644 --- a/Makefile +++ b/Makefile @@ -150,10 +150,12 @@ SRCS += \ rtl837x_lldp.c \ rtl837x_storm.c \ rtl837x_lacp.c \ - rtl837x_trap.c + rtl837x_trap.c \ + rtl837x_stp_cli.c SRCS += \ httpd/httpd.c \ - httpd/page_impl.c + httpd/page_impl.c \ + httpd/page_stp.c SRCS += \ uip/timer.c \ uip/uip.c \ @@ -246,7 +248,7 @@ $(BUILDDIR)/%.rel: %.asm $(CCFLAGS_STAMP) | create_build_dir # mv -f $(addprefix $(basename $^), .lst .rel .sym) . $(BUILDDIR)/rtlplayground.ihx: $(OBJS) $(BUILDDIR)/crtbank.rel $(BUILDDIR)/crc16.rel - $(CC) $(CC_FLAGS) --xram-size 49151 -Wl-bHOME=0x00000 -Wl-bBANK1=0x14000 -Wl-bBANK2=0x24000 -Wl-bBANK3=0x34000 -Wl-r -o $@ $^ + $(CC) $(CC_FLAGS) --xram-size 49151 -Wl-bHOME=0x00000 -Wl-bBANK1=0x14000 -Wl-bBANK2=0x24000 -Wl-bBANK3=0x34000 -Wl-bBANK4=0x44000 -Wl-r -o $@ $^ $(BUILDDIR)/rtlplayground.img: $(BUILDDIR)/rtlplayground.ihx objcopy --input-target=ihex -O binary $< $@ diff --git a/doc/stp.md b/doc/stp.md index 96368535..dd5ae002 100644 --- a/doc/stp.md +++ b/doc/stp.md @@ -2,9 +2,11 @@ The switch can take part in a spanning tree (IEEE 802.1D / 802.1w) so that redundant links between bridges are blocked instead of forming a loop. The -implementation elects a root bridge from the BPDUs it receives, promotes ports -to forwarding once their listen period expires, ages the root out when it goes -silent, and blocks a port on which it sees its own BPDU. +implementation elects a root bridge from the BPDUs it receives, picks the +cheapest path to it as the root port, blocks ports on which a better bridge is +already designated, promotes ports to forwarding once their listen period +expires, drops information a neighbour stopped sending, and blocks a port on +which it sees its own BPDU. STP can be enabled and controlled via the web interface or the command line, as follows: @@ -64,32 +66,6 @@ LACPDUs, delivered by forward, stopped arriving on port 3, and BPDUs sent into the same port, delivered by trap, kept arriving. A port that admits tagged frames only therefore takes part in STP. -## Link aggregation - -A LAG (Link Aggregation Group) is handled like an additional port by the STP -protocol, with its own timers and states. The switch devices support up to 4 -LAGs, which are shown alongside the physical ports in the STP status. A group -carries its own path cost, priority, edge and guard settings, and gets its own -row on the Spanning Tree page. - -``` -stp lag 1 cost 10000 # the group decides, not its members -stp lag 1 edge off -``` - -LAGs are configured via the `lag` command. Once a port is a member of a LAG it -can no longer be configured individually for STP, so `stp port ` on a member -tells you which group to configure instead. Membership is re-read from the -aggregation registers once a second, so a group changing under LACP is picked -up without any coordination between the two. - -The switch hardware does not handle STP state for a LAG as a whole. State -changes for the members have to be done by the firmware, updating every member -port, which is possible using a single register write. BPDUs go out through the -lowest member and carry the group's own port id. Losing one member of a live -LAG is not a topology change; the logical port only goes down with its last -link. - Port states live in `RTL837X_MSTP_STATES (0x5310)`, two bits per port: `00` disabled, `01` blocking, `10` learning, `11` forwarding. A port in blocking forwards nothing between ports, but it still sends what the CPU @@ -109,19 +85,80 @@ encodes. All configured values are in seconds: | `stp fwd ` | 15 | 4–30 | | `stp txhold ` | 6 | 1–10 | -A port entering the tree spends `fwd` seconds in blocking before it forwards -(an edge port skips the wait). Root information is discarded after `maxage` -seconds without a BPDU, and the switch then reclaims the root role. +802.1D also requires `2 * (fwd - 1) >= maxage >= 2 * (hello + 1)`. A +command typed on the console or sent by the web page that would break it is +refused and the value stays as it was, so change the values in an order that +keeps the rule at every step (for a larger max age raise the forward delay +first). The web page shows the error, and a refused command is not recorded +in the command history, so saving the configuration does not pick it up. Lines of the saved configuration are taken one by one whatever their +order, and `stp on` checks the result: if the rule is broken it warns and +starts with 2/20/15 instead. + +The values above are what the switch uses while it is the root. Otherwise it +takes max age and forward delay from the BPDUs on its root port, as 802.1D +requires, and passes them on in its own BPDUs with the message age one second +higher; the hello time stays its own. + +A port entering the tree discards for one forward delay, then learns +addresses without forwarding for a second one, and only then forwards (an +edge port skips the wait). In RSTP mode on a point-to-point link +(`p2p auto` or `on`) it can go faster: a designated port that is still +discarding sets the proposal flag, and forwards as soon as the bridge on the +other side answers with an agreement. When the root port receives a proposal, +the switch first puts its other non-edge designated ports back to discarding +(they propose in turn), then forwards the root port and answers with an +agreement; this sync happens once per root port, later proposals are only +answered. An alternate or backup port that receives a proposal does the same +sync once for the information it holds and answers with an agreement in a +BPDU with the alternate role, so the bridge on the other side does not have +to wait out its timers; it stays discarding itself. An agreement is accepted +from a root or an alternate port on the other side. A new root port forwards at once, after every port that was root within +the last forward delay has been put back to discarding; such a recent root +port stays closed, even to an agreement, until that forward delay is over. +A new root port that was a backup port within the last two hello times does +not take the fast path and waits for its timers. + +A designated port that hears worse information from a bridge that still +claims to be designated and learning or forwarding on the same link has a +dispute: usually one direction of the link is lost and the other bridge does +not hear this switch's BPDUs. The port goes back to discarding, and every +further such BPDU restarts its listen period, so it does not forward while +the dispute lasts. + +With `stp version rstp` each port still talks 802.1D to a neighbour that +does: once a Config BPDU or TCN arrives and the migrate time (3 s since the +port came up or last switched) is over, the port sends Config BPDUs, uses +the 802.1D TC window and reports changes with TCN if it is the root port. An +RST BPDU after the migrate time switches it back, and so does a link that +comes up again or `stp port mcheck`. Any worse information heard on a designated port, disputed or +not, is answered with a BPDU at once instead of at the next hello, so the +other bridge learns sooner that it is not the designated one. Information heard on a port stays +valid for three of the sender's hello times; a BPDU whose message age has +reached its max age is not used at all. When the root port's information +expires the switch moves to the next best port, or reclaims the root role if +there is none. + +Among ports offering the same root, the lowest root path cost wins, then the +designated bridge, the designated port ID, and finally this switch's own port +ID. A worse BPDU from a different bridge does not replace what a port holds; +a worse one from the same designated bridge and port does. ## Topology changes -A change on a local non-edge port (the link coming or going, a port promoted -to forwarding) flushes the addresses learned on it and sets the TC flag in -our BPDUs for `maxage + fwd` seconds. A TC flag received in a BPDU is passed -on: the switch flushes the other non-edge ports once and keeps the flag in -its own BPDUs until one hello after the last flagged frame, so the -notification crosses the switch instead of dying at it. A legacy TCN is -acknowledged with TCA and then treated like a local change. +A topology change is detected when a non-edge port enters forwarding. That +port, and every other forwarding non-edge port, then carries the TC flag in +its BPDUs for hello time plus one second (max age plus forward delay with +`stp version stp`), and each of the other ports flushes the addresses it +learned. The root port takes part too: while its TC window runs it sends +BPDUs toward the root, so the change reaches the rest of the network. With +`stp version stp` the root port sends TCN instead, until a Config BPDU with +TCA comes back. + +A TC flag received on a root or designated port is passed on the same way to +the other ports, not back to the port it came in on. A legacy TCN is +acknowledged with TCA and then treated like a received TC. A port that turns +alternate, loses its link, or is blocked for a loop flushes its own addresses +without raising a topology change, and an edge port never raises one. ## Bridge settings @@ -129,6 +166,8 @@ acknowledged with TCA and then treated like a local change. stp prio <0-15> # bridge priority = n * 4096, default 8 (32768) stp version rstp|stp # RST BPDUs (default) or legacy Config BPDUs stp hello|maxage|fwd|txhold +stp pathcost long|short # automatic port costs, 802.1D-2004 (default) or 802.1D-1998 +stp bpdu flood|filter # BPDUs while STP is off: pass them on (default) or drop them ``` The bridge with the lowest priority wins the root election; ties are broken by @@ -136,6 +175,17 @@ the MAC address. If you do not want this switch to become the root of an existing network, give it a worse priority than the current root — `stp prio 15` (61440) is the usual "never me" value. +A port without a configured cost takes it from its link speed. The long +method uses the 802.1D-2004 values (20000 for 1G, 2000 for 10G); the short +one the 802.1D-1998 table for bridges that still use 16-bit costs (100 for +10M, 19 for 100M, 4 for 1G, 2 for 10G, and 3 for 2.5G and 5G, which that +table does not list). All bridges in a network should use the same method. + +`stp bpdu filter` keeps BPDUs from being passed between ports while STP is +off, instead of the default flooding that lets a surrounding spanning tree +run through the switch. It takes effect at once when STP is off, otherwise +when STP is turned off. + ## Per-port settings ``` @@ -146,6 +196,7 @@ stp port <1-9> prio <0-240> # port priority, steps of 16 stp port <1-9> guard none|bpdu|root stp port <1-9> filter on|off # neither send nor accept BPDUs stp port <1-9> p2p auto|on|off +stp port <1-9> mcheck # try RSTP again on a port that fell back to STP ``` **edge** — an edge port forwards immediately and does not trigger a @@ -162,26 +213,67 @@ claims a better priority. on the far side reacts badly to them (some unmanaged switches with loop prevention cut the link) but you still want STP on the rest of the ports. +## Link aggregation + +A LAG (Link Aggregation Group) is one port of the tree, with its own roles, +timers and states. The switch supports up to 4 LAGs; each one takes the place +of its member ports in the STP status and gets its own row on the Spanning +Tree page. A group carries its own path cost, priority, edge, guard and +point-to-point settings, and takes the same commands as a port: + +``` +stp lag <1-4> on|off|mcheck|edge|cost|prio|guard|filter|p2p ... +stp lag 1 cost 10000 # the group decides, not its members +``` + +LAGs are configured with the `lag` command. Once a port is a member of a LAG +it can no longer be configured on its own for STP, and `stp port ` on a +member names the group to configure instead. Membership is read from the +aggregation registers once a second, so a group changing under LACP is picked +up without any coordination between the two; a port that joins or leaves a +group, and the group itself, start over from discarding. The automatic path +cost of a group is the one of its lowest member that has a link. + +The hardware keeps no STP state for a LAG as a whole, so every state change is +written to all member ports, in a single register write. BPDUs go out through +the lowest member that has a link and carry the group's own port id, and a +BPDU received on any member belongs to the group. Losing one member of a live +LAG is not a topology change; the group only goes down with its last link. + ## Status The Spanning Tree page shows the elected root (priority and MAC), the path cost -to it, the root port, the topology-change counter and, per port, the live state -read from the ASIC together with the configured options. The same data is -available as JSON: +to it, the root port, the topology-change counter and the time since it last +moved, the max age and forward +delay in use (the root's, when this switch is not the root) and, per port, the +live state read from the ASIC, or "No link", together with the configured +options. For a port taking part in the tree it also shows the designated +bridge, port and cost, and whether it talks RSTP or 802.1D to its neighbour; +the Check button runs `stp port mcheck`. The same data is available as +JSON: ``` GET /stp.json ``` -The `stp status` command prints the same view on the serial console. +The Counters section of the same page (or `/stpcnt.json`) shows per port the +BPDUs received and sent, those of them that carried the topology change flag +(TCNs included), and the time since the last BPDU arrived. The counters start +from zero with `stp on`, and `stp clear` resets them; the topology change +counter is not reset. + +The `stp status` command prints the same view on the serial console. A port +is reported with one of the roles Root, Designated, Alternate, Backup or +Disabled; Disabled covers a port with STP turned off on it, one shut down by +BPDU guard and one without link. + +BPDUs shorter than their type allows, judged by the 802.3 length field, are +dropped: 7 bytes for a TCN, 38 for a Config BPDU and 39 for an RST BPDU, +LLC header included. ## Limitations * One spanning-tree instance; no MSTP, no per-VLAN trees. -* No proposal/agreement handshake — an RST-capable neighbour will still - converge, but through the timers rather than the fast transition. -* Port roles are approximated: the root port and designated ports are - distinguished, alternate/backup are not. -* Topology changes propagate away from the root only: nothing is announced - on the root port (no TCN and no BPDUs at all), so bridges upstream rely on - their own detection. +* A backup port, one that hears the BPDUs of another port of this switch, + is blocked and reported as Backup, but keeps sending BPDUs: loop detection + on that segment relies on them. diff --git a/html/app.js b/html/app.js index 9e8432fa..7b4cba00 100644 --- a/html/app.js +++ b/html/app.js @@ -41,6 +41,11 @@ stp_en_d:"Ports start blocked and take up to twice the forward delay to reach fo 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", +stp_pcs:"Path cost",stp_pcs_long:"Long (32 bit)",stp_pcs_short:"Short (16 bit)", +stp_bh:"BPDUs while disabled",stp_bh_flood:"Flood",stp_bh_filter:"Filter", +stp_r4:"Backup",stp_times:"max age / forward delay",stp_proto:"Protocol",stp_nolink:"no link",stp_lasttc:"last", +stp_mcheck:"Check",stp_mcheck_t:"Send RST BPDUs again to find out whether the neighbour still needs 802.1D", +stp_cnt:"Counters",stp_crx:"BPDUs received",stp_ctx:"BPDUs sent",stp_ctrx:"TC received",stp_cttx:"TC sent",stp_cage:"Last BPDU [s]", 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", @@ -834,6 +839,8 @@ var CONF_CMDS=[ /^isolate\s+\d+(\s+(off|\d+))+$/, /^stp\s+(on|off)$/,/^stp\s+(prio|hello|maxage|fwd|txhold)\s+\d+$/, /^stp\s+version\s+(rstp|stp)$/, + /^stp\s+pathcost\s+(long|short)$/, + /^stp\s+bpdu\s+(filter|flood)$/, /^stp\s+(port\s+\d+|lag\s+[1-4])\s+(on|off)$/, /^stp\s+(port\s+\d+|lag\s+[1-4])\s+edge\s+(on|off|auto)$/, /^stp\s+(port\s+\d+|lag\s+[1-4])\s+cost\s+\d+$/, @@ -1223,12 +1230,14 @@ function fmtBridge(hex){ if(!hex||hex.length<16)return"-"; return parseInt(hex.slice(0,4),16)+" / "+hex.slice(4).replace(/(..)(?=.)/g,"$1:"); } +function stpEnt(pt){pt.lag=pt.p>100?pt.p-100:0} function stpKey(pt){return pt.lag?"L"+pt.lag:String(pt.p)} -function stpName(pt){return pt.lag?"LAG"+pt.lag+" ("+maskToPorts(pt.mbr).join(",")+")":String(pt.p)} +function stpName(pt){return pt.lag?"LAG"+pt.lag:String(pt.p)} +function stpDhms(x){return Math.floor(x/86400)+"d "+Math.floor(x/3600)%24+"h "+Math.floor(x/60)%60+"m "+x%60+"s"} function stpPre(k){return k.charAt(0)==="L"?"stp lag "+k.slice(1)+" ":"stp port "+k+" "} function stpBuild(ports){ - var tb=$("stpcfg").tBodies[0],st=$("stpstat").tBodies[0]; - tb.innerHTML="";st.innerHTML=""; + var tb=$("stpcfg").tBodies[0],st=$("stpstat").tBodies[0],ct=$("stpcnt").tBodies[0]; + tb.innerHTML="";st.innerHTML="";ct.innerHTML=""; ports.forEach(function(pt){ var p=stpKey(pt); var tr=tb.insertRow(); @@ -1246,7 +1255,12 @@ function stpBuild(ports){ tr.insertCell().appendChild(h("button",{class:"ctl",text:t("c_apply"),onclick:function(){stpApplyPort(p)}})); var sr=st.insertRow(); sr.insertCell().textContent=stpName(pt); - ["ro","st","db","dp","dc","oe","op"].forEach(function(k){sr.insertCell().id="st"+k+p}); + ["ro","st","db","dp","dc","oe","op","pv"].forEach(function(k){sr.insertCell().id="st"+k+p}); + sr.insertCell().appendChild(h("button",{class:"ctl",id:"stmc"+p,text:t("stp_mcheck"),title:t("stp_mcheck_t"), + onclick:function(){postCmd(stpPre(p)+"mcheck").then(stpLoad).catch(function(){})}})); + var cr=ct.insertRow(); + cr.insertCell().textContent=stpName(pt); + ["rx","tx","trx","ttx","age"].forEach(function(k){cr.insertCell().id="sc"+k+p}); }); } function stpPortVals(pt){ @@ -1260,10 +1274,21 @@ function stpPortVals(pt){ p2p:["auto","on","off"][pt.p2]||"auto", }; } +function stpCnt(c){ + c.ports.forEach(function(pt){ + stpEnt(pt); + var p=stpKey(pt); + if(!$("scrx"+p))return; + var v=[0,1,2,3].map(function(k){return parseInt(pt.c.slice(8*k,8*k+8),16)}),age=parseInt(pt.c.slice(32,36),16); + ["rx","tx","trx","ttx"].forEach(function(k,i){$("sc"+k+p).textContent=v[i]}); + $("scage"+p).textContent=!v[0]?"-":(age===0xffff?">":"")+Math.floor(age/c.hz); + }); +} function stpLoad(){ return getJSON("/stp.json").then(function(s){ - s.ports.sort(function(a,b){return((a.lag||0)-(b.lag||0))||(a.p-b.p)}); - var sig=s.ports.map(function(pt){return stpKey(pt)+":"+(pt.mbr||0)}).join(); + s.ports.forEach(stpEnt); + s.ports.sort(function(a,b){return(a.lag-b.lag)||(a.p-b.p)}); + var sig=s.ports.map(stpKey).join(); if(sig!==stpSig){stpSig=sig;stpBuild(s.ports);} stpCur=s; var en=$("stpen"); @@ -1271,16 +1296,19 @@ function stpLoad(){ var msg; if(!s.on)msg=t("stp_off_msg"); else{ - var me=(s.prio*4096).toString(16).padStart(4,"0")+s.myMac; + var me=(s.prio*4096).toString(16).padStart(4,"0")+s.myMac,tc=parseInt(s.tc,16),rp="-"; + s.ports.forEach(function(pt){if(pt.role===1)rp=stpName(pt)}); msg=t("stp_bridge")+" "+fmtBridge(me)+(s.weRoot ?" ("+t("stp_root_self")+")" - :"; "+t("stp_root")+" "+fmtBridge(s.rootPrio+s.rootMac)+" "+t("stp_via")+" "+s.rootPort+", "+t("stp_cost")+" "+parseInt(s.cost,16)) - +"; "+t("stp_tc")+": "+parseInt(s.tc,16); + :"; "+t("stp_root")+" "+fmtBridge(s.rootPrio+s.rootMac)+" "+t("stp_via")+" "+rp+", "+t("stp_cost")+" "+parseInt(s.cost,16)) + +"; "+t("stp_tc")+": "+tc+(tc?" ("+t("stp_lasttc")+" "+stpDhms(parseInt(s.tcs,16))+")":"") + +"; "+t("stp_times")+" "+s.rMaxage+" / "+s.rFwd+" s"; } $("stpids").textContent=msg; var bc=$("stpbridge"); if(!bc.dataset.dirty){ $("stpver").value=s.rstp?"rstp":"stp"; + $("stppcs").value=s.pcs?"short":"long";$("stpbh").value=s.bh?"filter":"flood"; $("stpprio").value=String(s.prio); $("stphello").value=s.hello;$("stpmaxage").value=s.maxage; $("stpfwd").value=s.fwd;$("stptxhold").value=s.txhold; @@ -1289,18 +1317,21 @@ function stpLoad(){ var p=stpKey(pt); var trip=(pt.f&STP_PF.TRIP)?" "+badge(t("stp_trip"),"bad"):""; $("stro"+p).textContent=s.on&&pt.role?t("stp_r"+pt.role):"-"; - $("stst"+p).innerHTML=s.on?badge(t("stp_s"+pt.st),pt.st===3?"ok":"")+trip:"-"; + $("stst"+p).innerHTML=s.on?(pt.lk?badge(t("stp_s"+pt.st),pt.st===3?"ok":""):badge(t("stp_nolink")))+trip:"-"; $("stdb"+p).textContent=s.on?fmtBridge(pt.db):"-"; $("stdp"+p).textContent=s.on?parseInt(pt.dp.slice(0,2),16)+"."+parseInt(pt.dp.slice(2),16):"-"; $("stdc"+p).textContent=s.on?parseInt(pt.dc,16):"-"; $("stoe"+p).textContent=s.on?t((pt.f&STP_PF.OPEREDGE)?"c_yes":"c_no"):"-"; $("stop"+p).textContent=s.on?t(pt.p2===2?"c_no":"c_yes"):"-"; + $("stpv"+p).textContent=s.on&&pt.role?(s.rstp&&!pt.lg?"RSTP":"STP"):"-"; + $("stmc"+p).disabled=!(s.on&&pt.role&&s.rstp); var row=$("sten"+p).closest("tr"); if(row.dataset.dirty)return; var v=stpPortVals(pt); $("sten"+p).checked=v.en;$("sted"+p).value=v.edge;$("stco"+p).value=v.cost; $("stpr"+p).value=v.prio;$("stgu"+p).value=v.guard;$("stfi"+p).value=v.filter;$("stpp"+p).value=v.p2p; }); + if($("stpcntd").open)return getJSON("/stpcnt.json").then(stpCnt); }).catch(function(){}); } function stpApplyPort(p){ @@ -1328,6 +1359,8 @@ $("stpbapply").addEventListener("click",function(){ var ver=$("stpver").value,prio=$("stpprio").value; if(ver!==(s.rstp?"rstp":"stp"))cmds.push("stp version "+ver); if(prio!==String(s.prio))cmds.push("stp prio "+prio); + if($("stppcs").value!==(s.pcs?"short":"long"))cmds.push("stp pathcost "+$("stppcs").value); + if($("stpbh").value!==(s.bh?"filter":"flood"))cmds.push("stp bpdu "+$("stpbh").value); [["stphello","hello"],["stpmaxage","maxage"],["stpfwd","fwd"],["stptxhold","txhold"]].forEach(function(f){ var v=$(f[0]).value; if(String(v)!==String(s[f[1]]))cmds.push("stp "+f[1]+" "+v); @@ -1346,6 +1379,7 @@ $("stpen").addEventListener("change",function(){ var sel=$("stpprio"); for(var i=0;i<16;i++)sel.appendChild(h("option",{value:String(i),text:i+" ("+(i*4096)+")"})); })(); +$("stpcntd").addEventListener("toggle",function(){if(this.open)stpLoad()}); var stpPoller=new Poller(stpLoad,3000); tabHooks.stp={enter:function(){stpPoller.start()},leave:function(){stpPoller.stop()}}; diff --git a/html/index.html b/html/index.html index 5695f819..d866ed37 100644 --- a/html/index.html +++ b/html/index.html @@ -264,6 +264,8 @@ button.danger{background:var(--bad);color:#fff;border-color:transparent}

-

+ + @@ -281,9 +283,14 @@ button.danger{background:var(--bad);color:#fff;border-color:transparent}

Port status

- +
PortRoleStateDesignated bridgeDesignated portDesignated costOper. edgeOper. P2P
PortRoleStateDesignated bridgeDesignated portDesignated costOper. edgeOper. P2PProtocol
+

Counters

+
+ +
PortBPDUs receivedBPDUs sentTC receivedTC sentLast BPDU [s]
+
diff --git a/httpd/httpd.c b/httpd/httpd.c index 5128cabb..88034fab 100644 --- a/httpd/httpd.c +++ b/httpd/httpd.c @@ -1055,6 +1055,8 @@ void httpd_appcall(void) send_lldp(); } else if (is_word(q, "/lacp.json")) { send_lacp(); + } else if (is_word(q, "/stpcnt.json")) { + send_stp_counters(); } 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 7f107b7d..7b416fce 100644 --- a/httpd/page_impl.c +++ b/httpd/page_impl.c @@ -600,143 +600,6 @@ void send_lag(void) } -static __xdata uint32_t pi_u32; -static __xdata uint8_t pi_prio, pi_ext; -static __xdata uint8_t * __xdata pi_mac; - - -static void u32hex_html(void) -{ - __xdata uint8_t *b = (__xdata uint8_t *)&pi_u32; - byte_to_html(b[3]); - byte_to_html(b[2]); - byte_to_html(b[1]); - byte_to_html(b[0]); -} - - -static void bridge_to_html(void) -{ - byte_to_html(pi_prio); - byte_to_html(pi_ext); - for (uint8_t i = 0; i < 6; i++) - byte_to_html(pi_mac[i]); -} - -void send_stp(void) -{ - uint8_t i, j, st, dsg; - - dbg_string("send_stp called\n"); - slen = strtox(outbuf, HTTP_RESPONCE_JSON); - - slen += strtox(outbuf + slen, "{\"on\":"); - bool_to_html(stp_enabled); - slen += strtox(outbuf + slen, ",\"rstp\":"); - bool_to_html(stp_rstp); - slen += strtox(outbuf + slen, ",\"prio\":"); - itoa_html(stp_prio >> 4); - slen += strtox(outbuf + slen, ",\"hello\":"); - itoa_html(stp_hello_s); - slen += strtox(outbuf + slen, ",\"maxage\":"); - itoa_html(stp_maxage_s); - slen += strtox(outbuf + slen, ",\"fwd\":"); - itoa_html(stp_fwddelay_s); - slen += strtox(outbuf + slen, ",\"txhold\":"); - itoa_html(stp_txhold); - slen += strtox(outbuf + slen, ",\"rootPrio\":\""); - byte_to_html(root_bridge.prio); - byte_to_html(root_bridge.ext); - slen += strtox(outbuf + slen, "\",\"rootMac\":\""); - for (j = 0; j < 6; j++) - byte_to_html(root_bridge.mac[j]); - slen += strtox(outbuf + slen, "\",\"myMac\":\""); - for (j = 0; j < 6; j++) - byte_to_html(uip_ethaddr.addr[j]); - slen += strtox(outbuf + slen, "\",\"cost\":\""); - byte_to_html(root_bridge_cost >> 24); - byte_to_html(root_bridge_cost >> 16); - byte_to_html(root_bridge_cost >> 8); - byte_to_html(root_bridge_cost); - slen += strtox(outbuf + slen, "\",\"weRoot\":"); - bool_to_html(stp_root_port == 0xff ? 1 : 0); - slen += strtox(outbuf + slen, ",\"rootPort\":"); - j = stp_root_port; - if (j != 0xff && j >= STP_LAG_BASE) { - j = 0; - while (j < STP_LAG_BASE && !((stp_lag_mask[stp_root_port - STP_LAG_BASE] >> j) & 1)) - j++; - if (j >= STP_LAG_BASE) - j = 0; - } - itoa_html(j == 0xff ? 0 : machine.log_to_phys_port[j]); - slen += strtox(outbuf + slen, ",\"tc\":\""); - byte_to_html(stp_tc_count >> 8); - byte_to_html(stp_tc_count); - slen += strtox(outbuf + slen, "\",\"ports\":["); - reg_read_m(RTL837X_MSTP_STATES); - for (i = 0; i < STP_ENTITIES; i++) { - if (i < STP_LAG_BASE && !MACHINE_PORT_VALID(i)) - continue; - if (i < STP_LAG_BASE && stp_ent_of[i] != i) - continue; - if (i >= STP_LAG_BASE && !stp_lag_mask[i - STP_LAG_BASE]) - continue; - slen += strtox(outbuf + slen, "{\"p\":"); - itoa_html(i < STP_LAG_BASE ? machine.log_to_phys_port[i] : 0); - slen += strtox(outbuf + slen, ",\"lag\":"); - itoa_html(i < STP_LAG_BASE ? 0 : i - STP_LAG_BASE + 1); - slen += strtox(outbuf + slen, ",\"mbr\":"); - itoa16_html(i < STP_LAG_BASE ? 0 : stp_lag_mask[i - STP_LAG_BASE]); - slen += strtox(outbuf + slen, ",\"st\":"); - j = i; - if (i >= STP_LAG_BASE) { - j = 0; - while (j < STP_LAG_BASE && !((stp_lag_mask[i - STP_LAG_BASE] >> j) & 1)) - j++; - if (j >= STP_LAG_BASE) - j = 0; - } - st = (sfr_data[3 - (j >> 2)] >> ((j << 1) & 0x7)) & 0x3; - itoa_html(st); - slen += strtox(outbuf + slen, ",\"role\":"); - if (!(stp_pflags[i] & STP_PF_ENABLED) || (stp_pflags[i] & STP_PF_TRIPPED)) - itoa_html(0); - else if (i == stp_root_port) - itoa_html(1); - else if (st == 3) - itoa_html(2); - else - itoa_html(3); - slen += strtox(outbuf + slen, ",\"f\":"); - itoa_html(stp_pflags[i]); - slen += strtox(outbuf + slen, ",\"pc\":\""); - pi_u32 = stp_pcost[i]; u32hex_html(); - slen += strtox(outbuf + slen, "\",\"prio\":"); - itoa_html(stp_pprio[i]); - slen += strtox(outbuf + slen, ",\"p2\":"); - itoa_html(stp_pp2p[i]); - dsg = stp_dpid[i] && stp_bpdu_age[i] < (uint16_t)stp_maxage_s * STP_HZ; - slen += strtox(outbuf + slen, ",\"db\":\""); - if (dsg) { - pi_prio = stp_dbridge[i].prio; pi_ext = stp_dbridge[i].ext; - pi_mac = stp_dbridge[i].mac; - } else { - pi_prio = stp_prio; pi_ext = 0; - pi_mac = uip_ethaddr.addr; - } - bridge_to_html(); - slen += strtox(outbuf + slen, "\",\"dp\":\""); - byte_to_html(dsg ? (stp_dpid[i] >> 8) : stp_pprio[i]); - byte_to_html(dsg ? stp_dpid[i] : (i + 1)); - slen += strtox(outbuf + slen, "\",\"dc\":\""); - pi_u32 = dsg ? stp_dcost[i] : root_bridge_cost; u32hex_html(); - slen += strtox(outbuf + slen, "\"},"); - } - slen -= 1; // remove comma - slen += strtox(outbuf + slen, "]}"); -} - void send_lacp(void) { dbg_string("send_lacp called\n"); @@ -816,6 +679,7 @@ void send_lldp(void) char_to_html('}'); } + void send_eee(void) { dbg_string("send_eee called\nsending EEE status\n"); diff --git a/httpd/page_impl.h b/httpd/page_impl.h index 5133d2eb..366bfa8b 100644 --- a/httpd/page_impl.h +++ b/httpd/page_impl.h @@ -22,9 +22,10 @@ void send_isolation(void); void send_config(void); void send_cmd_log(void); void send_lag(void); -void send_stp(void); +void send_stp(void) __banked; void send_lldp(void); void send_lacp(void); +void send_stp_counters(void) __banked; void send_vlanlist(void); /* Convert only the lower nibble to ascii HEX char. diff --git a/httpd/page_stp.c b/httpd/page_stp.c new file mode 100644 index 00000000..fbc2ce50 --- /dev/null +++ b/httpd/page_stp.c @@ -0,0 +1,195 @@ +#include "rtl837x_sfr.h" +#include "rtl837x_common.h" +#include "rtl837x_regs.h" +#include "rtl837x_port.h" +#include "uip.h" +#include +#include "phy.h" +#include "machine.h" +#include "rtl837x_stp.h" +#include "page_impl.h" +#include "debug.h" + +#pragma codeseg BANK4 +#pragma constseg BANK4 + +extern __xdata uint8_t outbuf[TCP_OUTBUF_SIZE]; +extern __xdata uint16_t slen; + +static __code const uint8_t stp_json_hdr[] = "HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Type: application/json\r\n\r\n"; + +static void stp_bool_html(char c) +{ + outbuf[slen++] = c ? '1' : '0'; +} + +static void stp_byte_html(uint8_t val) +{ + outbuf[slen++] = itohex(val >> 4); + outbuf[slen++] = itohex(val); +} + +static void stp_itoa_html(uint8_t v) +{ + uint8_t t = v / 100; + uint8_t print_zeros = t; + if (print_zeros) + outbuf[slen++] = '0' + t; + t = (v / 10) % 10; + print_zeros |= t; + if (print_zeros) + outbuf[slen++] = '0' + t; + outbuf[slen++] = '0' + (v % 10); +} + + +static __xdata uint32_t pi_u32; +static __xdata uint8_t pi_prio, pi_ext; +static __xdata uint8_t * __xdata pi_mac; + + +static void u32hex_html(void) +{ + __xdata uint8_t *b = (__xdata uint8_t *)&pi_u32; + stp_byte_html(b[3]); + stp_byte_html(b[2]); + stp_byte_html(b[1]); + stp_byte_html(b[0]); +} + + +static void bridge_to_html(void) +{ + stp_byte_html(pi_prio); + stp_byte_html(pi_ext); + for (uint8_t i = 0; i < 6; i++) + stp_byte_html(pi_mac[i]); +} + + +void send_stp(void) __banked +{ + static __xdata uint8_t i, j, st, dsg; + + dbg_string("send_stp called\n"); + slen = strtox(outbuf, stp_json_hdr); + + slen += strtox(outbuf + slen, "{\"on\":"); + stp_bool_html(stp_enabled); + slen += strtox(outbuf + slen, ",\"rstp\":"); + stp_bool_html(stp_rstp); + slen += strtox(outbuf + slen, ",\"prio\":"); + stp_itoa_html(stp_prio >> 4); + slen += strtox(outbuf + slen, ",\"hello\":"); + stp_itoa_html(stp_hello_s); + slen += strtox(outbuf + slen, ",\"maxage\":"); + stp_itoa_html(stp_maxage_s); + slen += strtox(outbuf + slen, ",\"fwd\":"); + stp_itoa_html(stp_fwddelay_s); + slen += strtox(outbuf + slen, ",\"txhold\":"); + stp_itoa_html(stp_txhold); + slen += strtox(outbuf + slen, ",\"pcs\":"); + stp_itoa_html(stp_pcost_short); + slen += strtox(outbuf + slen, ",\"bh\":"); + stp_itoa_html(stp_bpdu_filter); + slen += strtox(outbuf + slen, ",\"tcs\":\""); + pi_u32 = stp_tc_secs; u32hex_html(); + slen += strtox(outbuf + slen, "\",\"rMaxage\":"); + stp_itoa_html(stp_root_port == 0xff ? stp_maxage_s : stp_root_maxage); + slen += strtox(outbuf + slen, ",\"rFwd\":"); + stp_itoa_html(stp_root_port == 0xff ? stp_fwddelay_s : stp_root_fwd); + slen += strtox(outbuf + slen, ",\"rootPrio\":\""); + stp_byte_html(root_bridge.prio); + stp_byte_html(root_bridge.ext); + slen += strtox(outbuf + slen, "\",\"rootMac\":\""); + for (j = 0; j < 6; j++) + stp_byte_html(root_bridge.mac[j]); + slen += strtox(outbuf + slen, "\",\"myMac\":\""); + for (j = 0; j < 6; j++) + stp_byte_html(uip_ethaddr.addr[j]); + slen += strtox(outbuf + slen, "\",\"cost\":\""); + stp_byte_html(root_bridge_cost >> 24); + stp_byte_html(root_bridge_cost >> 16); + stp_byte_html(root_bridge_cost >> 8); + stp_byte_html(root_bridge_cost); + slen += strtox(outbuf + slen, "\",\"weRoot\":"); + stp_bool_html(stp_root_port == 0xff ? 1 : 0); + slen += strtox(outbuf + slen, ",\"tc\":\""); + stp_byte_html(stp_tc_count >> 8); + stp_byte_html(stp_tc_count); + slen += strtox(outbuf + slen, "\",\"ports\":["); + for (i = 0; i < STP_ENTITIES; i++) { + j = stp_ent_id(i); + if (!j) + continue; + slen += strtox(outbuf + slen, "{\"p\":"); + stp_itoa_html(j); + slen += strtox(outbuf + slen, ",\"st\":"); + st = stp_port_state(i); + stp_itoa_html(st); + slen += strtox(outbuf + slen, ",\"role\":"); + stp_itoa_html(stp_port_role(i)); + slen += strtox(outbuf + slen, ",\"f\":"); + stp_itoa_html(stp_pflags[i]); + slen += strtox(outbuf + slen, ",\"pc\":\""); + pi_u32 = stp_pcost[i]; u32hex_html(); + slen += strtox(outbuf + slen, "\",\"prio\":"); + stp_itoa_html(stp_pprio[i]); + slen += strtox(outbuf + slen, ",\"p2\":"); + stp_itoa_html(stp_pp2p[i]); + slen += strtox(outbuf + slen, ",\"lk\":"); + stp_itoa_html((stp_link_prev >> i) & 1); + slen += strtox(outbuf + slen, ",\"lg\":"); + stp_itoa_html((stp_legacy >> i) & 1); + dsg = stp_dpid[i] && stp_info_while[i]; + slen += strtox(outbuf + slen, ",\"db\":\""); + if (dsg) { + pi_prio = stp_dbridge[i].prio; pi_ext = stp_dbridge[i].ext; + pi_mac = stp_dbridge[i].mac; + } else { + pi_prio = stp_prio; pi_ext = 0; + pi_mac = uip_ethaddr.addr; + } + bridge_to_html(); + slen += strtox(outbuf + slen, "\",\"dp\":\""); + stp_byte_html(dsg ? (stp_dpid[i] >> 8) : stp_pprio[i]); + stp_byte_html(dsg ? stp_dpid[i] : (i + 1)); + slen += strtox(outbuf + slen, "\",\"dc\":\""); + pi_u32 = dsg ? stp_dcost[i] : root_bridge_cost; u32hex_html(); + slen += strtox(outbuf + slen, "\"},"); + } + slen -= 1; // remove comma + slen += strtox(outbuf + slen, "]}"); +} + + +void send_stp_counters(void) __banked +{ + uint8_t i, j; + + slen = strtox(outbuf, stp_json_hdr); + slen += strtox(outbuf + slen, "{\"on\":"); + stp_bool_html(stp_enabled); + slen += strtox(outbuf + slen, ",\"hz\":"); + stp_itoa_html(STP_HZ); + slen += strtox(outbuf + slen, ",\"tc\":\""); + stp_byte_html(stp_tc_count >> 8); + stp_byte_html(stp_tc_count); + slen += strtox(outbuf + slen, "\",\"ports\":["); + for (i = 0; i < STP_ENTITIES; i++) { + j = stp_ent_id(i); + if (!j) + continue; + slen += strtox(outbuf + slen, "{\"p\":"); + stp_itoa_html(j); + slen += strtox(outbuf + slen, ",\"c\":\""); + for (j = 0; j < STP_CNT_N; j++) { + pi_u32 = stp_cnt[j][i]; u32hex_html(); + } + stp_byte_html(stp_bpdu_age[i] >> 8); + stp_byte_html(stp_bpdu_age[i]); + slen += strtox(outbuf + slen, "\"},"); + } + slen -= 1; + slen += strtox(outbuf + slen, "]}"); +} diff --git a/rtl837x_stp.c b/rtl837x_stp.c index 118e265c..8316c34a 100644 --- a/rtl837x_stp.c +++ b/rtl837x_stp.c @@ -20,8 +20,8 @@ extern __xdata uint8_t err_status; // All entry points are __banked and nothing here runs from an interrupt, // so the module does not need to stay in the resident bank -#pragma codeseg BANK2 -#pragma constseg BANK2 +#pragma codeseg BANK4 +#pragma constseg BANK4 extern __code const struct machine machine; extern __xdata uint8_t sfr_data[4]; @@ -32,14 +32,6 @@ extern __xdata struct uip_eth_addr uip_ethaddr; extern __xdata uint8_t uip_buf[UIP_CONF_BUFFER_ALLOC_SIZE]; -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 char save_cmd; /* 0 while execute_config() replays the saved config */ -uint8_t cmd_compare(uint8_t start, __code const uint8_t * cmd); -uint8_t atoi_byte(uint8_t idx); -uint8_t cmd_parse_port_separator(uint8_t idx); -extern __xdata uint8_t atoi_results_u8; /* ---- Configuration ---- */ __xdata uint8_t stp_prio; /* bridge priority high byte (0x80 = 32768) */ @@ -50,36 +42,54 @@ __xdata uint8_t stp_rstp; /* 1 = RST BPDUs, 0 = legacy Config BPDUs */ __xdata uint8_t stp_txhold; /* BPDUs per port per second */ -__xdata uint8_t stp_ent_of[STP_PORTS]; -__xdata uint16_t stp_lag_mask[STP_LAG_COUNT]; -__xdata uint8_t stp_ss_i; -__xdata uint8_t stp_map_dirty; __xdata uint8_t stp_pflags[STP_ENTITIES]; -__xdata uint32_t stp_pcost[STP_ENTITIES]; /* 0 = auto */ +__xdata uint32_t stp_pcost[STP_ENTITIES]; /* 0 = auto */ __xdata uint8_t stp_pprio[STP_ENTITIES]; -__xdata uint8_t stp_pp2p[STP_ENTITIES]; /* admin point-to-point: 0 auto, 1 on, 2 off */ +__xdata uint8_t stp_pspeed[STP_ENTITIES]; /* speed nibble last read from the ASIC */ +__xdata uint8_t stp_pp2p[STP_ENTITIES]; /* admin point-to-point: 0 auto, 1 on, 2 off */ -/* Designated bridge, port and cost last heard on the port; stp_bpdu_age tells - * whether they are still current. +/* Designated bridge, port and cost last heard on the port; stp_info_while + * tells whether they are still current. */ __xdata struct bridge stp_dbridge[STP_ENTITIES]; +__xdata struct bridge stp_droot[STP_ENTITIES]; __xdata uint16_t stp_dpid[STP_ENTITIES]; __xdata uint32_t stp_dcost[STP_ENTITIES]; +__xdata uint16_t stp_alt; /* bit per port: blocked, a better bridge owns the segment */ +__xdata struct bridge stp_self; +__xdata uint8_t stp_j; +__xdata uint8_t stp_best; +__xdata uint32_t stp_cost_cand; +__xdata uint32_t stp_cost_best; +__xdata int8_t stp_cmp; +__xdata uint8_t stp_rxage[STP_ENTITIES]; /* message age heard on the port, seconds */ +__xdata uint8_t stp_rxmaxage[STP_ENTITIES]; +__xdata uint8_t stp_rxhello[STP_ENTITIES]; +__xdata uint8_t stp_rxfwd[STP_ENTITIES]; +__xdata uint16_t stp_info_while[STP_ENTITIES]; /* ticks the heard information stays valid, 0 = none */ +__xdata uint8_t stp_root_maxage; /* max age and forward delay of the root */ +__xdata uint8_t stp_root_fwd; +__xdata uint8_t stp_reselect_due; +__xdata uint32_t stp_rpcost[STP_ENTITIES]; /* root path cost through the port: heard cost plus port cost */ /* ---- Status / runtime ---- */ __xdata struct bridge root_bridge; __xdata uint32_t root_bridge_cost; /* our cost to the root (rx cost + root port cost) */ __xdata uint8_t stp_root_port; /* 0xff = we are the root */ __xdata uint16_t stp_tc_count; -__xdata uint16_t stp_scratch16; /* scratch for status printing and the lag map */ -__xdata uint8_t stp_st_of; +__xdata uint16_t stp_tc_seen; +__xdata uint32_t stp_tc_secs; /* seconds since the topology change counter last moved */ +__xdata uint8_t stp_pcost_short; /* 1: automatic port costs from the 802.1D-1998 table */ +__xdata uint8_t stp_bpdu_filter; /* 1: keep BPDUs on the CPU while STP is off instead of flooding */ +__xdata uint16_t stp_scratch16; /* scratch for status printing only */ __xdata uint16_t port_timers[STP_ENTITIES]; /* listen-period countdown (0 = not listening) */ __xdata uint16_t port_hello[STP_ENTITIES]; /* hello TX countdown */ __xdata uint16_t stp_bpdu_age[STP_ENTITIES]; /* ticks since last BPDU seen on port (saturating) */ __xdata uint8_t stp_loop_held[STP_ENTITIES]; /* port is out of forwarding because a loop was seen on it */ +__xdata uint16_t stp_heard; /* bit per port: a BPDU arrived since the link last went down */ __xdata uint8_t stp_tx_budget[STP_ENTITIES]; /* tx hold: BPDUs left in the current second */ -__xdata uint8_t stp_tx_count[STP_ENTITIES]; /* BPDUs actually put on the wire, wraps at 256 */ +__xdata uint32_t stp_cnt[STP_CNT_N][STP_ENTITIES]; /* per-port counters, first index STP_CNT_* */ __xdata uint16_t stp_sec_tick; /* 1 s window for the tx budget */ __xdata uint16_t stp_link_prev; /* carrier bitmap as of the last check */ __xdata uint16_t stp_link_now; @@ -89,15 +99,55 @@ __xdata uint8_t stp_tx_flags_extra; /* one-shot flags OR-ed into the next BPDU __xdata uint8_t stp_rxbody; __xdata uint16_t stp_rxlen; /* received frame length, saved before uip_len is consumed */ __xdata uint8_t stp_msg_age; /* message age of the root info we hold, seconds */ -__xdata uint16_t stp_tc_while; /* ticks left to set the TC flag in our BPDUs */ +__xdata uint16_t stp_tcwhile[STP_ENTITIES]; /* ticks left to send TC on the port, 0 = none */ +__xdata uint16_t stp_newinfo; /* bit per port: send a BPDU at the next tick */ +__xdata uint8_t stp_k; +__xdata uint8_t stp_tcn; +__xdata uint8_t stp_armed; +__xdata uint16_t stp_agree; /* bit per port: answer with an agreement in the next BPDU */ +__xdata uint16_t stp_rrwhile[STP_ENTITIES]; /* ticks the port still counts as a recent root port */ +__xdata uint16_t stp_rbwhile[STP_ENTITIES]; /* ticks the port still counts as a recent backup port */ +__xdata uint16_t stp_reroot; /* bit per port: recent root port held out of forwarding */ +__xdata uint8_t stp_synced_root; /* root port the last sync was done for, 0xff = none */ +__xdata uint16_t stp_backup; /* bit per port: blocked because another port of ours owns the segment */ +__xdata uint16_t stp_alt_agreed; /* bit per port: alternate or backup port that already agreed to the information it holds */ +__xdata uint16_t stp_legacy; /* bit per port: the neighbour speaks 802.1D, send Config BPDUs and TCN */ +__xdata uint16_t stp_seen_stp; /* bit per port: an 802.1D BPDU arrived during the migrate time */ +__xdata uint16_t stp_seen_rstp; /* bit per port: an RST BPDU arrived during the migrate time */ +__xdata uint8_t stp_mdelay[STP_ENTITIES]; /* ticks before the port may change protocol again */ __xdata uint8_t stp_i; __xdata uint32_t stp_cost_scratch; __xdata uint8_t stp_loop_peer; /* the other own port seen on a looped segment */ +__xdata uint8_t stp_ent_of[STP_PORTS]; /* entity a port answers to: itself, or STP_LAG_BASE + lag */ +__xdata uint16_t stp_lag_mask[STP_LAG_COUNT]; /* member ports of each lag, 0 = no such lag */ +__xdata uint16_t stp_map_changed; /* bit per entity: its membership changed, start it over */ +__xdata uint16_t stp_link_phys; /* carrier bitmap of the physical ports */ +__xdata uint16_t stp_ent_bit; +__xdata uint16_t stp_ss_mask; +__xdata uint8_t stp_ss_i; +__xdata uint8_t stp_lag; #define STP_EDGE_DELAY (3 * STP_HZ) /* auto-edge: forward after 3 s without BPDU */ -#define AUTO_COST 20000UL /* path cost used when stp_pcost == 0 (1G default) */ -#define PCOST(i) (stp_pcost[i] ? stp_pcost[i] : AUTO_COST) +#define MAXAGE_S (stp_root_port == 0xff ? stp_maxage_s : stp_root_maxage) +#define FWD_S (stp_root_port == 0xff ? stp_fwddelay_s : stp_root_fwd) +#define FWD_TICKS ((uint16_t)FWD_S * STP_HZ) +#define P2P(i) (stp_pp2p[i] != 2) +#define SEND_RSTP(i) (stp_rstp && !((stp_legacy >> (i)) & 1)) +#define STP_MIGRATE (3 * STP_HZ) + +#define AUTO_COST 20000UL /* path cost of a link whose speed we cannot read */ +#define PCOST(i) (stp_pcost[i] ? stp_pcost[i] : stp_speed_cost[(stp_pcost_short << 3) | (stp_pspeed[i] & 0x7)]) + +/* Path costs indexed by the speed nibble the ASIC reports: 0 10M, 1 100M, + * 2 1G, 4 10G, 5 2.5G, 6 5G; the rest are unknown to us. Entries 0-7 hold the + * 802.1Q recommended values, 8-15 the 802.1D-1998 ones for the same speeds. */ +static __code const uint32_t stp_speed_cost[16] = { + 2000000UL, 200000UL, 20000UL, AUTO_COST, + 2000UL, 8000UL, 4000UL, AUTO_COST, + 100UL, 19UL, 4UL, 4UL, + 2UL, 3UL, 3UL, 4UL +}; struct stp_pkt { uint8_t stp_addr[6]; @@ -165,31 +215,35 @@ struct stp_pkt_in { #define BPDU_LEN_RST 0x27 // LLC and a 36 byte body #define BPDU_LEN_MIN_BODY 9 #define BPDU_LEN_FULL_BODY 40 +#define BPDU_LEN_TCN 0x07 // LLC and a 4 byte body #define BPDU_FLAG_TC 0x01 +#define BPDU_FLAG_PROPOSAL 0x02 +#define BPDU_FLAG_AGREEMENT 0x40 #define BPDU_FLAG_LEARNING 0x10 #define BPDU_FLAG_FORWARDING 0x20 #define BPDU_FLAG_TCACK 0x80 #define BPDU_ROLE_ROOT (0b10 << 2) #define BPDU_ROLE_DESIGNATED (0b11 << 2) - -/* Port state as the MSTP register encodes it, two bits per port. */ -#define STP_ST_DISABLED 0b00 -#define STP_ST_BLOCKING 0b01 -#define STP_ST_LEARNING 0b10 -#define STP_ST_FORWARDING 0b11 -#define STP_ST_MASK 0b11 +#define BPDU_ROLE_MASK (0b11 << 2) +#define BPDU_ROLE_ALTBACK (0b01 << 2) /* Console messages name the port on the front panel, not the internal index. */ +static void print_ent(uint8_t e) __reentrant +{ + if (e >= STP_LAG_BASE) { + write_char('L'); + write_char('1' + e - STP_LAG_BASE); + } else { + print_byte(machine.log_to_phys_port[e]); + } +} + + static void print_port_nl(uint8_t port) __reentrant { - if (port >= STP_LAG_BASE) { - write_char('L'); - write_char('1' + port - STP_LAG_BASE); - } else { - print_byte(machine.log_to_phys_port[port]); - } + print_ent(port); write_char('\n'); } @@ -206,9 +260,10 @@ static void print_bridge_id(uint8_t prio, uint8_t ext, __xdata uint8_t *mac) __r * formatter. The state indices are the ASIC's own two bits, in the order * stp_state_set() writes them. */ static __code const char stp_state_txt[] = "off blocklearnfwd "; -static __code const char stp_role_txt[] = "desgroot"; +static __code const char stp_role_txt[] = "dis rootdesgaltnback"; static __code const char stp_edge_txt[] = "no yes "; +static uint8_t stp_state_get(uint8_t port) __reentrant; static uint8_t stp_ent_active(uint8_t e) __reentrant; static void print_field(__code const char *txt, uint8_t idx, uint8_t width) __reentrant @@ -219,7 +274,7 @@ static void print_field(__code const char *txt, uint8_t idx, uint8_t width) __re } -static void stp_status(void) +void stp_status(void) __banked { if (!stp_enabled) { print_string("STP off\n"); @@ -234,12 +289,7 @@ static void stp_status(void) print_string(" (this switch)\n"); } else { print_string(" port "); - if (stp_root_port >= STP_LAG_BASE) { - write_char('L'); - write_char('1' + stp_root_port - STP_LAG_BASE); - } else { - print_byte(machine.log_to_phys_port[stp_root_port]); - } + print_ent(stp_root_port); print_string(" cost "); print_long(root_bridge_cost); write_char('\n'); @@ -248,33 +298,19 @@ static void stp_status(void) print_short(stp_tc_count); write_char('\n'); print_string("port state role edge tx bpdu\n"); - reg_read_m(RTL837X_MSTP_STATES); for (stp_i = 0; stp_i < STP_ENTITIES; stp_i++) { if (!stp_ent_active(stp_i)) continue; write_char(' '); - if (stp_i >= STP_LAG_BASE) { - write_char('L'); - write_char('1' + stp_i - STP_LAG_BASE); - } else { - print_byte(machine.log_to_phys_port[stp_i]); - } + print_ent(stp_i); print_string(" "); - stp_st_of = stp_i; - if (stp_i >= STP_LAG_BASE) { - stp_st_of = 0; - while (stp_st_of < STP_PORTS && !((stp_lag_mask[stp_i - STP_LAG_BASE] >> stp_st_of) & 1)) - stp_st_of++; - if (stp_st_of >= STP_PORTS) - stp_st_of = 0; - } - print_field(stp_state_txt, (sfr_data[3 - (stp_st_of >> 2)] >> ((stp_st_of << 1) & 0x7)) & 0x3, 5); + print_field(stp_state_txt, stp_state_get(stp_i), 5); write_char(' '); - print_field(stp_role_txt, stp_i == stp_root_port ? 1 : 0, 4); + print_field(stp_role_txt, stp_port_role(stp_i), 4); write_char(' '); print_field(stp_edge_txt, stp_pflags[stp_i] & STP_PF_OPEREDGE ? 1 : 0, 4); write_char(' '); - print_byte(stp_tx_count[stp_i]); + print_byte((uint8_t)stp_cnt[STP_CNT_TX][stp_i]); write_char(' '); stp_scratch16 = stp_bpdu_age[stp_i] / STP_HZ; itoa(stp_scratch16 > 255 ? 255 : (uint8_t)stp_scratch16); @@ -283,20 +319,6 @@ static void stp_status(void) } -static void stp_record_designated(uint8_t port) __reentrant -{ - stp_dbridge[port].prio = STP_I->bridge.prio; - stp_dbridge[port].ext = STP_I->bridge.ext; - memcpy(stp_dbridge[port].mac, STP_I->bridge.mac, 6); - stp_dpid[port] = ((uint16_t)STP_I->port_prio << 8) | STP_I->port_id; - stp_cost_scratch = STP_I->root_path_cost; - stp_dcost[port] = ((stp_cost_scratch & 0xff) << 24) - | ((stp_cost_scratch & 0xff00) << 8) - | ((stp_cost_scratch >> 8) & 0xff00) - | (stp_cost_scratch >> 24); -} - - /* Lexicographic compare of n bytes. A MAC is 6 of them; a Bridge Identifier * is 8, the two priority octets ahead of the MAC, compared as one unsigned * number per 802.1D. */ @@ -313,109 +335,216 @@ int8_t cmpBytes(__xdata uint8_t *m1, __xdata uint8_t *m2, uint8_t n) __reentrant } -static void stp_lag_map(void) +static int8_t stp_msg_cmp(uint8_t port) __reentrant { - for (stp_ss_i = 0; stp_ss_i < STP_PORTS; stp_ss_i++) - stp_ent_of[stp_ss_i] = stp_ss_i; - for (stp_scratch = 0; stp_scratch < STP_LAG_COUNT; stp_scratch++) { - stp_scratch16 = port_lag_members_get(stp_scratch); - if (stp_lag_mask[stp_scratch] != stp_scratch16) - stp_map_dirty = 1; - stp_lag_mask[stp_scratch] = stp_scratch16; - for (stp_ss_i = 0; stp_ss_i < STP_PORTS; stp_ss_i++) - if ((stp_lag_mask[stp_scratch] >> stp_ss_i) & 1) - stp_ent_of[stp_ss_i] = STP_LAG_BASE + stp_scratch; - } + stp_cmp = cmpBytes((__xdata uint8_t *)&STP_I->root, (__xdata uint8_t *)&stp_droot[port], 8); + if (stp_cmp) + return stp_cmp; + if (stp_cost_scratch != stp_dcost[port]) + return stp_cost_scratch < stp_dcost[port] ? -1 : 1; + stp_cmp = cmpBytes((__xdata uint8_t *)&STP_I->bridge, (__xdata uint8_t *)&stp_dbridge[port], 8); + if (stp_cmp) + return stp_cmp; + if (STP_I->port_prio != (uint8_t)(stp_dpid[port] >> 8)) + return STP_I->port_prio < (uint8_t)(stp_dpid[port] >> 8) ? -1 : 1; + if (STP_I->port_id != (uint8_t)stp_dpid[port]) + return STP_I->port_id < (uint8_t)stp_dpid[port] ? -1 : 1; + return 0; } +static uint8_t stp_clamp(uint8_t v, uint8_t lo, uint8_t hi, uint8_t dflt) __reentrant +{ + return (v < lo || v > hi) ? dflt : v; +} + + +static void stp_rcv_info(uint8_t port) __reentrant +{ + if (STP_I->bpdu_type == BPDU_TYPE_RST + && (STP_I->flags & BPDU_ROLE_MASK) != BPDU_ROLE_DESIGNATED) + return; + + ((__xdata uint8_t *)&stp_cost_scratch)[0] = ((__xdata uint8_t *)&STP_I->root_path_cost)[3]; + ((__xdata uint8_t *)&stp_cost_scratch)[1] = ((__xdata uint8_t *)&STP_I->root_path_cost)[2]; + ((__xdata uint8_t *)&stp_cost_scratch)[2] = ((__xdata uint8_t *)&STP_I->root_path_cost)[1]; + ((__xdata uint8_t *)&stp_cost_scratch)[3] = ((__xdata uint8_t *)&STP_I->root_path_cost)[0]; + + if (stp_info_while[port] + && (cmpBytes(STP_I->bridge.mac, stp_dbridge[port].mac, 6) + || STP_I->port_id != (uint8_t)stp_dpid[port]) + && stp_msg_cmp(port) > 0) + return; + + if (!stp_info_while[port] || stp_msg_cmp(port)) + stp_alt_agreed &= ~((uint16_t)1 << port); + + stp_dbridge[port].prio = STP_I->bridge.prio; + stp_dbridge[port].ext = STP_I->bridge.ext; + memcpy(stp_dbridge[port].mac, STP_I->bridge.mac, 6); + stp_droot[port].prio = STP_I->root.prio; + stp_droot[port].ext = STP_I->root.ext; + memcpy(stp_droot[port].mac, STP_I->root.mac, 6); + stp_dpid[port] = ((uint16_t)STP_I->port_prio << 8) | STP_I->port_id; + stp_dcost[port] = stp_cost_scratch; + + stp_rxage[port] = (uint8_t)STP_I->age; + stp_rxmaxage[port] = stp_clamp((uint8_t)STP_I->age_max, 6, 40, 20); + stp_rxhello[port] = stp_clamp((uint8_t)STP_I->hello, 1, 10, 2); + stp_rxfwd[port] = stp_clamp((uint8_t)STP_I->fwd_delay, 4, 30, 15); + stp_info_while[port] = (uint16_t)stp_rxage[port] + 1 <= stp_rxmaxage[port] + ? (uint16_t)3 * stp_rxhello[port] * STP_HZ : 0; +} + + + + +/* Write one port's 2-bit state into the ASIC's MSTP register. + * 00 disable, 01 blocking, 10 learning, 11 forwarding. */ static uint8_t stp_ent_active(uint8_t e) __reentrant { if (e >= STP_ENTITIES) return 0; if (e >= STP_LAG_BASE) return stp_lag_mask[e - STP_LAG_BASE] != 0; - return MACHINE_PORT_VALID(e) && stp_ent_of[e] == e; + if (!MACHINE_PORT_VALID(e)) + return 0; + return stp_ent_of[e] == e; } -static uint8_t stp_ent_has(uint8_t ent, uint8_t p) __reentrant +uint8_t stp_ent_id(uint8_t e) __banked { - if (ent < STP_LAG_BASE) - return ent == p; - return (stp_lag_mask[ent - STP_LAG_BASE] >> p) & 1; + if (!stp_ent_active(e)) + return 0; + if (e >= STP_LAG_BASE) + return e + 101 - STP_LAG_BASE; + return machine.log_to_phys_port[e]; } -static uint8_t stp_state_port(uint8_t e) __reentrant +static uint16_t stp_members(uint8_t e) __reentrant +{ + return e >= STP_LAG_BASE ? stp_lag_mask[e - STP_LAG_BASE] : (uint16_t)1 << e; +} + + +static uint8_t stp_first(uint16_t mask) __reentrant { - if (e < STP_LAG_BASE) - return e; for (stp_ss_i = 0; stp_ss_i < STP_PORTS; stp_ss_i++) - if ((stp_lag_mask[e - STP_LAG_BASE] >> stp_ss_i) & 1) + if ((mask >> stp_ss_i) & 1) return stp_ss_i; - return 0; -} - - -static void stp_state_bits(uint8_t port, uint8_t state) __reentrant -{ - for (stp_ss_i = 0; stp_ss_i < STP_PORTS; stp_ss_i++) - if (stp_ent_has(port, stp_ss_i)) - sfr_data[3 - (stp_ss_i >> 2)] |= (uint8_t)(state << ((stp_ss_i << 1) & 0x7)); + return 0xff; } static void stp_state_set(uint8_t port, uint8_t state) __reentrant { + stp_ss_mask = stp_members(port); reg_read_m(RTL837X_MSTP_STATES); for (stp_ss_i = 0; stp_ss_i < STP_PORTS; stp_ss_i++) { - if (!stp_ent_has(port, stp_ss_i)) + if (!((stp_ss_mask >> stp_ss_i) & 1)) continue; stp_scratch = 3 - (stp_ss_i >> 2); - sfr_data[stp_scratch] &= ~(uint8_t)(STP_ST_MASK << ((stp_ss_i << 1) & 0x7)); + sfr_data[stp_scratch] &= ~(uint8_t)(0b11 << ((stp_ss_i << 1) & 0x7)); sfr_data[stp_scratch] |= (uint8_t)(state << ((stp_ss_i << 1) & 0x7)); } reg_write_m(RTL837X_MSTP_STATES); } -static void stp_ent_apply(uint8_t e) __reentrant +static uint8_t stp_state_get(uint8_t port) __reentrant { - if (!(stp_pflags[e] & STP_PF_ENABLED)) { - stp_state_set(e, STP_ST_FORWARDING); - return; - } - if (stp_pflags[e] & STP_PF_TRIPPED) { - stp_state_set(e, STP_ST_DISABLED); - return; - } - if (stp_pflags[e] & STP_PF_ADMEDGE) { - stp_pflags[e] |= STP_PF_OPEREDGE; - stp_state_set(e, STP_ST_FORWARDING); - return; - } - if (port_timers[e]) - stp_state_set(e, STP_ST_BLOCKING); - else - stp_state_set(e, ((stp_link_prev >> e) & 1) ? STP_ST_FORWARDING : STP_ST_BLOCKING); + if (stp_first(stp_members(port)) == 0xff) + return 0; + reg_read_m(RTL837X_MSTP_STATES); + return (sfr_data[3 - (stp_ss_i >> 2)] >> ((stp_ss_i << 1) & 0x7)) & 0b11; } -/* Signal a topology change. Edge ports are exempt. */ -static void stp_topology_change(uint8_t port) __reentrant +uint8_t stp_port_state(uint8_t port) __banked +{ + return stp_state_get(port); +} + + +static void stp_forget(uint8_t e) __reentrant +{ + stp_ss_mask = stp_members(e); + for (stp_ss_i = 0; stp_ss_i < STP_PORTS; stp_ss_i++) + if ((stp_ss_mask >> stp_ss_i) & 1) + port_l2_forget_port(stp_ss_i); +} + + +static void stp_new_tc_while(uint8_t port) __reentrant +{ + if (stp_tcwhile[port]) + return; + stp_tcwhile[port] = SEND_RSTP(port) ? ((uint16_t)stp_hello_s + 1) * STP_HZ + : (uint16_t)MAXAGE_S * STP_HZ + FWD_TICKS; + stp_newinfo |= (uint16_t)1 << port; +} + + +static uint8_t stp_tc_prop(uint8_t from) __reentrant +{ + stp_armed = 0; + for (stp_k = 0; stp_k < STP_ENTITIES; stp_k++) { + if (stp_k == from || !stp_ent_active(stp_k) || stp_tcwhile[stp_k] || stp_state_get(stp_k) != 0b11) + continue; + if (!(stp_pflags[stp_k] & STP_PF_ENABLED) || (stp_pflags[stp_k] & STP_PF_OPEREDGE)) + continue; + stp_forget(stp_k); + stp_new_tc_while(stp_k); + stp_armed = 1; + } + return stp_armed; +} + + +static void stp_tc_detected(uint8_t port) __reentrant { if (stp_pflags[port] & STP_PF_OPEREDGE) return; stp_tc_count++; - stp_tc_while = ((uint16_t)stp_maxage_s + stp_fwddelay_s) * STP_HZ; - if (port >= STP_LAG_BASE) { - for (uint8_t i = MACHINE_MIN_PORT; i <= MACHINE_MAX_PORT; i++) - if (stp_ent_has(port, i)) - port_l2_forget_port(i); - } else { - port_l2_forget_port(port); + stp_new_tc_while(port); + stp_tc_prop(port); +} + + +static void stp_forward_now(uint8_t port) __reentrant +{ + port_timers[port] = 0; + stp_loop_held[port] = 0; + stp_backup &= ~((uint16_t)1 << port); + stp_state_set(port, 0b11); + print_string("STP: rapid transition, port forwarding "); + print_port_nl(port); + stp_tc_detected(port); +} + + +static void stp_sync(uint8_t from) __reentrant +{ + for (stp_k = 0; stp_k < STP_ENTITIES; stp_k++) { + if (!stp_ent_active(stp_k)) + continue; + if (stp_k == from || stp_k == stp_root_port || !(stp_pflags[stp_k] & STP_PF_ENABLED) + || (stp_pflags[stp_k] & STP_PF_OPEREDGE) || ((stp_alt >> stp_k) & 1) + || !((stp_link_prev >> stp_k) & 1)) + continue; + if (stp_state_get(stp_k) != 0b01) { + stp_state_set(stp_k, 0b01); + port_timers[stp_k] = FWD_TICKS; + } + stp_newinfo |= (uint16_t)1 << stp_k; } + if (from != stp_root_port) + return; + stp_synced_root = from; + if (port_timers[from]) + stp_forward_now(from); } @@ -426,7 +555,7 @@ static void stp_topology_change(uint8_t port) __reentrant */ static void stp_loop_hold_peer(uint8_t port) __reentrant { - if (port >= STP_ENTITIES || !stp_ent_active(port)) + if (!stp_ent_active(port)) return; if (!(stp_pflags[port] & STP_PF_ENABLED)) return; @@ -435,12 +564,14 @@ static void stp_loop_hold_peer(uint8_t port) __reentrant if (!port_timers[port]) { print_string("STP: loop detected, blocking port "); print_port_nl(port); - stp_state_set(port, STP_ST_BLOCKING); + stp_state_set(port, 0b01); stp_pflags[port] &= ~STP_PF_OPEREDGE; - stp_topology_change(port); + stp_forget(port); + stp_tcwhile[port] = 0; } stp_loop_held[port] = 1; - port_timers[port] = (uint16_t)stp_fwddelay_s * STP_HZ; + stp_backup |= (uint16_t)1 << port; + port_timers[port] = FWD_TICKS; } @@ -456,11 +587,155 @@ static void stp_claim_root(void) } +static uint8_t stp_info_fresh(uint8_t port) __reentrant +{ + return stp_info_while[port] != 0; +} + + +static int8_t stp_cmp_root_path(uint8_t a, uint8_t b) __reentrant +{ + stp_cmp = cmpBytes((__xdata uint8_t *)&stp_droot[a], (__xdata uint8_t *)&stp_droot[b], 8); + if (stp_cmp) + return stp_cmp; + if (stp_rpcost[a] != stp_rpcost[b]) + return stp_rpcost[a] < stp_rpcost[b] ? -1 : 1; + stp_cmp = cmpBytes((__xdata uint8_t *)&stp_dbridge[a], (__xdata uint8_t *)&stp_dbridge[b], 8); + if (stp_cmp) + return stp_cmp; + if (stp_dpid[a] != stp_dpid[b]) + return stp_dpid[a] < stp_dpid[b] ? -1 : 1; + if (stp_pprio[a] != stp_pprio[b]) + return stp_pprio[a] < stp_pprio[b] ? -1 : 1; + return a < b ? -1 : 1; +} + + +static int8_t stp_cmp_designated(uint8_t port) __reentrant +{ + stp_cmp = cmpBytes((__xdata uint8_t *)&stp_droot[port], (__xdata uint8_t *)&root_bridge, 8); + if (stp_cmp) + return stp_cmp; + if (stp_dcost[port] != root_bridge_cost) + return stp_dcost[port] < root_bridge_cost ? -1 : 1; + stp_self.prio = stp_prio; + stp_self.ext = 0x00; + memcpy(stp_self.mac, uip_ethaddr.addr, 6); + stp_cmp = cmpBytes((__xdata uint8_t *)&stp_dbridge[port], (__xdata uint8_t *)&stp_self, 8); + if (stp_cmp) + return stp_cmp; + if (stp_dpid[port] == (((uint16_t)stp_pprio[port] << 8) | (port + 1))) + return 0; + return stp_dpid[port] < (((uint16_t)stp_pprio[port] << 8) | (port + 1)) ? -1 : 1; +} + + +static void stp_reroot_tree(void) __reentrant +{ + for (stp_k = 0; stp_k < STP_ENTITIES; stp_k++) { + if (!stp_ent_active(stp_k)) + continue; + if (stp_k == stp_root_port || !stp_rrwhile[stp_k] + || !(stp_pflags[stp_k] & STP_PF_ENABLED) || (stp_pflags[stp_k] & STP_PF_OPEREDGE)) + continue; + stp_reroot |= (uint16_t)1 << stp_k; + if (stp_state_get(stp_k) != 0b01) { + stp_state_set(stp_k, 0b01); + port_timers[stp_k] = FWD_TICKS; + stp_newinfo |= (uint16_t)1 << stp_k; + } + } +} + + +static void stp_reselect(void) +{ + stp_self.prio = stp_prio; + stp_self.ext = 0x00; + memcpy(stp_self.mac, uip_ethaddr.addr, 6); + + stp_best = 0xff; + for (stp_j = 0; stp_j < STP_ENTITIES; stp_j++) { + if (!stp_ent_active(stp_j)) + continue; + if (!(stp_pflags[stp_j] & STP_PF_ENABLED) || (stp_pflags[stp_j] & STP_PF_ROOTGUARD)) + continue; + if (!stp_info_fresh(stp_j)) + continue; + if (!cmpBytes(stp_dbridge[stp_j].mac, uip_ethaddr.addr, 6)) + continue; + if (cmpBytes((__xdata uint8_t *)&stp_droot[stp_j], (__xdata uint8_t *)&stp_self, 8) >= 0) + continue; + stp_rpcost[stp_j] = stp_dcost[stp_j] + PCOST(stp_j); + if (stp_best == 0xff || stp_cmp_root_path(stp_j, stp_best) < 0) + stp_best = stp_j; + } + + if (stp_best != 0xff) { + if (stp_root_port != stp_best + || cmpBytes((__xdata uint8_t *)&root_bridge, (__xdata uint8_t *)&stp_droot[stp_best], 8)) { + if (cmpBytes((__xdata uint8_t *)&root_bridge, (__xdata uint8_t *)&stp_droot[stp_best], 8)) + print_string("Updating Root bridge\n"); + stp_synced_root = 0xff; + stp_root_port = stp_best; + root_bridge.prio = stp_droot[stp_best].prio; + root_bridge.ext = stp_droot[stp_best].ext; + memcpy(root_bridge.mac, stp_droot[stp_best].mac, 6); + stp_tc_count++; + } + root_bridge_cost = stp_rpcost[stp_best]; + stp_msg_age = stp_rxage[stp_best]; + stp_root_maxage = stp_rxmaxage[stp_best]; + stp_root_fwd = stp_rxfwd[stp_best]; + } else if (stp_root_port != 0xff) { + print_string("STP: root aged out, claiming root\n"); + stp_synced_root = 0xff; + stp_claim_root(); + stp_tc_count++; + } + + for (stp_j = 0; stp_j < STP_ENTITIES; stp_j++) { + if (!stp_ent_active(stp_j)) + continue; + if (!(stp_pflags[stp_j] & STP_PF_ENABLED)) + continue; + if (stp_j != stp_root_port && stp_info_fresh(stp_j) + && stp_cmp_designated(stp_j) < 0) { + stp_pflags[stp_j] &= ~STP_PF_OPEREDGE; + port_timers[stp_j] = 0; + stp_state_set(stp_j, 0b01); + if (!((stp_alt >> stp_j) & 1)) { + stp_alt |= (uint16_t)1 << stp_j; + stp_forget(stp_j); + stp_tcwhile[stp_j] = 0; + print_string("STP: better bridge on the segment, blocking port "); + print_port_nl(stp_j); + } + continue; + } + if (!((stp_alt >> stp_j) & 1)) + continue; + stp_alt &= ~((uint16_t)1 << stp_j); + stp_alt_agreed &= ~((uint16_t)1 << stp_j); + port_timers[stp_j] = FWD_TICKS; + print_string("STP: port released, listening "); + print_port_nl(stp_j); + } + + if (stp_rstp && stp_root_port != 0xff && port_timers[stp_root_port] + && !((stp_alt >> stp_root_port) & 1) && !stp_rbwhile[stp_root_port]) { + stp_reroot_tree(); + stp_forward_now(stp_root_port); + } +} + + void stp_cnf_send(uint8_t port) __reentrant { /* A one-shot flag (TCA) belongs to the BPDU we were asked to send: drop * it with the frame, or it would surface on an unrelated port later. */ - if (!(stp_pflags[port] & STP_PF_ENABLED) || (stp_pflags[port] & (STP_PF_FILTER | STP_PF_TRIPPED))) { + if (!(stp_pflags[port] & STP_PF_ENABLED) || (stp_pflags[port] & (STP_PF_FILTER | STP_PF_TRIPPED)) + || !((stp_link_prev >> port) & 1) || !(stp_members(port) & stp_link_phys)) { stp_tx_flags_extra = 0; return; } @@ -469,7 +744,8 @@ void stp_cnf_send(uint8_t port) __reentrant return; } stp_tx_budget[port]--; - stp_tx_count[port]++; + stp_cnt[STP_CNT_TX][port]++; + stp_tcn = !SEND_RSTP(port) && port == stp_root_port; STP_O->stp_addr[0] = 0x01; STP_O->stp_addr[1] = 0x80; STP_O->stp_addr[2] = 0xc2; STP_O->stp_addr[3] = STP_O->stp_addr[4] = STP_O->stp_addr[5] = 0x00; @@ -478,42 +754,47 @@ void stp_cnf_send(uint8_t port) __reentrant STP_O->rtl_tag.version = RTL_FRAME_TAG_VERSION; STP_O->rtl_tag.reason = 0x00; STP_O->rtl_tag.flags = HTONS(RTL_TAG_LEARN_DIS); - if (port >= STP_LAG_BASE) { - stp_scratch = 0; - while (stp_scratch < STP_PORTS && !stp_ent_has(port, stp_scratch)) - stp_scratch++; - if (stp_scratch >= STP_PORTS) { - stp_tx_flags_extra = 0; - return; - } - STP_O->rtl_tag.pmask = HTONS(((uint16_t)1) << stp_scratch); - } else { - STP_O->rtl_tag.pmask = HTONS(((uint16_t)1) << port); - } + STP_O->rtl_tag.pmask = HTONS(((uint16_t)1) << stp_first(stp_members(port) & stp_link_phys)); STP_O->dsap = 0x42; STP_O->ssap = 0x42; STP_O->ctrl = 0x03; STP_O->proto = 0x0000; - if (stp_rstp) { + if (stp_tcn) { + STP_O->msg_len = HTONS(BPDU_LEN_TCN); + STP_O->version = BPDU_VER_STP; + STP_O->bpdu_type = BPDU_TYPE_TCN; + STP_O->flags = 0x00; + } else if (SEND_RSTP(port)) { STP_O->msg_len = HTONS(BPDU_LEN_RST); STP_O->version = BPDU_VER_RSTP; STP_O->bpdu_type = BPDU_TYPE_RST; - reg_read_m(RTL837X_MSTP_STATES); - stp_scratch = stp_state_port(port); - STP_O->flags = port == stp_root_port ? BPDU_ROLE_ROOT : BPDU_ROLE_DESIGNATED; - if (((sfr_data[3 - (stp_scratch >> 2)] >> ((stp_scratch << 1) & 0x7)) & STP_ST_MASK) == STP_ST_FORWARDING) + STP_O->flags = port == stp_root_port ? BPDU_ROLE_ROOT + : (((stp_alt | stp_backup) >> port) & 1) ? BPDU_ROLE_ALTBACK : BPDU_ROLE_DESIGNATED; + stp_scratch = stp_state_get(port); + if (stp_scratch == 0b11) STP_O->flags |= BPDU_FLAG_LEARNING | BPDU_FLAG_FORWARDING; + else if (stp_scratch == 0b10) + STP_O->flags |= BPDU_FLAG_LEARNING; + if (port != stp_root_port && port_timers[port] && P2P(port) + && !(stp_pflags[port] & STP_PF_OPEREDGE) && !(((stp_alt | stp_backup) >> port) & 1)) + STP_O->flags |= BPDU_FLAG_PROPOSAL; + if ((stp_agree >> port) & 1) { + STP_O->flags |= BPDU_FLAG_AGREEMENT; + stp_agree &= ~((uint16_t)1 << port); + } } else { STP_O->msg_len = HTONS(BPDU_LEN_CONFIG); STP_O->version = BPDU_VER_STP; STP_O->bpdu_type = BPDU_TYPE_CONFIG; STP_O->flags = 0x00; } - if (stp_tc_while) + if (stp_tcwhile[port]) STP_O->flags |= BPDU_FLAG_TC; STP_O->flags |= stp_tx_flags_extra; stp_tx_flags_extra = 0; + if (stp_tcn || (STP_O->flags & BPDU_FLAG_TC)) + stp_cnt[STP_CNT_TCTX][port]++; memcpy(STP_O->src_addr, uip_ethaddr.addr, 6); STP_O->src_addr[0] |= 0x02; @@ -539,16 +820,143 @@ void stp_cnf_send(uint8_t port) __reentrant * little-endian, so assigning the plain second count lands the value in * the high (seconds) octet - see age_max/hello/fwd_delay below. */ STP_O->age = (stp_root_port == 0xff) ? 0 : (uint16_t)(stp_msg_age + 1); - STP_O->age_max = stp_maxage_s; + STP_O->age_max = MAXAGE_S; STP_O->hello = stp_hello_s; - STP_O->fwd_delay = stp_fwddelay_s; + STP_O->fwd_delay = FWD_S; STP_O->version1_length = 0; /* RST BPDU: no version-1 information */ - uip_len = stp_rstp ? sizeof(struct stp_pkt) : sizeof(struct stp_pkt) - 1; + uip_len = stp_tcn ? sizeof(struct stp_pkt) - 32 + : (SEND_RSTP(port) ? sizeof(struct stp_pkt) : sizeof(struct stp_pkt) - 1); tcpip_output(); } +static uint8_t stp_len_ok(void) __reentrant +{ + if (STP_I->bpdu_type == BPDU_TYPE_TCN) + return HTONS(STP_I->msg_len) >= BPDU_LEN_TCN; + if (STP_I->bpdu_type == BPDU_TYPE_RST) + return HTONS(STP_I->msg_len) >= BPDU_LEN_RST; + return HTONS(STP_I->msg_len) >= BPDU_LEN_CONFIG; +} + + +static void stp_rx_seen(uint8_t port) __reentrant +{ + stp_bpdu_age[port] = 0; + stp_cnt[STP_CNT_RX][port]++; + if (stp_len_ok() && (STP_I->bpdu_type == BPDU_TYPE_TCN || (STP_I->flags & BPDU_FLAG_TC))) + stp_cnt[STP_CNT_TCRX][port]++; + if (!stp_rstp) + return; + if (STP_I->version < BPDU_VER_RSTP) + stp_seen_stp |= (uint16_t)1 << port; + else + stp_seen_rstp |= (uint16_t)1 << port; +} + + +static void stp_migrate_check(uint8_t port) __reentrant +{ + if (stp_mdelay[port]) { + stp_mdelay[port]--; + return; + } + if (!((stp_legacy >> port) & 1) && ((stp_seen_stp >> port) & 1)) { + stp_legacy |= (uint16_t)1 << port; + print_string("STP: 802.1D neighbour, sending STP on port "); + } else if (((stp_legacy >> port) & 1) && ((stp_seen_rstp >> port) & 1)) { + stp_legacy &= ~((uint16_t)1 << port); + print_string("STP: RSTP neighbour, sending RSTP on port "); + } else { + return; + } + print_port_nl(port); + stp_seen_stp &= ~((uint16_t)1 << port); + stp_seen_rstp &= ~((uint16_t)1 << port); + stp_mdelay[port] = STP_MIGRATE; + stp_newinfo |= (uint16_t)1 << port; +} + + +static int8_t stp_msg_vs_ours(uint8_t port) __reentrant +{ + stp_cmp = cmpBytes((__xdata uint8_t *)&STP_I->root, (__xdata uint8_t *)&root_bridge, 8); + if (stp_cmp) + return stp_cmp; + ((__xdata uint8_t *)&stp_cost_scratch)[0] = ((__xdata uint8_t *)&STP_I->root_path_cost)[3]; + ((__xdata uint8_t *)&stp_cost_scratch)[1] = ((__xdata uint8_t *)&STP_I->root_path_cost)[2]; + ((__xdata uint8_t *)&stp_cost_scratch)[2] = ((__xdata uint8_t *)&STP_I->root_path_cost)[1]; + ((__xdata uint8_t *)&stp_cost_scratch)[3] = ((__xdata uint8_t *)&STP_I->root_path_cost)[0]; + if (stp_cost_scratch != root_bridge_cost) + return stp_cost_scratch < root_bridge_cost ? -1 : 1; + stp_self.prio = stp_prio; + stp_self.ext = 0x00; + memcpy(stp_self.mac, uip_ethaddr.addr, 6); + stp_cmp = cmpBytes((__xdata uint8_t *)&STP_I->bridge, (__xdata uint8_t *)&stp_self, 8); + if (stp_cmp) + return stp_cmp; + if (STP_I->port_prio != stp_pprio[port]) + return STP_I->port_prio < stp_pprio[port] ? -1 : 1; + if (STP_I->port_id != port + 1) + return STP_I->port_id < port + 1 ? -1 : 1; + return 0; +} + + +static void stp_dispute_rx(uint8_t port) __reentrant +{ + if (port == stp_root_port || ((stp_alt >> port) & 1)) + return; + if (STP_I->bpdu_type == BPDU_TYPE_RST + && (STP_I->flags & BPDU_ROLE_MASK) != BPDU_ROLE_DESIGNATED) + return; + if (stp_msg_vs_ours(port) <= 0) + return; + stp_newinfo |= (uint16_t)1 << port; + if (!stp_rstp || STP_I->bpdu_type != BPDU_TYPE_RST + || !(STP_I->flags & BPDU_FLAG_LEARNING)) + return; + if (stp_state_get(port) != 0b01) { + stp_state_set(port, 0b01); + print_string("STP: dispute, port discarding "); + print_port_nl(port); + } + port_timers[port] = FWD_TICKS; +} + + +static void stp_rapid_rx(uint8_t port) __reentrant +{ + if (stp_rstp && STP_I->bpdu_type == BPDU_TYPE_RST && P2P(port)) { + if (port == stp_root_port + && (STP_I->flags & BPDU_ROLE_MASK) == BPDU_ROLE_DESIGNATED + && (STP_I->flags & BPDU_FLAG_PROPOSAL)) { + if (stp_synced_root != port) + stp_sync(port); + stp_agree |= (uint16_t)1 << port; + stp_newinfo |= (uint16_t)1 << port; + } else if ((((stp_alt | stp_backup) >> port) & 1) + && (STP_I->flags & BPDU_ROLE_MASK) == BPDU_ROLE_DESIGNATED + && (STP_I->flags & BPDU_FLAG_PROPOSAL)) { + if (!((stp_alt_agreed >> port) & 1)) + stp_sync(port); + stp_alt_agreed |= (uint16_t)1 << port; + stp_agree |= (uint16_t)1 << port; + stp_newinfo |= (uint16_t)1 << port; + } else if (port != stp_root_port && port_timers[port] + && !((stp_alt >> port) & 1) + && !(((stp_reroot >> port) & 1) && stp_rrwhile[port]) + && ((STP_I->flags & BPDU_ROLE_MASK) == BPDU_ROLE_ROOT + || (STP_I->flags & BPDU_ROLE_MASK) == BPDU_ROLE_ALTBACK) + && (STP_I->flags & BPDU_FLAG_AGREEMENT) + && !cmpBytes((__xdata uint8_t *)&STP_I->root, (__xdata uint8_t *)&root_bridge, 8)) { + stp_forward_now(port); + } + } +} + + void stp_in(void) __banked { uint8_t port; @@ -588,28 +996,34 @@ void stp_in(void) __banked print_string("STP: BPDU guard tripped, disabling port "); print_port_nl(port); stp_pflags[port] |= STP_PF_TRIPPED; - stp_state_set(port, STP_ST_DISABLED); + stp_state_set(port, 0b00); stp_tc_count++; return; } - stp_bpdu_age[port] = 0; + stp_rx_seen(port); /* A port that hears a BPDU is not an edge port, whatever it decided * during the silence after the link came up. Only the flag is dropped: * the port keeps whatever forwarding state the rules below give it, * rather than being pushed back through the listen period, which would * black-hole a working link for a forward delay on the first BPDU. The - * flag matters beyond the status page, since stp_topology_change() - * exempts edge ports and so would go on skipping the counter and the - * L2 flush for a port that has a bridge behind it. */ + * flag matters beyond the status page, since topology changes skip + * edge ports and would go on skipping the counter and the L2 flush for + * a port that has a bridge behind it. */ stp_pflags[port] &= ~STP_PF_OPEREDGE; + stp_heard |= (uint16_t)1 << port; + + if (!stp_len_ok()) + return; if (STP_I->bpdu_type == BPDU_TYPE_TCN) { stp_tx_flags_extra = BPDU_FLAG_TCACK; stp_cnf_send(port); uip_len = 0; - stp_topology_change(port); + stp_tc_count++; + stp_new_tc_while(port); + stp_tc_prop(port); return; } @@ -647,57 +1061,107 @@ void stp_in(void) __banked return; } - /* Topology Change in transit. The flag arms a short window that our - * own BPDUs copy downstream (the TX side already sends TC while - * stp_tc_while runs) and that each further flagged BPDU refreshes, so - * it expires one hello after the neighbour stops - without shortening - * the long window a local change may have armed. The flush runs once, - * on the arming edge: everything learned on the other non-edge ports - * may sit behind the moved link and must be relearned. */ - if (STP_I->flags & BPDU_FLAG_TC) { - if (!stp_tc_while) { - uint8_t i; - stp_tc_count++; - for (i = MACHINE_MIN_PORT; i <= MACHINE_MAX_PORT; i++) - if (!stp_ent_has(port, i) - && (stp_pflags[stp_ent_of[i]] & STP_PF_ENABLED) - && !(stp_pflags[stp_ent_of[i]] & STP_PF_OPEREDGE)) - port_l2_forget_port(i); - } - if (stp_tc_while < ((uint16_t)stp_hello_s + 1) * STP_HZ) - stp_tc_while = ((uint16_t)stp_hello_s + 1) * STP_HZ; - } - - stp_record_designated(port); - - /* Better root than the one we know? The identifier is priority, system - * ID extension and MAC in that order: comparing the priority byte and - * then jumping to the MAC skipped the twelve bits in between, so two - * bridges differing only in the extension were ranked by MAC. */ - if (cmpBytes((__xdata uint8_t *)&STP_I->root, (__xdata uint8_t *)&root_bridge, 8) < 0) { - /* Root guard: this port must never become our path to the root. */ - if (stp_pflags[port] & STP_PF_ROOTGUARD) { - print_string("STP: root guard blocking port "); - print_port_nl(port); - stp_state_set(port, STP_ST_BLOCKING); - port_timers[port] = (uint16_t)stp_fwddelay_s * STP_HZ; - stp_pflags[port] &= ~STP_PF_OPEREDGE; - return; - } - print_string("Updating Root bridge\n"); - root_bridge.prio = STP_I->root.prio; - root_bridge.ext = STP_I->root.ext; - memcpy(root_bridge.mac, STP_I->root.mac, 6); - stp_root_port = port; + if ((STP_I->flags & BPDU_FLAG_TCACK) && port == stp_root_port) + stp_tcwhile[port] = 0; + if ((STP_I->flags & BPDU_FLAG_TC) && !((stp_alt >> port) & 1) && stp_tc_prop(port)) stp_tc_count++; + + if ((stp_pflags[port] & STP_PF_ROOTGUARD) + && cmpBytes((__xdata uint8_t *)&STP_I->root, (__xdata uint8_t *)&root_bridge, 8) < 0) { + print_string("STP: root guard blocking port "); + print_port_nl(port); + stp_state_set(port, 0b01); + port_timers[port] = FWD_TICKS; + stp_pflags[port] &= ~STP_PF_OPEREDGE; + return; } - /* Refresh our cost to the root when the update comes in on the root port */ - if (port == stp_root_port) { - /* Age of the information we now hold (see the TX note on the wire - * format); saturate rather than wrap on absurd input. */ - stp_msg_age = (STP_I->age > 254) ? 254 : (uint8_t)STP_I->age; - root_bridge_cost = stp_dcost[port] + PCOST(port); + stp_rcv_info(port); + stp_reselect(); + + stp_rapid_rx(port); + stp_dispute_rx(port); +} + + +static void stp_tc_clock(void) __reentrant +{ + if (stp_tc_seen != stp_tc_count) { + stp_tc_seen = stp_tc_count; + stp_tc_secs = 0; + } else if (stp_tc_secs != 0xffffffffUL) { + stp_tc_secs++; + } +} + + +void stp_lag_map(void) __banked +{ + for (stp_ss_i = 0; stp_ss_i < STP_PORTS; stp_ss_i++) + stp_ent_of[stp_ss_i] = stp_ss_i; + for (stp_lag = 0; stp_lag < STP_LAG_COUNT; stp_lag++) { + stp_ss_mask = port_lag_members_get(stp_lag); + if (stp_ss_mask != stp_lag_mask[stp_lag]) { + stp_map_changed |= stp_ss_mask | stp_lag_mask[stp_lag] + | (uint16_t)1 << (STP_LAG_BASE + stp_lag); + stp_lag_mask[stp_lag] = stp_ss_mask; + } + for (stp_ss_i = 0; stp_ss_i < STP_PORTS; stp_ss_i++) + if ((stp_ss_mask >> stp_ss_i) & 1) + stp_ent_of[stp_ss_i] = STP_LAG_BASE + stp_lag; + } +} + + +static void stp_links_read(void) __reentrant +{ + reg_read_m(RTL837X_REG_LINKS_STS); + stp_link_phys = (uint16_t)sfr_data[1] | ((uint16_t)sfr_data[2] << 8); + stp_link_now = 0; + for (stp_k = 0; stp_k < STP_ENTITIES; stp_k++) { + if (!stp_ent_active(stp_k) || stp_first(stp_members(stp_k) & stp_link_phys) == 0xff) + continue; + stp_link_now |= (uint16_t)1 << stp_k; + if (stp_k >= STP_LAG_BASE) + stp_pspeed[stp_k] = stp_pspeed[stp_ss_i]; + } +} + + +static void stp_ent_reset(uint8_t e) __reentrant +{ + stp_ent_bit = (uint16_t)1 << e; + stp_alt &= ~stp_ent_bit; + stp_backup &= ~stp_ent_bit; + stp_alt_agreed &= ~stp_ent_bit; + stp_legacy &= ~stp_ent_bit; + stp_seen_stp &= ~stp_ent_bit; + stp_seen_rstp &= ~stp_ent_bit; + stp_agree &= ~stp_ent_bit; + stp_reroot &= ~stp_ent_bit; + stp_heard &= ~stp_ent_bit; + stp_newinfo &= ~stp_ent_bit; + stp_link_prev = (stp_link_prev & ~stp_ent_bit) | (stp_link_now & stp_ent_bit); + stp_pflags[e] &= ~(STP_PF_OPEREDGE | STP_PF_TRIPPED); + stp_loop_held[e] = 0; + stp_bpdu_age[e] = 0; + stp_tx_budget[e] = stp_txhold; + stp_info_while[e] = 0; + stp_tcwhile[e] = 0; + stp_mdelay[e] = STP_MIGRATE; + stp_rrwhile[e] = 0; + stp_rbwhile[e] = 0; + port_timers[e] = 0; + port_hello[e] = (uint16_t)stp_hello_s * STP_HZ; + if (!stp_ent_active(e)) + return; + if (!(stp_pflags[e] & STP_PF_ENABLED) || (stp_pflags[e] & STP_PF_ADMEDGE)) { + if (stp_pflags[e] & STP_PF_ADMEDGE) + stp_pflags[e] |= STP_PF_OPEREDGE; + stp_state_set(e, 0b11); + } else { + stp_state_set(e, 0b01); + port_timers[e] = FWD_TICKS; } } @@ -709,32 +1173,7 @@ void stp_timers(void) __banked stp_sec_tick = 0; for (stp_i = 0; stp_i < STP_ENTITIES; stp_i++) stp_tx_budget[stp_i] = stp_txhold; - - stp_lag_map(); - if (stp_map_dirty) { - stp_map_dirty = 0; - for (stp_i = 0; stp_i < STP_LAG_BASE; stp_i++) { - if (!MACHINE_PORT_VALID(stp_i)) - continue; - if (stp_ent_of[stp_i] != stp_i) { - stp_loop_held[stp_i] = 0; - port_timers[stp_i] = 0; - } else { - stp_ent_apply(stp_i); - } - } - for (stp_i = STP_LAG_BASE; stp_i < STP_ENTITIES; stp_i++) { - if (stp_lag_mask[stp_i - STP_LAG_BASE]) { - stp_ent_apply(stp_i); - } else { - stp_pflags[stp_i] &= ~(STP_PF_OPEREDGE | STP_PF_TRIPPED); - stp_loop_held[stp_i] = 0; - port_timers[stp_i] = 0; - } - } - if (stp_root_port != 0xff && !stp_ent_active(stp_root_port)) - stp_claim_root(); - } + stp_tc_clock(); /* Link supervision. Without this the state machine never learns * that a port lost carrier: it keeps the port in forwarding, keeps @@ -742,44 +1181,64 @@ void stp_timers(void) __banked * yet losing a link is the most ordinary topology change there is. * Once per second is soon enough, and it keeps register reads out * of the 50 Hz tick. */ - reg_read_m(RTL837X_REG_LINKS_STS); - stp_link_now = (uint16_t)sfr_data[1] | ((uint16_t)sfr_data[2] << 8); - { - __xdata static uint16_t eff; - eff = 0; - for (stp_i = MACHINE_MIN_PORT; stp_i <= MACHINE_MAX_PORT; stp_i++) - if ((stp_link_now >> stp_i) & 1) - eff |= (uint16_t)1 << stp_ent_of[stp_i]; - stp_link_now = eff; + reg_read_m(RTL837X_REG_LINKS); + for (stp_i = MACHINE_MIN_PORT; stp_i <= MACHINE_MAX_PORT; stp_i++) { + if (stp_i == 8) + reg_read_m(RTL837X_REG_LINKS_89); + stp_pspeed[stp_i] = (stp_i & 1) + ? (sfr_data[3 - ((stp_i & 7) >> 1)] >> 4) + : (sfr_data[3 - ((stp_i & 7) >> 1)] & 0xf); } + stp_lag_map(); + stp_links_read(); + if (stp_map_changed) { + for (stp_i = 0; stp_i < STP_ENTITIES; stp_i++) + if ((stp_map_changed >> stp_i) & 1) + stp_ent_reset(stp_i); + stp_map_changed = 0; + } + if (stp_link_now != stp_link_prev) { for (stp_i = 0; stp_i < STP_ENTITIES; stp_i++) { - if (stp_i < STP_PORTS && !MACHINE_PORT_VALID(stp_i)) - continue; - if (stp_i < STP_PORTS && stp_ent_of[stp_i] != stp_i) + if (!stp_ent_active(stp_i)) continue; if (!(stp_pflags[stp_i] & STP_PF_ENABLED)) continue; if (!((stp_link_now ^ stp_link_prev) >> stp_i & 1)) continue; /* Either way the port must stop forwarding first. */ - stp_state_set(stp_i, STP_ST_BLOCKING); + stp_state_set(stp_i, 0b01); if ((stp_link_now >> stp_i) & 1) { /* Carrier back: re-run the listen period rather than * forwarding straight away - the segment may have been * rewired while we were down. Auto edge still applies. */ - port_timers[stp_i] = (uint16_t)stp_fwddelay_s * STP_HZ; + port_timers[stp_i] = FWD_TICKS; stp_pflags[stp_i] &= ~STP_PF_OPEREDGE; + stp_alt &= ~((uint16_t)1 << stp_i); stp_bpdu_age[stp_i] = 0; + stp_newinfo |= (uint16_t)1 << stp_i; + stp_legacy &= ~((uint16_t)1 << stp_i); + stp_seen_stp &= ~((uint16_t)1 << stp_i); + stp_seen_rstp &= ~((uint16_t)1 << stp_i); + stp_mdelay[stp_i] = STP_MIGRATE; } else { port_timers[stp_i] = 0; + stp_info_while[stp_i] = 0; + stp_rrwhile[stp_i] = 0; + stp_reroot &= ~((uint16_t)1 << stp_i); + stp_backup &= ~((uint16_t)1 << stp_i); + stp_alt_agreed &= ~((uint16_t)1 << stp_i); + stp_heard &= ~((uint16_t)1 << stp_i); + stp_alt &= ~((uint16_t)1 << stp_i); print_string("STP: link down, port blocking "); print_port_nl(stp_i); - stp_topology_change(stp_i); + stp_forget(stp_i); + stp_tcwhile[stp_i] = 0; } } stp_link_prev = stp_link_now; } + stp_reselect(); } for (stp_i = 0; stp_i < STP_ENTITIES; stp_i++) { @@ -788,6 +1247,19 @@ void stp_timers(void) __banked if (stp_bpdu_age[stp_i] < 0xffff) stp_bpdu_age[stp_i]++; + if (stp_info_while[stp_i] && !--stp_info_while[stp_i]) + stp_reselect_due = 1; + if (stp_tcwhile[stp_i]) + stp_tcwhile[stp_i]--; + if (stp_i == stp_root_port) + stp_rrwhile[stp_i] = FWD_TICKS; + else if (stp_rrwhile[stp_i] && !--stp_rrwhile[stp_i]) + stp_reroot &= ~((uint16_t)1 << stp_i); + if ((stp_backup >> stp_i) & 1) + stp_rbwhile[stp_i] = (uint16_t)2 * stp_hello_s * STP_HZ; + else if (stp_rbwhile[stp_i]) + stp_rbwhile[stp_i]--; + stp_migrate_check(stp_i); /* Periodic hello */ if (port_hello[stp_i]) @@ -798,43 +1270,56 @@ void stp_timers(void) __banked * where our own root information comes FROM, and echoing it back * there just feeds the upstream bridge its own data (and looks * like a competing designated bridge on that segment). */ - if (stp_i != stp_root_port) + if (((stp_agree >> stp_i) & 1) || (!((stp_alt >> stp_i) & 1) && (stp_i != stp_root_port || stp_tcwhile[stp_i]))) + stp_cnf_send(stp_i); + } + if ((stp_newinfo >> stp_i) & 1) { + stp_newinfo &= ~((uint16_t)1 << stp_i); + if (((stp_agree >> stp_i) & 1) || (!((stp_alt >> stp_i) & 1) && (stp_i != stp_root_port || stp_tcwhile[stp_i]))) stp_cnf_send(stp_i); } /* Promote a port out of blocking once its listen period expires * with no reason to stay blocked (no better root heard: we are * the designated bridge on that port). */ + if (port_timers[stp_i] && ((stp_alt >> stp_i) & 1)) + port_timers[stp_i] = 0; + if (port_timers[stp_i]) { if (!--port_timers[stp_i]) { - stp_loop_held[stp_i] = 0; - stp_state_set(stp_i, STP_ST_FORWARDING); - print_string("STP: port forwarding "); - print_port_nl(stp_i); - stp_topology_change(stp_i); + if (((stp_reroot >> stp_i) & 1) && stp_rrwhile[stp_i]) { + port_timers[stp_i] = stp_rrwhile[stp_i] + 1; + } else if (stp_state_get(stp_i) == 0b01) { + stp_state_set(stp_i, 0b10); + port_timers[stp_i] = FWD_TICKS; + print_string("STP: port learning "); + print_port_nl(stp_i); + } else { + stp_loop_held[stp_i] = 0; + stp_backup &= ~((uint16_t)1 << stp_i); + stp_state_set(stp_i, 0b11); + print_string("STP: port forwarding "); + print_port_nl(stp_i); + stp_tc_detected(stp_i); + } } else if ((stp_pflags[stp_i] & STP_PF_AUTOEDGE) && !stp_loop_held[stp_i] + && !((stp_heard >> stp_i) & 1) && stp_bpdu_age[stp_i] > STP_EDGE_DELAY) { /* Auto edge: nothing talks (R)STP on this port - it is * host-facing, go to forwarding without the full wait. */ port_timers[stp_i] = 0; stp_pflags[stp_i] |= STP_PF_OPEREDGE; - stp_state_set(stp_i, STP_ST_FORWARDING); + stp_state_set(stp_i, 0b11); print_string("STP: edge port forwarding "); print_port_nl(stp_i); } } } - if (stp_tc_while) - stp_tc_while--; - - /* Age out a root that went silent: reclaim the tree. */ - if (stp_root_port != 0xff - && stp_bpdu_age[stp_root_port] > (uint16_t)stp_maxage_s * STP_HZ) { - print_string("STP: root aged out, claiming root\n"); - stp_claim_root(); - stp_tc_count++; + if (stp_reselect_due) { + stp_reselect_due = 0; + stp_reselect(); } } @@ -849,11 +1334,13 @@ void stp_defaults(void) __banked stp_fwddelay_s = 15; stp_rstp = 1; stp_txhold = 6; + stp_pcost_short = 0; + stp_bpdu_filter = 0; for (stp_i = 0; stp_i < STP_PORTS; stp_i++) stp_ent_of[stp_i] = stp_i; for (stp_i = 0; stp_i < STP_LAG_COUNT; stp_i++) stp_lag_mask[stp_i] = 0; - stp_map_dirty = 0; + stp_map_changed = 0; for (stp_i = 0; stp_i < STP_ENTITIES; stp_i++) { /* enabled, auto-edge on: host-facing ports go forwarding after * 3 s of BPDU silence instead of the full forward delay */ @@ -864,9 +1351,24 @@ void stp_defaults(void) __banked port_timers[stp_i] = 0; port_hello[stp_i] = 0; stp_tx_budget[stp_i] = 6; + stp_info_while[stp_i] = 0; + stp_tcwhile[stp_i] = 0; + stp_mdelay[stp_i] = 0; + stp_rrwhile[stp_i] = 0; + stp_rbwhile[stp_i] = 0; } + stp_reroot = 0; + stp_newinfo = 0; + stp_legacy = 0; + stp_seen_stp = 0; + stp_seen_rstp = 0; + stp_agree = 0; + stp_synced_root = 0xff; + stp_backup = 0; + stp_alt_agreed = 0; + stp_heard = 0; + stp_alt = 0; stp_tc_count = 0; - stp_tc_while = 0; stp_claim_root(); } @@ -897,54 +1399,54 @@ static void stp_fdb_update(__xdata uint16_t pmask) } +static void stp_times_check(void) __reentrant +{ + if ((uint16_t)2 * (stp_fwddelay_s - 1) >= stp_maxage_s + && stp_maxage_s >= (uint16_t)2 * (stp_hello_s + 1)) + return; + print_string("STP: timers break 2*(fwd-1) >= maxage >= 2*(hello+1), using 2/20/15\n"); + stp_hello_s = 2; + stp_maxage_s = 20; + stp_fwddelay_s = 15; +} + + +void stp_counters_clear(void) __banked __reentrant +{ + for (uint8_t i = 0; i < STP_CNT_N * STP_ENTITIES; i++) + ((__xdata uint32_t *)stp_cnt)[i] = 0; +} + + void stp_setup(void) __banked { + stp_times_check(); + stp_counters_clear(); + stp_tc_seen = stp_tc_count; + stp_tc_secs = 0; print_string("Enabling STP: "); - stp_lag_map(); + stp_claim_root(); + stp_newinfo = 0; + stp_reroot = 0; + stp_legacy = 0; + stp_seen_stp = 0; + stp_seen_rstp = 0; + stp_agree = 0; + stp_synced_root = 0xff; + stp_backup = 0; + stp_alt_agreed = 0; sfr_data[0] = sfr_data[1] = sfr_data[2] = sfr_data[3] = 0; - for (stp_i = 0; stp_i < STP_ENTITIES; stp_i++) { - stp_pflags[stp_i] &= ~(STP_PF_OPEREDGE | STP_PF_TRIPPED); - stp_loop_held[stp_i] = 0; - stp_bpdu_age[stp_i] = 0; - stp_tx_budget[stp_i] = stp_txhold; - stp_tx_count[stp_i] = 0; - port_timers[stp_i] = 0; - port_hello[stp_i] = (uint16_t)stp_hello_s * STP_HZ; - if (stp_i < STP_PORTS && !MACHINE_PORT_VALID(stp_i)) - continue; - if (stp_i >= STP_LAG_BASE && !stp_lag_mask[stp_i - STP_LAG_BASE]) - continue; - if (stp_i < STP_PORTS && stp_ent_of[stp_i] != stp_i) - continue; - if (!(stp_pflags[stp_i] & STP_PF_ENABLED) - || (stp_pflags[stp_i] & STP_PF_ADMEDGE)) { - /* not participating, or admin edge: forwarding immediately */ - if (stp_pflags[stp_i] & STP_PF_ADMEDGE) - stp_pflags[stp_i] |= STP_PF_OPEREDGE; - stp_state_bits(stp_i, STP_ST_FORWARDING); - port_timers[stp_i] = 0; - } else { - /* listen first: blocking until the forward-delay expires */ - stp_state_bits(stp_i, STP_ST_BLOCKING); - port_timers[stp_i] = (uint16_t)stp_fwddelay_s * STP_HZ; - } - } sfr_data[1] |= 0x0c; // Do not block the CPU port (bits 3:2 of byte 1 = port 9) reg_write_m(RTL837X_MSTP_STATES); + stp_lag_map(); + stp_links_read(); + stp_link_prev = stp_link_now; + stp_map_changed = 0; + for (stp_i = 0; stp_i < STP_ENTITIES; stp_i++) + stp_ent_reset(stp_i); print_reg(RTL837X_MSTP_STATES); write_char('\n'); - /* Seed the carrier bitmap, so turning STP on does not report every - * port that was already down as a fresh topology change. */ - reg_read_m(RTL837X_REG_LINKS_STS); - stp_link_now = (uint16_t)sfr_data[1] | ((uint16_t)sfr_data[2] << 8); - stp_link_prev = 0; - for (stp_i = MACHINE_MIN_PORT; stp_i <= MACHINE_MAX_PORT; stp_i++) - if ((stp_link_now >> stp_i) & 1) - stp_link_prev |= (uint16_t)1 << stp_ent_of[stp_i]; - - stp_claim_root(); - trap_rma_set(RTL837X_RMA0_CONF, RTL837X_RMA_ACT_TRAP); } @@ -952,11 +1454,8 @@ void stp_setup(void) __banked void stp_off(void) __banked { sfr_data[0] = sfr_data[1] = sfr_data[2] = sfr_data[3] = 0; - for (stp_i = MACHINE_MIN_PORT; stp_i <= MACHINE_MAX_PORT; stp_i++) { - // Set STP port state to forwarding - // States are: 00 disable, 01 blocking, 10 learning, 11 forwarding - sfr_data[3 - (stp_i >> 2)] |= (uint8_t)(STP_ST_FORWARDING << ((stp_i << 1) & 0x7)); - } + for (stp_i = MACHINE_MIN_PORT; stp_i <= MACHINE_MAX_PORT; stp_i++) + sfr_data[3 - (stp_i >> 2)] |= (uint8_t)(0b11 << ((stp_i << 1) & 0x7)); for (stp_i = 0; stp_i < STP_ENTITIES; stp_i++) { stp_pflags[stp_i] &= ~(STP_PF_OPEREDGE | STP_PF_TRIPPED); stp_loop_held[stp_i] = 0; @@ -965,173 +1464,55 @@ void stp_off(void) __banked sfr_data[1] |= 0x0c; // Do not block the CPU port (bits 3:2 of byte 1 = port 9) reg_write_m(RTL837X_MSTP_STATES); - /* Restore BPDU transparency: flood them again like an unmanaged switch. */ - stp_fdb_update(PMASK_CPU | (machine_detected.isRTL8373 ? PMASK_9 : PMASK_6)); - trap_rma_set(RTL837X_RMA0_CONF, RTL837X_RMA_ACT_FORWARD); -} - - -void stp_parse(void) __banked __reentrant -{ - uint8_t ent; - - if (cmd_compare(1, "on")) { - print_string("STP enabled\n"); - stp_enabled = 1; - stp_setup(); - return; - } - if (cmd_compare(1, "off")) { - print_string("STP disabled\n"); - stp_off(); - stp_enabled = 0; - return; - } - if (cmd_compare(1, "status")) { - stp_status(); - return; - } - if (cmd_words_len < 3) - goto err; - - if (cmd_compare(1, "port") || cmd_compare(1, "lag")) { - stp_lag_map(); - if (cmd_words_len < 4) - goto err; - if (cmd_compare(1, "lag")) { - if (!atoi_byte(cmd_words_b[2])) - goto err; - if (!atoi_results_u8 || atoi_results_u8 > STP_LAG_COUNT) - goto err; - ent = STP_LAG_BASE + atoi_results_u8 - 1; - } else { - if (!cmd_parse_port_separator(cmd_words_b[2])) - goto err; - ent = atoi_results_u8; - if (stp_ent_of[ent] != ent) { - print_string("Port belongs to a LAG, configure it as lag "); - print_byte(stp_ent_of[ent] - STP_LAG_BASE + 1); - write_char('\n'); - return; - } - } - if (cmd_words_len < 5 && !cmd_compare(3, "on") && !cmd_compare(3, "off")) - goto err; - if (cmd_compare(3, "on")) { - stp_pflags[ent] |= STP_PF_ENABLED; - stp_pflags[ent] &= ~STP_PF_TRIPPED; - if (stp_enabled) { /* (re)join: listen first */ - stp_state_set(ent, STP_ST_BLOCKING); - port_timers[ent] = (uint16_t)stp_fwddelay_s * STP_HZ; - } - } else if (cmd_compare(3, "off")) { - stp_pflags[ent] &= ~STP_PF_ENABLED; - if (stp_enabled) - stp_state_set(ent, STP_ST_FORWARDING); /* plain forwarding */ - } else if (cmd_compare(3, "edge")) { - /* Also drop the *operational* edge flag: it is what exempts the - * ent from topology changes and lets it skip the listen period, - * so leaving it set would keep the old behaviour until the next - * "stp off"/"stp on". An admin edge is operational immediately. */ - stp_pflags[ent] &= ~(STP_PF_ADMEDGE | STP_PF_AUTOEDGE | STP_PF_OPEREDGE); - if (cmd_compare(4, "on")) - stp_pflags[ent] |= STP_PF_ADMEDGE | STP_PF_OPEREDGE; - else if (cmd_compare(4, "auto")) - stp_pflags[ent] |= STP_PF_AUTOEDGE; - else if (!cmd_compare(4, "off")) - goto err; - } else if (cmd_compare(3, "cost")) { - /* raw 802.1D value, 0..200000000; 0 = auto (speed-based) */ - stp_cost_scratch = 0; - { - __xdata uint8_t *cp = &cmd_buffer[cmd_words_b[4]]; - if (*cp < '0' || *cp > '9') - goto err; - while (*cp >= '0' && *cp <= '9') { - stp_cost_scratch = stp_cost_scratch * 10 + (*cp - '0'); - cp++; - } - } - if (stp_cost_scratch > 200000000UL) - goto err; - stp_pcost[ent] = stp_cost_scratch; - } else if (cmd_compare(3, "p2p")) { - if (cmd_compare(4, "auto")) - stp_pp2p[ent] = 0; - else if (cmd_compare(4, "on")) - stp_pp2p[ent] = 1; - else if (cmd_compare(4, "off")) - stp_pp2p[ent] = 2; - else - goto err; - } else if (cmd_compare(3, "prio")) { - if (!atoi_byte(cmd_words_b[4])) - goto err; - if (atoi_results_u8 > 240 || (atoi_results_u8 & 0x0f)) - goto err; - stp_pprio[ent] = atoi_results_u8; - } else if (cmd_compare(3, "guard")) { - stp_pflags[ent] &= ~(STP_PF_BPDUGUARD | STP_PF_ROOTGUARD); - if (cmd_compare(4, "bpdu")) - stp_pflags[ent] |= STP_PF_BPDUGUARD; - else if (cmd_compare(4, "root")) - stp_pflags[ent] |= STP_PF_ROOTGUARD; - else if (!cmd_compare(4, "none")) - goto err; - } else if (cmd_compare(3, "filter")) { - if (cmd_compare(4, "on")) - stp_pflags[ent] |= STP_PF_FILTER; - else if (cmd_compare(4, "off")) - stp_pflags[ent] &= ~STP_PF_FILTER; - else - goto err; - } else { - goto err; - } - return; - } - - if (!atoi_byte(cmd_words_b[2])) { - if (cmd_compare(1, "version")) { - if (cmd_compare(2, "rstp")) - stp_rstp = 1; - else if (cmd_compare(2, "stp")) - stp_rstp = 0; - else - goto err; - return; - } - goto err; - } - stp_scratch = atoi_results_u8; - - if (cmd_compare(1, "prio")) { - if (stp_scratch > 15) - goto err; - stp_prio = stp_scratch << 4; /* n * 4096, as the BPDU's high byte */ - if (stp_root_port == 0xff) - stp_claim_root(); /* re-announce with the new priority */ - } else if (cmd_compare(1, "hello")) { - if (stp_scratch < 1 || stp_scratch > 10) - goto err; - stp_hello_s = stp_scratch; - } else if (cmd_compare(1, "maxage")) { - if (stp_scratch < 6 || stp_scratch > 40) - goto err; - stp_maxage_s = stp_scratch; - } else if (cmd_compare(1, "fwd")) { - if (stp_scratch < 4 || stp_scratch > 30) - goto err; - stp_fwddelay_s = stp_scratch; - } else if (cmd_compare(1, "txhold")) { - if (stp_scratch < 1 || stp_scratch > 10) - goto err; - stp_txhold = stp_scratch; + if (stp_bpdu_filter) { + stp_fdb_update(PMASK_CPU); + trap_rma_set(RTL837X_RMA0_CONF, RTL837X_RMA_ACT_DROP); } else { - goto err; + /* Restore BPDU transparency: flood them again like an unmanaged switch. */ + stp_fdb_update(PMASK_CPU | (machine_detected.isRTL8373 ? PMASK_9 : PMASK_6)); + trap_rma_set(RTL837X_RMA0_CONF, RTL837X_RMA_ACT_FORWARD); } - return; -err: - err_status = ERR_INVALID_ARGUMENT; - print_string("Error: stp on|off|status | prio <0-15> | hello <1-10> | maxage <6-40> | fwd <4-30> | txhold <1-10> | version rstp|stp | port <1-9>|lag <1-4> on|off|edge|cost|prio|guard|filter ...\n"); +} + + +void stp_port_admin(uint8_t port, uint8_t on) __banked +{ + if (on) { + stp_state_set(port, 0b01); + port_timers[port] = FWD_TICKS; + } else { + stp_state_set(port, 0b11); + } +} + + +uint8_t stp_port_role(uint8_t port) __banked +{ + if (!(stp_pflags[port] & STP_PF_ENABLED) || (stp_pflags[port] & STP_PF_TRIPPED) + || !((stp_link_prev >> port) & 1)) + return 0; + if (port == stp_root_port) + return 1; + if ((stp_backup >> port) & 1) + return 4; + if ((stp_alt >> port) & 1) + return 3; + return 2; +} + + +void stp_port_mcheck(uint8_t port) __banked +{ + stp_legacy &= ~((uint16_t)1 << port); + stp_seen_stp &= ~((uint16_t)1 << port); + stp_seen_rstp &= ~((uint16_t)1 << port); + stp_mdelay[port] = STP_MIGRATE; + stp_newinfo |= (uint16_t)1 << port; +} + + +void stp_prio_apply(void) __banked +{ + if (stp_root_port == 0xff) + stp_claim_root(); } diff --git a/rtl837x_stp.h b/rtl837x_stp.h index 95bac8af..5b60c5ea 100644 --- a/rtl837x_stp.h +++ b/rtl837x_stp.h @@ -2,13 +2,21 @@ #define _RTL837X_STP_H_ #include -#include "rtl837x_common.h" void stp_in(void) __banked; void stp_setup(void) __banked; void stp_timers(void) __banked; void stp_off(void) __banked; void stp_parse(void) __banked __reentrant; void stp_defaults(void) __banked; +void stp_status(void) __banked; +void stp_port_admin(uint8_t port, uint8_t on) __banked; +void stp_prio_apply(void) __banked; +void stp_port_mcheck(uint8_t port) __banked; +uint8_t stp_port_role(uint8_t port) __banked; +uint8_t stp_port_state(uint8_t port) __banked; +uint8_t stp_ent_id(uint8_t e) __banked; +void stp_lag_map(void) __banked; +void stp_counters_clear(void) __banked __reentrant; /* Tick rate of stp_timers(), also used by the web UI. */ #define STP_HZ 50 @@ -43,22 +51,38 @@ extern __xdata uint8_t stp_txhold; #define STP_PF_TRIPPED 0x80 /* runtime: disabled by BPDU guard */ extern __xdata uint8_t stp_pflags[STP_ENTITIES]; -extern __xdata uint32_t stp_pcost[STP_ENTITIES]; /* path cost; 0 = auto (20000) */ +extern __xdata uint32_t stp_pcost[STP_ENTITIES]; extern __xdata uint8_t stp_pprio[STP_ENTITIES]; extern __xdata uint8_t stp_pp2p[STP_ENTITIES]; -extern __xdata uint8_t stp_ent_of[STP_PORTS]; /* the STP entity a port answers to: itself, or its lag */ -extern __xdata uint16_t stp_lag_mask[STP_LAG_COUNT]; /* member ports of each lag; 0 = not part of STP */ +extern __xdata uint8_t stp_ent_of[STP_PORTS]; +extern __xdata uint16_t stp_lag_mask[STP_LAG_COUNT]; -/* Last-heard designated info per port (from received BPDUs); consult - * stp_bpdu_age to decide whether it is still current. */ extern __xdata struct bridge stp_dbridge[STP_ENTITIES]; extern __xdata uint16_t stp_dpid[STP_ENTITIES]; extern __xdata uint32_t stp_dcost[STP_ENTITIES]; -extern __xdata uint16_t stp_bpdu_age[STP_ENTITIES]; /* ticks since a BPDU was heard */ +extern __xdata uint16_t stp_bpdu_age[STP_ENTITIES]; +#define STP_CNT_RX 0 /* BPDUs taken in on an STP port */ +#define STP_CNT_TX 1 /* BPDUs put on the wire */ +#define STP_CNT_TCRX 2 /* BPDUs received with the TC flag, TCNs */ +#define STP_CNT_TCTX 3 /* BPDUs sent with the TC flag, TCNs */ +#define STP_CNT_N 4 +extern __xdata uint32_t stp_cnt[STP_CNT_N][STP_ENTITIES]; +extern __xdata uint16_t stp_info_while[STP_ENTITIES]; extern __xdata struct bridge root_bridge; extern __xdata uint32_t root_bridge_cost; extern __xdata uint8_t stp_root_port; +extern __xdata uint8_t stp_root_maxage; +extern __xdata uint8_t stp_root_fwd; +extern __xdata uint16_t stp_link_prev; +extern __xdata uint16_t stp_alt; +extern __xdata uint16_t stp_backup; +extern __xdata uint16_t stp_legacy; +extern __xdata uint16_t stp_rrwhile[STP_ENTITIES]; +extern __xdata uint16_t stp_rbwhile[STP_ENTITIES]; extern __xdata uint16_t stp_tc_count; +extern __xdata uint32_t stp_tc_secs; +extern __xdata uint8_t stp_pcost_short; +extern __xdata uint8_t stp_bpdu_filter; #endif diff --git a/rtl837x_stp_cli.c b/rtl837x_stp_cli.c new file mode 100644 index 00000000..107dbc21 --- /dev/null +++ b/rtl837x_stp_cli.c @@ -0,0 +1,228 @@ +/* + * Command line interface of the Spanning Tree Protocol for the RTL837x platform + * This code is in the Public Domain + */ + +#include +#include "rtl837x_common.h" +#include "rtl837x_stp.h" +#include "machine.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]; +uint8_t cmd_compare(uint8_t start, __code const uint8_t * cmd); +uint8_t atoi_byte(uint8_t idx); +uint8_t cmd_parse_port_separator(uint8_t idx); +extern __xdata uint8_t atoi_results_u8; +extern __xdata char save_cmd; +extern __xdata uint8_t err_status; + +#define TIMES_OK(h, m, f) ((uint16_t)2 * ((f) - 1) >= (m) && (m) >= (uint16_t)2 * ((h) + 1)) + +__xdata uint32_t stp_cli_cost; +__xdata uint8_t stp_cli_val; + + +void stp_parse(void) __banked __reentrant +{ + uint8_t port; + + if (cmd_compare(1, "on")) { + print_string("STP enabled\n"); + stp_enabled = 1; + stp_setup(); + return; + } + if (cmd_compare(1, "off")) { + print_string("STP disabled\n"); + stp_off(); + stp_enabled = 0; + return; + } + if (cmd_compare(1, "status")) { + stp_status(); + return; + } + if (cmd_compare(1, "clear")) { + stp_counters_clear(); + return; + } + if (cmd_words_len < 3) + goto err; + + if (cmd_compare(1, "port") || cmd_compare(1, "lag")) { + if (cmd_words_len < 4) + goto err; + stp_lag_map(); + if (cmd_compare(1, "lag")) { + if (!atoi_byte(cmd_words_b[2]) || !atoi_results_u8 || atoi_results_u8 > STP_LAG_COUNT) + goto err; + port = STP_LAG_BASE - 1 + atoi_results_u8; + } else { + if (!cmd_parse_port_separator(cmd_words_b[2])) + goto err; + port = atoi_results_u8; + if (stp_ent_of[port] != port) { + print_string("Error: the port is in lag "); + write_char('1' + stp_ent_of[port] - STP_LAG_BASE); + write_char('\n'); + err_status = ERR_INVALID_ARGUMENT; + return; + } + } + if (cmd_words_len < 5 && !cmd_compare(3, "on") && !cmd_compare(3, "off") + && !cmd_compare(3, "mcheck")) + goto err; + if (cmd_compare(3, "on")) { + stp_pflags[port] |= STP_PF_ENABLED; + stp_pflags[port] &= ~STP_PF_TRIPPED; + if (stp_enabled) { /* (re)join: listen first */ + stp_port_admin(port, 1); + } + } else if (cmd_compare(3, "mcheck")) { + if (stp_enabled) + stp_port_mcheck(port); + } else if (cmd_compare(3, "off")) { + stp_pflags[port] &= ~STP_PF_ENABLED; + if (stp_enabled) + stp_port_admin(port, 0); /* plain forwarding */ + } else if (cmd_compare(3, "edge")) { + /* Also drop the *operational* edge flag: it is what exempts the + * port from topology changes and lets it skip the listen period, + * so leaving it set would keep the old behaviour until the next + * "stp off"/"stp on". An admin edge is operational immediately. */ + stp_pflags[port] &= ~(STP_PF_ADMEDGE | STP_PF_AUTOEDGE | STP_PF_OPEREDGE); + if (cmd_compare(4, "on")) + stp_pflags[port] |= STP_PF_ADMEDGE | STP_PF_OPEREDGE; + else if (cmd_compare(4, "auto")) + stp_pflags[port] |= STP_PF_AUTOEDGE; + else if (!cmd_compare(4, "off")) + goto err; + } else if (cmd_compare(3, "cost")) { + /* raw 802.1D value, 0..200000000; 0 = auto (speed-based) */ + stp_cli_cost = 0; + { + __xdata uint8_t *cp = &cmd_buffer[cmd_words_b[4]]; + if (*cp < '0' || *cp > '9') + goto err; + while (*cp >= '0' && *cp <= '9') { + stp_cli_cost = stp_cli_cost * 10 + (*cp - '0'); + cp++; + } + } + if (stp_cli_cost > 200000000UL) + goto err; + stp_pcost[port] = stp_cli_cost; + } else if (cmd_compare(3, "p2p")) { + if (cmd_compare(4, "auto")) + stp_pp2p[port] = 0; + else if (cmd_compare(4, "on")) + stp_pp2p[port] = 1; + else if (cmd_compare(4, "off")) + stp_pp2p[port] = 2; + else + goto err; + } else if (cmd_compare(3, "prio")) { + if (!atoi_byte(cmd_words_b[4])) + goto err; + if (atoi_results_u8 > 240 || (atoi_results_u8 & 0x0f)) + goto err; + stp_pprio[port] = atoi_results_u8; + } else if (cmd_compare(3, "guard")) { + stp_pflags[port] &= ~(STP_PF_BPDUGUARD | STP_PF_ROOTGUARD); + if (cmd_compare(4, "bpdu")) + stp_pflags[port] |= STP_PF_BPDUGUARD; + else if (cmd_compare(4, "root")) + stp_pflags[port] |= STP_PF_ROOTGUARD; + else if (!cmd_compare(4, "none")) + goto err; + } else if (cmd_compare(3, "filter")) { + if (cmd_compare(4, "on")) + stp_pflags[port] |= STP_PF_FILTER; + else if (cmd_compare(4, "off")) + stp_pflags[port] &= ~STP_PF_FILTER; + else + goto err; + } else { + goto err; + } + return; + } + + if (!atoi_byte(cmd_words_b[2])) { + if (cmd_compare(1, "version")) { + if (cmd_compare(2, "rstp")) + stp_rstp = 1; + else if (cmd_compare(2, "stp")) + stp_rstp = 0; + else + goto err; + return; + } + if (cmd_compare(1, "pathcost")) { + if (cmd_compare(2, "long")) + stp_pcost_short = 0; + else if (cmd_compare(2, "short")) + stp_pcost_short = 1; + else + goto err; + return; + } + if (cmd_compare(1, "bpdu")) { + if (cmd_compare(2, "filter")) + stp_bpdu_filter = 1; + else if (cmd_compare(2, "flood")) + stp_bpdu_filter = 0; + else + goto err; + if (!stp_enabled) + stp_off(); + return; + } + goto err; + } + stp_cli_val = atoi_results_u8; + + if (cmd_compare(1, "prio")) { + if (stp_cli_val > 15) + goto err; + stp_prio = stp_cli_val << 4; /* n * 4096, as the BPDU's high byte */ + stp_prio_apply(); + } else if (cmd_compare(1, "hello")) { + if (stp_cli_val < 1 || stp_cli_val > 10) + goto err; + if (save_cmd && !TIMES_OK(stp_cli_val, stp_maxage_s, stp_fwddelay_s)) + goto times; + stp_hello_s = stp_cli_val; + } else if (cmd_compare(1, "maxage")) { + if (stp_cli_val < 6 || stp_cli_val > 40) + goto err; + if (save_cmd && !TIMES_OK(stp_hello_s, stp_cli_val, stp_fwddelay_s)) + goto times; + stp_maxage_s = stp_cli_val; + } else if (cmd_compare(1, "fwd")) { + if (stp_cli_val < 4 || stp_cli_val > 30) + goto err; + if (save_cmd && !TIMES_OK(stp_hello_s, stp_maxage_s, stp_cli_val)) + goto times; + stp_fwddelay_s = stp_cli_val; + } else if (cmd_compare(1, "txhold")) { + if (stp_cli_val < 1 || stp_cli_val > 10) + goto err; + stp_txhold = stp_cli_val; + } else { + goto err; + } + return; +times: + print_string("Error: needs 2*(fwd-1) >= maxage >= 2*(hello+1)\n"); + err_status = ERR_INVALID_ARGUMENT; + return; +err: + err_status = ERR_INVALID_ARGUMENT; + print_string("Error: stp on|off|status|clear | prio <0-15> | hello <1-10> | maxage <6-40> | fwd <4-30> | txhold <1-10> | version rstp|stp | pathcost long|short | bpdu filter|flood | port <1-9>|lag <1-4> on|off|mcheck|edge|cost|prio|guard|filter ...\n"); +} diff --git a/test/env_tables.c b/test/env_tables.c index 494499ff..9fbb676b 100644 --- a/test/env_tables.c +++ b/test/env_tables.c @@ -121,6 +121,12 @@ uint8_t stp_pflags[STP_ENTITIES]; uint8_t stp_pp2p[STP_ENTITIES]; uint8_t stp_pprio[STP_ENTITIES]; uint8_t stp_prio = 0x80, stp_root_port = 0xff, stp_rstp = 1, stp_txhold = 6; +uint8_t stp_bpdu_filter, stp_pcost_short, stp_root_fwd = 15, stp_root_maxage = 20; +uint16_t stp_legacy, stp_link_prev = 0x3ff; +uint16_t stp_info_while[STP_ENTITIES]; +uint32_t stp_cnt[STP_CNT_N][STP_ENTITIES]; +uint32_t stp_tc_secs; +uint8_t stp_ent_id(uint8_t e) { return e >= STP_LAG_BASE ? e + 101 - STP_LAG_BASE : machine.log_to_phys_port[e]; } /* ---- SFP: no module present, reads fail ---- */ uint8_t sfp_buf[16]; diff --git a/test/stp/Makefile b/test/stp/Makefile new file mode 100644 index 00000000..9fda7ac2 --- /dev/null +++ b/test/stp/Makefile @@ -0,0 +1,21 @@ +CC ?= cc +CFLAGS = -std=c11 -Wall -Wextra -Wno-unknown-pragmas \ + -D__xdata= -D__code= -D__banked= -D__reentrant= \ + -Ishim -I../.. + +all: build/stp_sim + ./build/stp_sim + +verbose: build/stp_sim + ./build/stp_sim -v + +build/stp_sim: stp_sim.c ../../rtl837x_stp.c ../../rtl837x_stp_cli.c ../../rtl837x_stp.h shim/*.h + mkdir -p build + cp ../../rtl837x_stp.c build/stp_under_test.c + cp ../../rtl837x_stp_cli.c build/stp_cli_under_test.c + $(CC) $(CFLAGS) -o $@ stp_sim.c build/stp_under_test.c build/stp_cli_under_test.c + +clean: + rm -rf build + +.PHONY: all verbose clean diff --git a/test/stp/shim/machine.h b/test/stp/shim/machine.h new file mode 100644 index 00000000..bce13895 --- /dev/null +++ b/test/stp/shim/machine.h @@ -0,0 +1,12 @@ +#ifndef _SHIM_MACHINE_H_ +#define _SHIM_MACHINE_H_ +#include +#define MACHINE_MIN_PORT 0 +#define MACHINE_MAX_PORT 8 +#define MACHINE_PORT_VALID(p) \ + ((uint8_t)((p) - MACHINE_MIN_PORT) <= (MACHINE_MAX_PORT - MACHINE_MIN_PORT)) +struct machine { uint8_t log_to_phys_port[9]; }; +extern const struct machine machine; +struct machine_runtime { uint8_t isRTL8373; }; +extern struct machine_runtime machine_detected; +#endif diff --git a/test/stp/shim/rtl837x_common.h b/test/stp/shim/rtl837x_common.h new file mode 100644 index 00000000..6f124245 --- /dev/null +++ b/test/stp/shim/rtl837x_common.h @@ -0,0 +1,50 @@ +#ifndef _SHIM_RTL837X_COMMON_H_ +#define _SHIM_RTL837X_COMMON_H_ + +#include +#include +#include + +struct rtl_tag { + uint16_t tag; + uint8_t version; + uint8_t reason; + uint16_t flags; + uint16_t pmask; /* TX: port bitmask; RX: 4-bit port number */ +}; + +struct vlan_tag { + uint16_t svlan; + uint16_t vlan; +}; + +#define RTL_FRAME_TAG_ID 0x8899 +#define RTL_FRAME_TAG_VERSION 0x04 +#define RTL_FRAME_DESC_SIZE 12 +#define RTL_TAG_LEARN_DIS 0x0020 + +#define PMASK_9 0x1ff +#define PMASK_6 0x1f8 +#define PMASK_CPU 0x200 + +#define CMD_BUF_SIZE 128 +#define CPU_PORT 9 + +#define ERR_OK 0 +#define ERR_INVALID_ARGUMENT 3 + +#define HTONS(n) ((uint16_t)((((uint16_t)(n)) << 8) | (((uint16_t)(n)) >> 8))) + +extern bool stp_enabled; +void print_string(char *s); +void print_byte(uint8_t b); +void print_short(uint16_t v); +void print_long(uint32_t v); +void write_char(char c); +void itoa(uint8_t v); +void print_reg(uint16_t reg); +void reg_read_m(uint16_t addr); +void reg_write_m(uint16_t addr); +void tcpip_output(void); + +#endif diff --git a/test/stp/shim/rtl837x_port.h b/test/stp/shim/rtl837x_port.h new file mode 100644 index 00000000..4a33e7a3 --- /dev/null +++ b/test/stp/shim/rtl837x_port.h @@ -0,0 +1,11 @@ +#ifndef _SHIM_RTL837X_PORT_H_ +#define _SHIM_RTL837X_PORT_H_ +#include +#define VLAN_TAGGED 1 +int8_t vlan_get(uint16_t vlan); +uint16_t port_pvid_get(uint8_t port); +void port_l2mc_set(uint8_t mac_last, uint16_t vid, uint16_t pmask); +void port_l2_forget_port(uint8_t port); +uint16_t port_lag_members_get(uint8_t lag); +uint8_t port_ingress_filter_get(uint8_t port); +#endif diff --git a/test/stp/shim/rtl837x_regs.h b/test/stp/shim/rtl837x_regs.h new file mode 100644 index 00000000..16c1ab86 --- /dev/null +++ b/test/stp/shim/rtl837x_regs.h @@ -0,0 +1,9 @@ +#ifndef _SHIM_RTL837X_REGS_H_ +#define _SHIM_RTL837X_REGS_H_ + +#define RTL837X_MSTP_STATES 0x5310 +#define RTL837X_REG_LINKS_STS 0x63E8 +#define RTL837X_REG_LINKS 0x63f0 +#define RTL837X_REG_LINKS_89 0x63f4 + +#endif diff --git a/test/stp/shim/rtl837x_sfr.h b/test/stp/shim/rtl837x_sfr.h new file mode 100644 index 00000000..38560691 --- /dev/null +++ b/test/stp/shim/rtl837x_sfr.h @@ -0,0 +1,6 @@ +#ifndef _SHIM_RTL837X_SFR_H_ +#define _SHIM_RTL837X_SFR_H_ + +extern unsigned char sfr_data[4]; + +#endif diff --git a/test/stp/shim/uip.h b/test/stp/shim/uip.h new file mode 100644 index 00000000..e46f8a6e --- /dev/null +++ b/test/stp/shim/uip.h @@ -0,0 +1,8 @@ +#ifndef _SHIM_UIP_H_ +#define _SHIM_UIP_H_ +#include +#define UIP_CONF_BUFFER_SIZE 600 +struct uip_eth_addr { uint8_t addr[6]; }; +extern uint16_t uip_len; +void tcpip_output(void); +#endif diff --git a/test/stp/stp_sim.c b/test/stp/stp_sim.c new file mode 100644 index 00000000..d69702a8 --- /dev/null +++ b/test/stp/stp_sim.c @@ -0,0 +1,1633 @@ +#include +#include +#include +#include +#include + +#include "rtl837x_common.h" +#include "machine.h" +#include "uip.h" +#include "rtl837x_regs.h" +#include "rtl837x_stp.h" + +#define NPORTS 9 /* indices 0..8, front-panel 1..9 */ + +uint8_t uip_buf[UIP_CONF_BUFFER_ALLOC_SIZE]; +uint16_t uip_len; +struct uip_eth_addr uip_ethaddr = { .addr = {0x1c,0x2a,0xa3,0x1a,0x72,0x4e} }; +const struct machine machine = { + .log_to_phys_port = {1, 2, 3, 4, 5, 6, 7, 8, 9} }; +struct machine_runtime machine_detected = { .isRTL8373 = 1 }; +uint8_t sfr_data[4]; +bool stp_enabled; +uint16_t management_vlan; +uint8_t cmd_buffer[CMD_BUF_SIZE]; +uint8_t cmd_words_len; +uint8_t cmd_words_b[15]; +char save_cmd; +uint8_t err_status; +uint8_t atoi_results_u8; + +static int verbose; +void print_string(char *s) { if (verbose) fputs(s, stdout); } +void print_byte(uint8_t b) { if (verbose) printf("%02x", b); } +void print_short(uint16_t v) { if (verbose) printf("%04x", v); } +void print_long(uint32_t v) { if (verbose) printf("%08x", v); } +void write_char(char c) { if (verbose) putchar(c); } +void itoa(uint8_t v) { if (verbose) printf("%u", v); } +void print_reg(uint16_t reg) { (void)reg; } + +uint8_t cmd_compare(uint8_t start, uint8_t *cmd) +{ + if (start >= cmd_words_len) + return 0; + const char *w = (const char *)&cmd_buffer[cmd_words_b[start]]; + size_t n = strlen((const char *)cmd); + return strncmp(w, (const char *)cmd, n) == 0 && (w[n] == ' ' || w[n] == 0); +} +uint8_t atoi_byte(uint8_t idx) +{ + unsigned v = 0, n = 0; + while (cmd_buffer[idx + n] >= '0' && cmd_buffer[idx + n] <= '9') + v = v * 10 + (cmd_buffer[idx + n++] - '0'); + atoi_results_u8 = (uint8_t)v; + return (uint8_t)n; +} +uint8_t cmd_parse_port_separator(uint8_t idx) +{ + if (!atoi_byte(idx) || atoi_results_u8 < 1 || atoi_results_u8 > NPORTS) + return 0; + atoi_results_u8--; + return 1; +} +static void sim_cmd(const char *line) +{ + memset(cmd_buffer, 0, sizeof(cmd_buffer)); + strncpy((char *)cmd_buffer, line, sizeof(cmd_buffer) - 1); + cmd_words_len = 0; + for (uint8_t i = 0; cmd_buffer[i]; i++) + if (cmd_buffer[i] != ' ' && (i == 0 || cmd_buffer[i - 1] == ' ')) + cmd_words_b[cmd_words_len++] = i; + err_status = ERR_OK; + stp_parse(); +} +void execute_config(void) { } +int8_t vlan_get(uint16_t vlan) { (void)vlan; return -1; } +uint16_t port_pvid_get(uint8_t port) { (void)port; return 1; } +static int l2mc_calls; +static uint16_t l2mc_pmask; +void port_l2mc_set(uint8_t mac_last, uint16_t vid, uint16_t pmask) { (void)mac_last; (void)vid; l2mc_calls++; l2mc_pmask = pmask; } +uint8_t port_ingress_filter_get(uint8_t port) { (void)port; return 0; } + +static int flush_count[NPORTS]; +void port_l2_forget_port(uint8_t port) { if (port < NPORTS) flush_count[port]++; } + +static uint8_t mstp_reg[4]; +static uint16_t sim_lag[4]; +uint16_t port_lag_members_get(uint8_t lag) { return sim_lag[lag]; } +static uint16_t sim_links; +static uint8_t sim_speed[NPORTS]; /* nibble as the ASIC reports it: 2 = 1G, 4 = 10G */ +static int tx_frames[NPORTS]; + +struct sim_pkt_out { + uint8_t stp_addr[6]; + uint8_t src_addr[6]; + struct rtl_tag rtl_tag; + uint16_t msg_len; + uint8_t dsap; + uint8_t ssap; + uint8_t ctrl; + uint16_t proto; + uint8_t version; + uint8_t bpdu_type; + uint8_t flags; + struct bridge root; + uint32_t root_path_cost; + struct bridge bridge; + uint8_t port_prio; + uint8_t port_id; + uint16_t age; + uint16_t age_max; + uint16_t hello; + uint16_t fwd_delay; + uint8_t version1_length; +}; +static struct sim_pkt_out last_tx[NPORTS]; +static int tc_frames[NPORTS]; +static int tcn_frames[NPORTS]; + +void reg_read_m(uint16_t addr) +{ + memset(sfr_data, 0, 4); + if (addr == RTL837X_MSTP_STATES) + memcpy(sfr_data, mstp_reg, 4); + else if (addr == RTL837X_REG_LINKS_STS) { + sfr_data[1] = (uint8_t)sim_links; + sfr_data[2] = (uint8_t)(sim_links >> 8); + } else if (addr == RTL837X_REG_LINKS || addr == RTL837X_REG_LINKS_89) { + uint8_t base = (addr == RTL837X_REG_LINKS) ? 0 : 8; + for (uint8_t k = 0; k < 8 && base + k < NPORTS; k++) + sfr_data[3 - (k >> 1)] |= (k & 1) ? (uint8_t)(sim_speed[base + k] << 4) + : sim_speed[base + k]; + } +} + +void reg_write_m(uint16_t addr) +{ + if (addr == RTL837X_MSTP_STATES) { + memcpy(mstp_reg, sfr_data, 4); + if (verbose > 1) + printf(" [mstp <- %02x %02x %02x %02x]\n", + mstp_reg[0], mstp_reg[1], mstp_reg[2], mstp_reg[3]); + } +} + +void tcpip_output(void) +{ + struct sim_pkt_out *f = (struct sim_pkt_out *)&uip_buf[RTL_FRAME_DESC_SIZE]; + uint16_t pmask = HTONS(f->rtl_tag.pmask); + for (uint8_t p = 0; p < NPORTS; p++) + if (pmask & (1 << p)) { + tx_frames[p]++; + if (f->bpdu_type == 0x80) + tcn_frames[p]++; + else if (f->flags & 0x01) + tc_frames[p]++; + memcpy(&last_tx[p], f, sizeof(*f)); + } + uip_len = 0; +} + +static uint8_t port_state(uint8_t port) +{ + return (mstp_reg[3 - (port >> 2)] >> ((port << 1) & 0x7)) & 0x3; +} + +static const char *state_name(uint8_t s) +{ + static const char *n[] = { "off", "discarding", "learning", "forwarding" }; + return n[s & 3]; +} + +static void links_set(uint16_t mask) +{ + sim_links = mask; +} + +static void tick(unsigned n) +{ + while (n--) + stp_timers(); +} + +static void secs(unsigned s) +{ + tick(s * STP_HZ); +} + +struct sim_bpdu { + uint8_t port; /* ingress port index */ + uint8_t root_prio; /* high byte of the root priority */ + uint8_t root_mac[6]; + uint32_t root_cost; /* root path cost as advertised */ + uint8_t br_prio; /* sender's own bridge priority high byte */ + uint8_t br_mac[6]; + uint8_t port_id; /* sender's port number, 1-based */ + uint8_t port_prio; /* 0 = 0x80 */ + uint8_t flags; /* 0 = designated, learning + forwarding */ + uint8_t age; /* message age, seconds */ + uint8_t maxage; /* 0 = 20 s, and hello/fwd default with it */ + uint8_t hello; + uint8_t fwd; + uint8_t legacy; /* 1 = 802.1D Config BPDU, 2 = TCN */ + uint8_t msg_len; /* 0 = the right length for the type */ +}; + +struct sim_pkt_in { + uint8_t stp_addr[6]; + uint8_t src_addr[6]; + struct rtl_tag rtl_tag; + struct vlan_tag vlan_tag; + uint16_t msg_len; + uint8_t dsap; + uint8_t ssap; + uint8_t ctrl; + uint16_t proto; + uint8_t version; + uint8_t bpdu_type; + uint8_t flags; + struct bridge root; + uint32_t root_path_cost; + struct bridge bridge; + uint8_t port_prio; + uint8_t port_id; + uint16_t age; + uint16_t age_max; + uint16_t hello; + uint16_t fwd_delay; + uint8_t version1_length; +}; + +static uint32_t swap32(uint32_t v) +{ + return (v << 24) | ((v & 0xff00) << 8) | ((v >> 8) & 0xff00) | (v >> 24); +} + +static void bpdu_in(const struct sim_bpdu *b) +{ + struct sim_pkt_in *f = (struct sim_pkt_in *)uip_buf; + + memset(uip_buf, 0, sizeof(uip_buf)); + memcpy(f->stp_addr, "\x01\x80\xc2\x00\x00\x00", 6); + memcpy(f->src_addr, b->br_mac, 6); + f->rtl_tag.pmask = HTONS(b->port); + f->msg_len = HTONS(b->msg_len ? b->msg_len : (b->legacy == 2 ? 7 : (b->legacy ? 38 : 39))); + f->dsap = 0x42; + f->ssap = 0x42; + f->ctrl = 0x03; + f->proto = 0; + f->version = b->legacy ? 0x00 : 0x02; + f->bpdu_type = b->legacy == 2 ? 0x80 : (b->legacy ? 0x00 : 0x02); + f->flags = b->flags ? b->flags : (b->legacy ? 0x00 : 0x3c); + f->root.prio = b->root_prio; + f->root.ext = 0; + memcpy(f->root.mac, b->root_mac, 6); + f->root_path_cost = swap32(b->root_cost); + f->bridge.prio = b->br_prio; + f->bridge.ext = 0; + memcpy(f->bridge.mac, b->br_mac, 6); + f->port_prio = b->port_prio ? b->port_prio : 0x80; + f->port_id = b->port_id; + f->age = b->age; + f->age_max = b->maxage ? b->maxage : 20; + f->hello = b->maxage ? b->hello : 2; + f->fwd_delay = b->maxage ? b->fwd : 15; + uip_len = sizeof(struct sim_pkt_in); + stp_in(); +} + +static int failures; + +static void check(int cond, const char *what) +{ + if (!cond) { + printf(" FAIL: %s\n", what); + failures++; + } else if (verbose) + printf(" ok: %s\n", what); +} + +static void check_state(uint8_t port, uint8_t want, const char *what) +{ + uint8_t got = port_state(port); + if (got != want) { + printf(" FAIL: %s: port %u is %s, expected %s\n", + what, port + 1, state_name(got), state_name(want)); + failures++; + } else if (verbose) + printf(" ok: %s (port %u %s)\n", what, port + 1, state_name(got)); +} + +static void reset_all(void) +{ + memset(mstp_reg, 0, sizeof(mstp_reg)); + memset(sim_lag, 0, sizeof(sim_lag)); + memset(flush_count, 0, sizeof(flush_count)); + memset(tx_frames, 0, sizeof(tx_frames)); + memset(last_tx, 0, sizeof(last_tx)); + memset(tc_frames, 0, sizeof(tc_frames)); + memset(tcn_frames, 0, sizeof(tcn_frames)); + for (uint8_t p = 0; p < NPORTS; p++) + sim_speed[p] = 2; + stp_enabled = 1; + stp_defaults(); + links_set(0); + stp_setup(); + tick(2 * STP_HZ); +} + +static const uint8_t ROOT_MAC[6] = {0x1c,0x2a,0xa3,0x1e,0xc2,0x03}; +static const uint8_t PEER_MAC[6] = {0x00,0x82,0x44,0x2a,0x74,0x82}; + +static void scen_edge_ports(void) +{ + printf("1. a port nobody talks STP on becomes an edge port\n"); + reset_all(); + links_set(1 << 3); + secs(1); + check_state(3, 1, "port with fresh carrier starts discarding"); + secs(4); + check_state(3, 3, "silent port goes forwarding as an edge port"); +} + +static void scen_root_port(void) +{ + printf("2. the bridge follows the root heard on port 9\n"); + reset_all(); + links_set(1 << 8); + secs(1); + struct sim_bpdu root_bpdu = { .port = 8, .root_prio = 0x40, .root_cost = 0, + .br_prio = 0x40, .port_id = 2 }; + memcpy(root_bpdu.root_mac, ROOT_MAC, 6); + memcpy(root_bpdu.br_mac, ROOT_MAC, 6); + bpdu_in(&root_bpdu); + check(stp_root_port == 8, "port 9 becomes the root port"); + secs(20); + bpdu_in(&root_bpdu); + check_state(8, 3, "root port forwards after the listen period"); +} + +static void scen_ring(void) +{ + printf("3. a neighbour with a cheaper path to the root must block us\n"); + reset_all(); + links_set((1 << 0) | (1 << 8)); + secs(1); + + struct sim_bpdu from_root = { .port = 8, .root_prio = 0x40, .root_cost = 0, + .br_prio = 0x40, .port_id = 2 }; + memcpy(from_root.root_mac, ROOT_MAC, 6); + memcpy(from_root.br_mac, ROOT_MAC, 6); + + struct sim_bpdu from_peer = { .port = 0, .root_prio = 0x40, .root_cost = 2000, + .br_prio = 0x80, .port_id = 1 }; + memcpy(from_peer.root_mac, ROOT_MAC, 6); + memcpy(from_peer.br_mac, PEER_MAC, 6); + + for (int i = 0; i < 12; i++) { + bpdu_in(&from_root); + bpdu_in(&from_peer); + secs(2); + } + check(stp_root_port == 8, "the direct uplink stays the root port"); + check_state(8, 3, "root port forwards"); + check_state(0, 1, "port 1 is discarding (alternate)"); +} + +static void scen_ring_clears(void) +{ + printf("4. when the better neighbour goes quiet the port comes back\n"); + scen_ring(); + struct sim_bpdu from_root = { .port = 8, .root_prio = 0x40, .root_cost = 0, + .br_prio = 0x40, .port_id = 2 }; + memcpy(from_root.root_mac, ROOT_MAC, 6); + memcpy(from_root.br_mac, ROOT_MAC, 6); + bpdu_in(&from_root); + secs(2); + bpdu_in(&from_root); + secs(2); + check_state(0, 1, "port 1 still discards within three of the peer's hellos"); + for (int i = 0; i < 18; i++) { + bpdu_in(&from_root); + secs(2); + } + check_state(0, 3, "port 1 forwards again once the peer information aged out"); +} + +static void scen_cheaper_path(void) +{ + printf("5. the cheaper of two paths to the root becomes the root port\n"); + reset_all(); + stp_pcost[0] = 2000; + links_set((1 << 0) | (1 << 8)); + secs(1); + struct sim_bpdu from_root_p9 = { .port = 8, .root_prio = 0x40, .root_cost = 0, + .br_prio = 0x40, .port_id = 2 }; + memcpy(from_root_p9.root_mac, ROOT_MAC, 6); + memcpy(from_root_p9.br_mac, ROOT_MAC, 6); + struct sim_bpdu from_root_p1 = from_root_p9; + from_root_p1.port = 0; + from_root_p1.port_id = 7; + for (int i = 0; i < 12; i++) { + bpdu_in(&from_root_p9); + bpdu_in(&from_root_p1); + secs(2); + } + check(stp_root_port == 0, "port 1 wins on cost and becomes the root port"); + check(root_bridge_cost == 2000, "the root path cost follows that port"); + check_state(0, 3, "the root port forwards"); + check_state(8, 1, "the dearer path to the same root is discarding"); + stp_pcost[0] = 0; +} + +static void scen_we_are_better(void) +{ + printf("6. a neighbour with a worse path does not block us\n"); + reset_all(); + links_set((1 << 0) | (1 << 8)); + secs(1); + struct sim_bpdu from_root = { .port = 8, .root_prio = 0x40, .root_cost = 0, + .br_prio = 0x40, .port_id = 2 }; + memcpy(from_root.root_mac, ROOT_MAC, 6); + memcpy(from_root.br_mac, ROOT_MAC, 6); + struct sim_bpdu from_peer = { .port = 0, .root_prio = 0x40, .root_cost = 200000, + .br_prio = 0x80, .port_id = 1, .flags = 0x0c }; + memcpy(from_peer.root_mac, ROOT_MAC, 6); + memcpy(from_peer.br_mac, PEER_MAC, 6); + for (int i = 0; i < 18; i++) { + bpdu_in(&from_root); + bpdu_in(&from_peer); + secs(2); + } + check(stp_root_port == 8, "the uplink stays the root port"); + check_state(0, 3, "port 1 keeps forwarding"); +} + +static void scen_alt_survives_link_bounce(void) +{ + printf("7. a blocked port stays blocked when the carrier returns\n"); + scen_ring(); + struct sim_bpdu from_root = { .port = 8, .root_prio = 0x40, .root_cost = 0, + .br_prio = 0x40, .port_id = 2 }; + memcpy(from_root.root_mac, ROOT_MAC, 6); + memcpy(from_root.br_mac, ROOT_MAC, 6); + struct sim_bpdu from_peer = { .port = 0, .root_prio = 0x40, .root_cost = 2000, + .br_prio = 0x80, .port_id = 1 }; + memcpy(from_peer.root_mac, ROOT_MAC, 6); + memcpy(from_peer.br_mac, PEER_MAC, 6); + + links_set(1 << 8); + secs(2); + links_set((1 << 0) | (1 << 8)); + bpdu_in(&from_peer); + for (int i = 0; i < 15; i++) { + bpdu_in(&from_root); + bpdu_in(&from_peer); + secs(2); + } + check_state(0, 1, "port 1 never reaches forwarding while the peer is better"); + check(stp_root_port == 8, "the uplink is still the root port"); +} + +static void scen_speed_cost(void) +{ + printf("8. the faster of two links to one root wins on cost\n"); + reset_all(); + sim_speed[8] = 4; + sim_speed[0] = 2; + links_set((1 << 0) | (1 << 8)); + secs(1); + struct sim_bpdu from_root = { .port = 8, .root_prio = 0x40, .root_cost = 0, + .br_prio = 0x40, .port_id = 2 }; + memcpy(from_root.root_mac, ROOT_MAC, 6); + memcpy(from_root.br_mac, ROOT_MAC, 6); + struct sim_bpdu slow = from_root; + slow.port = 0; + slow.port_id = 7; + for (int i = 0; i < 12; i++) { + bpdu_in(&from_root); + bpdu_in(&slow); + secs(2); + } + check(stp_root_port == 8, "the 10G port is the root port"); + check(root_bridge_cost == 2000, "its cost is the 10G value, not a flat default"); +} + +static struct sim_bpdu root_on(uint8_t port) +{ + struct sim_bpdu b = { .port = port, .root_prio = 0x40, .root_cost = 0, + .br_prio = 0x40, .port_id = 2 }; + memcpy(b.root_mac, ROOT_MAC, 6); + memcpy(b.br_mac, ROOT_MAC, 6); + return b; +} + +static const uint8_t THIRD_MAC[6] = {0x00,0x11,0x22,0x33,0x44,0x55}; + +static void scen_root_times(void) +{ + printf("9. the times of the root travel on and time the listen period\n"); + reset_all(); + stp_pflags[1] &= ~STP_PF_AUTOEDGE; + links_set(1 << 8); + secs(1); + struct sim_bpdu r = root_on(8); + r.age = 3; r.maxage = 12; r.hello = 1; r.fwd = 8; + bpdu_in(&r); + check(stp_root_port == 8, "port 9 is the root port"); + links_set((1 << 1) | (1 << 8)); + for (int i = 0; i < 2; i++) { bpdu_in(&r); secs(1); } + check_state(1, 1, "a new designated port starts discarding"); + for (int i = 0; i < 5; i++) { bpdu_in(&r); secs(1); } + check_state(1, 1, "it still discards short of the root's forward delay"); + for (int i = 0; i < 3; i++) { bpdu_in(&r); secs(1); } + check_state(1, 2, "it learns after the root's 8 s, not our own 15 s"); + for (int i = 0; i < 8; i++) { bpdu_in(&r); secs(1); } + check_state(1, 3, "and forwards after a second 8 s"); + for (int i = 0; i < 3; i++) { bpdu_in(&r); secs(1); } + check(tx_frames[1] > 0, "the designated port sends BPDUs"); + check((uint8_t)last_tx[1].age == 4, "the message age is one more than received"); + check((uint8_t)last_tx[1].age_max == 12, "max age is the root's"); + check((uint8_t)last_tx[1].fwd_delay == 8, "forward delay is the root's"); + check((uint8_t)last_tx[1].hello == 2, "hello time stays our own"); +} + +static void scen_too_old(void) +{ + printf("10. information as old as max age is not used\n"); + reset_all(); + links_set(1 << 8); + secs(1); + struct sim_bpdu r = root_on(8); + r.age = 20; r.maxage = 20; r.hello = 2; r.fwd = 15; + bpdu_in(&r); + check(stp_root_port == 0xff, "the switch stays its own root"); + r.age = 19; + bpdu_in(&r); + check(stp_root_port == 8, "one second younger it is accepted"); +} + +static void scen_info_expiry(void) +{ + printf("11. received information lasts three of the sender's hellos\n"); + reset_all(); + links_set(1 << 8); + secs(1); + struct sim_bpdu r = root_on(8); + r.maxage = 20; r.hello = 1; r.fwd = 15; + bpdu_in(&r); + check(stp_root_port == 8, "port 9 is the root port"); + tick(2 * STP_HZ + STP_HZ / 2); + check(stp_root_port == 8, "still the root port 2.5 s later"); + tick(STP_HZ); + check(stp_root_port == 0xff, "gone after 3 s with a 1 s hello"); +} + +static void scen_dport_tiebreak(void) +{ + printf("12. equal cost to one bridge: the lower designated port ID wins\n"); + reset_all(); + links_set((1 << 1) | (1 << 2)); + secs(1); + struct sim_bpdu a = root_on(1); a.port_id = 5; + struct sim_bpdu b = root_on(2); b.port_id = 2; + for (int i = 0; i < 12; i++) { bpdu_in(&a); bpdu_in(&b); secs(2); } + check(stp_root_port == 2, "port 3, which hears port ID 8002, is the root port"); + check_state(1, 1, "port 2, which hears 8005, is discarding"); +} + +static void scen_rxport_tiebreak(void) +{ + printf("13. same designated port on two of ours: the receiving port ID decides\n"); + reset_all(); + links_set((1 << 3) | (1 << 4)); + secs(1); + struct sim_bpdu a = root_on(3); + struct sim_bpdu b = root_on(4); + for (int i = 0; i < 3; i++) { bpdu_in(&a); bpdu_in(&b); secs(2); } + check(stp_root_port == 3, "equal port priority: the lower port number, port 4"); + stp_pprio[4] = 0x40; + for (int i = 0; i < 3; i++) { bpdu_in(&a); bpdu_in(&b); secs(2); } + check(stp_root_port == 4, "a better priority on port 5 wins"); + stp_pprio[4] = 0x80; +} + +static void scen_inferior_info(void) +{ + printf("14. worse information replaces stored only from the same sender\n"); + reset_all(); + links_set((1 << 0) | (1 << 8)); + secs(1); + struct sim_bpdu r = root_on(8); + struct sim_bpdu peer = { .port = 0, .root_prio = 0x40, .root_cost = 2000, + .br_prio = 0x80, .port_id = 1 }; + memcpy(peer.root_mac, ROOT_MAC, 6); + memcpy(peer.br_mac, PEER_MAC, 6); + struct sim_bpdu third = peer; + third.root_cost = 400000; + memcpy(third.br_mac, THIRD_MAC, 6); + for (int i = 0; i < 10; i++) { bpdu_in(&r); bpdu_in(&peer); secs(2); } + check_state(0, 1, "port 1 is discarding behind the better peer"); + for (int i = 0; i < 2; i++) { bpdu_in(&r); bpdu_in(&peer); bpdu_in(&third); secs(1); } + check(stp_dcost[0] == 2000, "a worse bridge on the segment does not overwrite the peer"); + check_state(0, 1, "and port 1 keeps discarding"); + peer.root_cost = 400000; + peer.flags = 0x0c; + bpdu_in(&peer); + check(stp_dcost[0] == 400000, "the peer's own worse information does replace it"); + for (int i = 0; i < 18; i++) { bpdu_in(&r); bpdu_in(&peer); secs(2); } + check_state(0, 3, "port 1 is designated and forwards again"); +} + +static void scen_root_role_bpdu(void) +{ + printf("15. an RST BPDU sent by a root port is not designated information\n"); + reset_all(); + links_set((1 << 0) | (1 << 8)); + secs(1); + struct sim_bpdu r = root_on(8); + struct sim_bpdu peer = { .port = 0, .root_prio = 0x40, .root_cost = 2000, + .br_prio = 0x80, .port_id = 1, .flags = 0x08 | 0x30 }; + memcpy(peer.root_mac, ROOT_MAC, 6); + memcpy(peer.br_mac, PEER_MAC, 6); + for (int i = 0; i < 18; i++) { bpdu_in(&r); bpdu_in(&peer); secs(2); } + check_state(0, 3, "port 1 keeps forwarding"); + check(stp_root_port == 8, "the uplink stays the root port"); +} + +static void scen_alternate_takes_over(void) +{ + printf("16. when the root port's information expires an alternate path takes over\n"); + reset_all(); + stp_pcost[8] = 2000; + links_set((1 << 0) | (1 << 8)); + secs(1); + struct sim_bpdu r = root_on(8); + struct sim_bpdu peer = { .port = 0, .root_prio = 0x40, .root_cost = 2000, + .br_prio = 0x80, .port_id = 1 }; + memcpy(peer.root_mac, ROOT_MAC, 6); + memcpy(peer.br_mac, PEER_MAC, 6); + for (int i = 0; i < 12; i++) { bpdu_in(&r); bpdu_in(&peer); secs(2); } + check(stp_root_port == 8, "the 10G uplink is the root port"); + for (int i = 0; i < 4; i++) { bpdu_in(&peer); secs(2); } + check(stp_root_port == 0, "port 1 is the root port once port 9 went quiet"); + check(memcmp(root_bridge.mac, ROOT_MAC, 6) == 0, "the root is still the same bridge"); + check(root_bridge_cost == 22000, "the cost is the peer's plus port 1's"); + stp_pcost[8] = 0; +} + +static void scen_dbridge_tiebreak(void) +{ + printf("17. equal cost through two bridges: the lower bridge ID wins\n"); + reset_all(); + links_set((1 << 1) | (1 << 2)); + secs(1); + struct sim_bpdu via_peer = { .port = 1, .root_prio = 0x40, .root_cost = 2000, + .br_prio = 0x80, .port_id = 1 }; + memcpy(via_peer.root_mac, ROOT_MAC, 6); + memcpy(via_peer.br_mac, PEER_MAC, 6); + struct sim_bpdu via_third = via_peer; + via_third.port = 2; + memcpy(via_third.br_mac, THIRD_MAC, 6); + via_third.br_prio = 0x70; + for (int i = 0; i < 12; i++) { bpdu_in(&via_peer); bpdu_in(&via_third); secs(2); } + check(stp_root_port == 2, "port 3, behind the bridge with priority 7000, is the root port"); + check_state(1, 1, "port 2, behind 8000, is discarding"); +} + +static void scen_times_revert(void) +{ + printf("18. back to our own times once we are the root again\n"); + reset_all(); + links_set((1 << 1) | (1 << 8)); + secs(1); + struct sim_bpdu r = root_on(8); + r.age = 3; r.maxage = 12; r.hello = 2; r.fwd = 8; + for (int i = 0; i < 4; i++) { bpdu_in(&r); secs(2); } + check(stp_root_port == 8, "port 9 is the root port"); + secs(8); + check(stp_root_port == 0xff, "the switch is its own root again"); + tx_frames[1] = 0; + secs(3); + check(tx_frames[1] > 0, "port 2 still sends"); + check((uint8_t)last_tx[1].age == 0, "the message age is 0 from the root"); + check((uint8_t)last_tx[1].age_max == 20, "max age is our own again"); + check((uint8_t)last_tx[1].fwd_delay == 15, "forward delay is our own again"); +} + +static void ring_with_uplink_on_9(struct sim_bpdu *r, struct sim_bpdu *peer) +{ + reset_all(); + stp_pcost[8] = 2000; + links_set((1 << 0) | (1 << 8)); + secs(1); + *r = root_on(8); + struct sim_bpdu p = { .port = 0, .root_prio = 0x40, .root_cost = 2000, + .br_prio = 0x80, .port_id = 1 }; + memcpy(p.root_mac, ROOT_MAC, 6); + memcpy(p.br_mac, PEER_MAC, 6); + *peer = p; + for (int i = 0; i < 12; i++) { bpdu_in(r); bpdu_in(peer); secs(2); } +} + +static void scen_tc_via_root_port(void) +{ + printf("19. a port that takes over as root port tells the root about it\n"); + struct sim_bpdu r, peer; + ring_with_uplink_on_9(&r, &peer); + check(stp_root_port == 8 && port_state(0) == 1, "port 1 starts as the blocked alternate"); + check(tc_frames[0] == 0, "no TC on port 1 so far"); + links_set(1 << 0); + int waited = 0; + while (port_state(0) != 3 && waited < 20 * STP_HZ) { bpdu_in(&peer); tick(STP_HZ / 2); waited += STP_HZ / 2; } + check(stp_root_port == 0, "port 1 is the root port after port 9 went down"); + check_state(0, 3, "and forwards"); + tick(2); + check(tc_frames[0] > 0, "port 1 sends a BPDU with TC toward the root right away"); + check((last_tx[0].flags & 0x0c) == 0x08, "that BPDU carries the root port role"); + stp_pcost[8] = 0; +} + +static void scen_tc_window_rstp(void) +{ + printf("20. in RSTP the TC window lasts hello time plus one second\n"); + struct sim_bpdu r, peer; + ring_with_uplink_on_9(&r, &peer); + links_set(1 << 0); + int waited = 0; + while (port_state(0) != 3 && waited < 20 * STP_HZ) { bpdu_in(&peer); tick(STP_HZ / 2); waited += STP_HZ / 2; } + for (int i = 0; i < 4; i++) { bpdu_in(&peer); secs(1); } + int seen = tc_frames[0]; + for (int i = 0; i < 6; i++) { bpdu_in(&peer); secs(1); } + check(seen > 0, "TC was sent"); + check(tc_frames[0] == seen, "and stops once hello plus one second is over"); + stp_pcost[8] = 0; +} + +static void scen_tc_propagation(void) +{ + printf("21. a TC from the root side goes out on the other ports, not back\n"); + reset_all(); + stp_pflags[1] &= ~STP_PF_AUTOEDGE; + links_set((1 << 1) | (1 << 8)); + secs(1); + struct sim_bpdu r = root_on(8); + for (int i = 0; i < 18; i++) { bpdu_in(&r); secs(2); } + check_state(1, 3, "designated port 2 forwards"); + for (int i = 0; i < 3; i++) { bpdu_in(&r); secs(2); } + memset(flush_count, 0, sizeof(flush_count)); + int tc2 = tc_frames[1], tc9 = tc_frames[8]; + struct sim_bpdu rtc = r; rtc.flags = 0x3d; + bpdu_in(&rtc); + tick(2); + check(tc_frames[1] > tc2, "port 2 passes the TC on at once"); + check(flush_count[1] > 0, "port 2 forgets what it learned"); + check(flush_count[8] == 0, "the port the TC came in on keeps its addresses"); + check(tc_frames[8] == tc9, "no TC is echoed back to the root"); +} + +static void scen_tc_legacy_tcn(void) +{ + printf("22. with 802.1D neighbours the root port sends TCN until acknowledged\n"); + reset_all(); + stp_rstp = 0; + stp_pflags[1] &= ~STP_PF_AUTOEDGE; + links_set(1 << 8); + secs(1); + struct sim_bpdu r = root_on(8); r.legacy = 1; + for (int i = 0; i < 18; i++) { bpdu_in(&r); secs(2); } + check(stp_root_port == 8, "port 9 is the root port"); + check(tcn_frames[8] > 0, "port 9 entering forwarding is itself reported with TCN"); + struct sim_bpdu ack = r; ack.flags = 0x81; + bpdu_in(&ack); + int quiet = tcn_frames[8]; + for (int i = 0; i < 5; i++) { bpdu_in(&r); secs(2); } + check(tcn_frames[8] == quiet, "acknowledged: no more TCN while nothing changes"); + links_set((1 << 1) | (1 << 8)); + for (int i = 0; i < 18; i++) { bpdu_in(&r); secs(2); } + check_state(1, 3, "port 2 came up and forwards"); + check(tcn_frames[8] > quiet, "port 9 sends TCN toward the root again"); + bpdu_in(&ack); + int n = tcn_frames[8]; + for (int i = 0; i < 5; i++) { bpdu_in(&r); secs(2); } + check(tcn_frames[8] == n, "and stops once the root acknowledged it"); +} + +static void scen_alt_forgets(void) +{ + printf("23. a port that turns alternate forgets what it learned\n"); + reset_all(); + links_set((1 << 0) | (1 << 8)); + secs(1); + struct sim_bpdu r = root_on(8); + for (int i = 0; i < 10; i++) { bpdu_in(&r); secs(2); } + check_state(0, 3, "port 1 forwards as designated"); + memset(flush_count, 0, sizeof(flush_count)); + struct sim_bpdu peer = { .port = 0, .root_prio = 0x40, .root_cost = 2000, + .br_prio = 0x80, .port_id = 1 }; + memcpy(peer.root_mac, ROOT_MAC, 6); + memcpy(peer.br_mac, PEER_MAC, 6); + bpdu_in(&peer); + check_state(0, 1, "port 1 is blocked behind the better peer"); + check(flush_count[0] > 0, "and its addresses are flushed"); +} + +static void scen_edge_no_tc(void) +{ + printf("24. an edge port coming up raises no topology change\n"); + reset_all(); + links_set((1 << 1) | (1 << 8)); + secs(1); + struct sim_bpdu r = root_on(8); + for (int i = 0; i < 10; i++) { bpdu_in(&r); secs(2); } + for (int i = 0; i < 20; i++) { bpdu_in(&r); secs(2); } + uint16_t tc = stp_tc_count; + int tc9 = tc_frames[8], tc2 = tc_frames[1]; + links_set((1 << 1) | (1 << 3) | (1 << 8)); + for (int i = 0; i < 4; i++) { bpdu_in(&r); secs(2); } + check_state(3, 3, "the host port forwards as an edge port"); + check(stp_tc_count == tc, "the change counter does not move"); + check(tc_frames[8] == tc9 && tc_frames[1] == tc2, "no TC is sent"); +} + +static void scen_link_loss_no_tc(void) +{ + printf("25. losing a designated link flushes it but sends no TC\n"); + reset_all(); + stp_pflags[1] &= ~STP_PF_AUTOEDGE; + stp_pflags[2] &= ~STP_PF_AUTOEDGE; + links_set((1 << 1) | (1 << 2) | (1 << 8)); + secs(1); + struct sim_bpdu r = root_on(8); + for (int i = 0; i < 20; i++) { bpdu_in(&r); secs(2); } + check_state(1, 3, "port 2 forwards"); + memset(flush_count, 0, sizeof(flush_count)); + int tc3 = tc_frames[2], tc9 = tc_frames[8]; + links_set((1 << 2) | (1 << 8)); + for (int i = 0; i < 3; i++) { bpdu_in(&r); secs(1); } + check(flush_count[1] > 0, "port 2's addresses are flushed"); + check(tc_frames[2] == tc3 && tc_frames[8] == tc9, "no TC goes out"); +} + +static void scen_proposal_agreement(void) +{ + printf("26. a new designated port proposes and forwards as soon as the neighbour agrees\n"); + reset_all(); + stp_pflags[1] &= ~STP_PF_AUTOEDGE; + links_set(1 << 8); + secs(1); + struct sim_bpdu r = root_on(8); + for (int i = 0; i < 3; i++) { bpdu_in(&r); secs(2); } + links_set((1 << 1) | (1 << 8)); + tick(STP_HZ + STP_HZ / 2); + check_state(1, 1, "port 2 starts discarding"); + check((last_tx[1].flags & 0x02) != 0, "and its BPDU carries the proposal flag"); + check((last_tx[1].flags & 0x0c) == 0x0c, "with the designated role"); + struct sim_bpdu agree = { .port = 1, .root_prio = 0x40, .root_cost = 22000, + .br_prio = 0x80, .port_id = 3, .flags = 0x78 }; + memcpy(agree.root_mac, ROOT_MAC, 6); + memcpy(agree.br_mac, PEER_MAC, 6); + bpdu_in(&agree); + tick(2); + check_state(1, 3, "port 2 forwards right after the agreement"); +} + +static void scen_no_agreement(void) +{ + printf("27. without an agreement the port waits both forward delays\n"); + reset_all(); + stp_pflags[1] &= ~STP_PF_AUTOEDGE; + stp_pflags[2] &= ~STP_PF_AUTOEDGE; + stp_pp2p[2] = 2; + links_set(1 << 8); + secs(1); + struct sim_bpdu r = root_on(8); + for (int i = 0; i < 3; i++) { bpdu_in(&r); secs(2); } + links_set((1 << 1) | (1 << 2) | (1 << 8)); + for (int i = 0; i < 5; i++) { bpdu_in(&r); secs(2); } + check_state(1, 1, "port 2 still discards after 10 s"); + check((last_tx[2].flags & 0x02) == 0, "a port set to p2p off does not propose"); + for (int i = 0; i < 4; i++) { bpdu_in(&r); secs(2); } + check_state(1, 2, "port 2 learns once the first forward delay is over"); + for (int i = 0; i < 8; i++) { bpdu_in(&r); secs(2); } + check_state(1, 3, "and forwards after the second"); + stp_pp2p[2] = 0; +} + +static void scen_sync_on_proposal(void) +{ + printf("28. a proposal on the root port syncs the designated ports and is agreed\n"); + reset_all(); + stp_pflags[1] &= ~STP_PF_AUTOEDGE; + links_set((1 << 1) | (1 << 3) | (1 << 8)); + secs(1); + struct sim_bpdu r = root_on(8); + for (int i = 0; i < 18; i++) { bpdu_in(&r); secs(2); } + check_state(1, 3, "designated port 2 forwards"); + check_state(3, 3, "edge port 4 forwards"); + check_state(8, 3, "root port 9 forwards"); + struct sim_bpdu prop = r; prop.flags = 0x3e; + bpdu_in(&prop); + tick(2); + check_state(1, 1, "port 2 is put back to discarding for the sync"); + check((last_tx[1].flags & 0x02) != 0, "and proposes downstream"); + check_state(3, 3, "the edge port is left alone"); + check_state(8, 3, "the root port keeps forwarding"); + check((last_tx[8].flags & 0x40) != 0, "the root port answers with an agreement"); + check((last_tx[8].flags & 0x0c) == 0x08, "in a BPDU with the root role"); +} + +static void scen_rapid_failover(void) +{ + printf("29. when the root port goes down the alternate forwards at once\n"); + struct sim_bpdu r, peer; + ring_with_uplink_on_9(&r, &peer); + check(stp_root_port == 8 && port_state(0) == 1, "port 1 is the blocked alternate"); + links_set(1 << 0); + for (int i = 0; i < 4; i++) { bpdu_in(&peer); tick(STP_HZ / 2); } + check(stp_root_port == 0, "port 1 is the root port within two seconds"); + check_state(0, 3, "and already forwards"); + stp_pcost[8] = 0; +} + +static void scen_rapid_reroot(void) +{ + printf("30. the uplink coming back takes over without both ports forwarding\n"); + struct sim_bpdu r, peer; + ring_with_uplink_on_9(&r, &peer); + stp_pcost[8] = 2000; + links_set(1 << 0); + for (int i = 0; i < 6; i++) { bpdu_in(&peer); tick(STP_HZ / 2); } + check(stp_root_port == 0 && port_state(0) == 3, "port 1 is the forwarding root port"); + links_set((1 << 0) | (1 << 8)); + int both = 0; + for (int i = 0; i < 12; i++) { + struct sim_bpdu p = r; p.flags = 0x3e; + bpdu_in(&p); + if (port_state(0) == 3 && port_state(8) == 3) both++; + bpdu_in(&peer); + if (port_state(0) == 3 && port_state(8) == 3) both++; + for (int k = 0; k < STP_HZ / 4; k++) { + tick(1); + if (port_state(0) == 3 && port_state(8) == 3) both++; + } + } + check(stp_root_port == 8, "port 9 is the root port again"); + check_state(8, 3, "and forwards within three seconds"); + check_state(0, 1, "port 1 is back to discarding"); + check(both == 0, "ports 1 and 9 never forwarded at the same time"); + stp_pcost[8] = 0; +} + +static void scen_agreement_other_root(void) +{ + printf("31. an agreement for a different root does not open a port\n"); + reset_all(); + stp_pflags[1] &= ~STP_PF_AUTOEDGE; + links_set(1 << 8); + secs(1); + struct sim_bpdu r = root_on(8); + for (int i = 0; i < 3; i++) { bpdu_in(&r); secs(2); } + links_set((1 << 1) | (1 << 8)); + tick(STP_HZ + STP_HZ / 2); + struct sim_bpdu agree = { .port = 1, .root_prio = 0x40, .root_cost = 22000, + .br_prio = 0x80, .port_id = 3, .flags = 0x78 }; + memcpy(agree.root_mac, THIRD_MAC, 6); + memcpy(agree.br_mac, PEER_MAC, 6); + bpdu_in(&agree); + tick(2); + check_state(1, 1, "port 2 keeps discarding"); +} + +static void scen_repeated_proposal(void) +{ + printf("32. a proposal repeated every hello syncs only once\n"); + reset_all(); + stp_pflags[1] &= ~STP_PF_AUTOEDGE; + links_set((1 << 1) | (1 << 8)); + secs(1); + struct sim_bpdu r = root_on(8); + for (int i = 0; i < 18; i++) { bpdu_in(&r); secs(2); } + check_state(1, 3, "designated port 2 forwards"); + struct sim_bpdu prop = r; prop.flags = 0x3e; + bpdu_in(&prop); + tick(2); + check_state(1, 1, "the first proposal syncs port 2"); + struct sim_bpdu agree = { .port = 1, .root_prio = 0x40, .root_cost = 22000, + .br_prio = 0x80, .port_id = 3, .flags = 0x78 }; + memcpy(agree.root_mac, ROOT_MAC, 6); + memcpy(agree.br_mac, PEER_MAC, 6); + bpdu_in(&agree); + tick(2); + check_state(1, 3, "port 2 forwards again after its own agreement"); + int agreements = 0; + for (int i = 0; i < 5; i++) { + int before = tx_frames[8]; + bpdu_in(&prop); + tick(2); + if (tx_frames[8] > before && (last_tx[8].flags & 0x40)) agreements++; + secs(2); + check_state(1, 3, "a repeated proposal does not block port 2 again"); + } + check(agreements == 5, "every repeated proposal is answered with an agreement"); +} + +static void scen_dispute(void) +{ + printf("33. a worse neighbour that claims to forward as designated is disputed\n"); + reset_all(); + stp_pflags[1] &= ~STP_PF_AUTOEDGE; + links_set((1 << 1) | (1 << 8)); + secs(1); + struct sim_bpdu r = root_on(8); + for (int i = 0; i < 18; i++) { bpdu_in(&r); secs(2); } + check_state(1, 3, "designated port 2 forwards"); + struct sim_bpdu quiet = { .port = 1, .root_prio = 0x40, .root_cost = 200000, + .br_prio = 0x80, .port_id = 4, .flags = 0x0c }; + memcpy(quiet.root_mac, ROOT_MAC, 6); + memcpy(quiet.br_mac, PEER_MAC, 6); + bpdu_in(&quiet); + tick(2); + check_state(1, 3, "worse designated info without the learning flag is no dispute"); + struct sim_bpdu claim = quiet; claim.flags = 0x3c; + bpdu_in(&claim); + tick(2); + check_state(1, 1, "with the learning flag set port 2 goes back to discarding"); + int opened = 0; + for (int i = 0; i < 12; i++) { + bpdu_in(&r); + bpdu_in(&claim); + for (int k = 0; k < 2 * STP_HZ; k++) { + tick(1); + if (port_state(1) == 3) opened++; + } + } + check(opened == 0, "and never forwards while the dispute goes on"); + for (int i = 0; i < 18; i++) { bpdu_in(&r); bpdu_in(&quiet); secs(2); } + check_state(1, 3, "once the neighbour stops claiming, port 2 forwards again"); +} + +static void scen_learning_state(void) +{ + printf("34. without an agreement a port learns for one forward delay before it forwards\n"); + reset_all(); + stp_pflags[1] &= ~STP_PF_AUTOEDGE; + links_set(1 << 8); + secs(1); + struct sim_bpdu r = root_on(8); + for (int i = 0; i < 3; i++) { bpdu_in(&r); secs(2); } + links_set((1 << 1) | (1 << 8)); + for (int i = 0; i < 6; i++) { bpdu_in(&r); secs(2); } + check_state(1, 1, "port 2 discards for the first forward delay"); + for (int i = 0; i < 3; i++) { bpdu_in(&r); secs(2); } + check_state(1, 2, "then learns"); + check((last_tx[1].flags & 0x30) == 0x10, "its BPDUs carry learning without forwarding"); + struct sim_bpdu claim = { .port = 1, .root_prio = 0x40, .root_cost = 200000, + .br_prio = 0x80, .port_id = 4, .flags = 0x3c }; + memcpy(claim.root_mac, ROOT_MAC, 6); + memcpy(claim.br_mac, PEER_MAC, 6); + bpdu_in(&claim); + tick(2); + check_state(1, 1, "a dispute while learning sends it back to discarding"); + for (int i = 0; i < 9; i++) { bpdu_in(&r); secs(2); } + check_state(1, 2, "after a full forward delay it learns again"); + for (int i = 0; i < 8; i++) { bpdu_in(&r); secs(2); } + check_state(1, 3, "and forwards after the second forward delay"); +} + +static void scen_reply_to_inferior(void) +{ + printf("35. a designated port answers worse information at once\n"); + reset_all(); + stp_pflags[1] &= ~STP_PF_AUTOEDGE; + links_set((1 << 1) | (1 << 8)); + secs(1); + struct sim_bpdu r = root_on(8); + for (int i = 0; i < 18; i++) { bpdu_in(&r); secs(2); } + check_state(1, 3, "designated port 2 forwards"); + int n = tx_frames[1], guard = 0; + while (tx_frames[1] == n && guard++ < 3 * STP_HZ) tick(1); + check(tx_frames[1] > n, "port 2 sent its regular hello"); + struct sim_bpdu worse = { .port = 1, .root_prio = 0x40, .root_cost = 200000, + .br_prio = 0x80, .port_id = 4, .flags = 0x0c }; + memcpy(worse.root_mac, ROOT_MAC, 6); + memcpy(worse.br_mac, PEER_MAC, 6); + n = tx_frames[1]; + bpdu_in(&worse); + tick(2); + check(tx_frames[1] > n, "the worse BPDU is answered within two ticks, not at the next hello"); + tick(STP_HZ / 2); + n = tx_frames[1]; + struct sim_bpdu same_root_better = { .port = 1, .root_prio = 0x40, .root_cost = 2000, + .br_prio = 0x80, .port_id = 1, .flags = 0x0c }; + memcpy(same_root_better.root_mac, ROOT_MAC, 6); + memcpy(same_root_better.br_mac, PEER_MAC, 6); + bpdu_in(&same_root_better); + tick(2); + check(tx_frames[1] == n, "better information is not answered"); +} + +static void scen_backup_role(void) +{ + printf("36. a port hearing another port of this bridge is a blocked backup\n"); + reset_all(); + stp_pflags[2] &= ~STP_PF_AUTOEDGE; + stp_pflags[3] &= ~STP_PF_AUTOEDGE; + links_set((1 << 2) | (1 << 3) | (1 << 8)); + secs(1); + struct sim_bpdu r = root_on(8); + for (int i = 0; i < 18; i++) { bpdu_in(&r); secs(2); } + check_state(2, 3, "port 3 forwards"); + check_state(3, 3, "port 4 forwards"); + struct sim_bpdu own = { .port = 2, .root_prio = 0x40, .root_cost = 20000, + .br_prio = 0x80, .port_id = 4 }; + memcpy(own.root_mac, ROOT_MAC, 6); + memcpy(own.br_mac, uip_ethaddr.addr, 6); + bpdu_in(&own); + tick(2); + check_state(3, 1, "port 4, the worse of the two, is blocked"); + check(((stp_backup >> 3) & 1) == 1, "and has the backup role"); + check(((stp_backup >> 2) & 1) == 0, "port 3 is not a backup"); + for (int i = 0; i < 20; i++) { bpdu_in(&r); secs(2); } + check_state(3, 3, "once its own BPDUs stop coming back port 4 forwards again"); + check(((stp_backup >> 3) & 1) == 0, "and is no longer a backup"); + bpdu_in(&own); + tick(2); + check(((stp_backup >> 3) & 1) == 1, "a new loop makes it a backup again"); + links_set((1 << 2) | (1 << 8)); + secs(2); + check(((stp_backup >> 3) & 1) == 0, "losing the link clears the backup role"); +} + +static void scen_migrate_to_stp(void) +{ + printf("37. a port that hears an 802.1D neighbour sends Config BPDUs\n"); + reset_all(); + stp_pflags[1] &= ~STP_PF_AUTOEDGE; + links_set((1 << 1) | (1 << 8)); + secs(1); + struct sim_bpdu r = root_on(8); + for (int i = 0; i < 3; i++) { bpdu_in(&r); secs(2); } + check(last_tx[1].version == 2, "port 2 starts with RST BPDUs"); + struct sim_bpdu old = { .port = 1, .root_prio = 0x40, .root_cost = 200000, + .br_prio = 0x80, .port_id = 4, .legacy = 1 }; + memcpy(old.root_mac, ROOT_MAC, 6); + memcpy(old.br_mac, PEER_MAC, 6); + bpdu_in(&old); + tick(2); + check(last_tx[1].version == 0 && last_tx[1].bpdu_type == 0, "after the 802.1D BPDU port 2 sends Config BPDUs"); + check((last_tx[1].flags & 0x02) == 0, "without a proposal"); + check(((stp_legacy >> 1) & 1) == 1 && ((stp_legacy >> 8) & 1) == 0, "only port 2 is switched, not the root port"); +} + +static void scen_migrate_back(void) +{ + printf("38. mcheck and an RST neighbour bring the port back to RSTP\n"); + scen_migrate_to_stp(); + struct sim_bpdu r = root_on(8); + secs(4); + stp_port_mcheck(1); + tick(2); + check(last_tx[1].version == 2, "mcheck makes port 2 send RST BPDUs again"); + struct sim_bpdu old = { .port = 1, .root_prio = 0x40, .root_cost = 200000, + .br_prio = 0x80, .port_id = 4, .legacy = 1 }; + memcpy(old.root_mac, ROOT_MAC, 6); + memcpy(old.br_mac, PEER_MAC, 6); + bpdu_in(&old); + tick(STP_HZ); + check(last_tx[1].version == 2, "an 802.1D BPDU within the migrate time does not switch it yet"); + for (int i = 0; i < 4; i++) { bpdu_in(&r); tick(STP_HZ / 2); } + tick(2 * STP_HZ); + check(last_tx[1].version == 0, "once the migrate time is over it falls back to STP"); + secs(4); + struct sim_bpdu rst = old; rst.legacy = 0; rst.flags = 0x0c; + bpdu_in(&rst); + tick(2); + check(last_tx[1].version == 2, "an RST BPDU after the migrate time switches it back to RSTP"); +} + +static void scen_legacy_root_tcn(void) +{ + printf("39. behind an 802.1D root port a topology change goes out as TCN\n"); + reset_all(); + stp_pflags[1] &= ~STP_PF_AUTOEDGE; + links_set(1 << 8); + secs(1); + struct sim_bpdu r = root_on(8); r.legacy = 1; + for (int i = 0; i < 18; i++) { bpdu_in(&r); secs(2); } + check(stp_root_port == 8, "port 9 is the root port"); + struct sim_bpdu ack = r; ack.flags = 0x81; + bpdu_in(&ack); + for (int i = 0; i < 5; i++) { bpdu_in(&r); secs(2); } + int tcn = tcn_frames[8]; + links_set((1 << 1) | (1 << 8)); + for (int i = 0; i < 18; i++) { bpdu_in(&r); secs(2); } + check_state(1, 3, "port 2 came up and forwards"); + check(tcn_frames[8] > tcn, "port 9 reports the change with TCN"); + check(last_tx[1].version == 2, "port 2, with no 802.1D neighbour, sends RST BPDUs"); +} + +static void scen_alternate_agrees(void) +{ + printf("40. an alternate port answers a proposal with an agreement\n"); + struct sim_bpdu r, peer; + ring_with_uplink_on_9(&r, &peer); + stp_pflags[2] &= ~STP_PF_AUTOEDGE; + links_set((1 << 0) | (1 << 2) | (1 << 8)); + for (int i = 0; i < 18; i++) { bpdu_in(&r); bpdu_in(&peer); secs(2); } + check(stp_root_port == 8 && port_state(0) == 1, "port 1 is the blocked alternate"); + check_state(2, 3, "designated port 3 forwards"); + struct sim_bpdu prop = peer; prop.flags = 0x3e; + int n = tx_frames[0]; + bpdu_in(&prop); + tick(2); + check(tx_frames[0] > n, "port 1 answers at once"); + check((last_tx[0].flags & 0x40) != 0, "with the agreement flag"); + check((last_tx[0].flags & 0x0c) == 0x04, "in a BPDU with the alternate role"); + check((last_tx[0].flags & 0x02) == 0, "and without a proposal of its own"); + check_state(0, 1, "port 1 stays discarding"); + check_state(8, 3, "the root port is not touched by the sync"); + check_state(2, 1, "the first proposal syncs designated port 3"); + for (int i = 0; i < 18; i++) { bpdu_in(&r); bpdu_in(&peer); secs(2); } + check_state(2, 3, "port 3 forwards again"); + n = tx_frames[0]; + bpdu_in(&prop); + tick(2); + check(tx_frames[0] > n && (last_tx[0].flags & 0x40), "a repeated proposal is answered again"); + check_state(2, 3, "but does not sync port 3 a second time"); + n = tx_frames[0]; + for (int i = 0; i < 3; i++) { bpdu_in(&r); bpdu_in(&peer); secs(2); } + check(tx_frames[0] == n, "without a proposal the alternate port stays silent"); + stp_pcost[8] = 0; +} + +static void scen_agreement_from_alternate(void) +{ + printf("41. an agreement from an alternate port opens a designated port\n"); + reset_all(); + stp_pflags[1] &= ~STP_PF_AUTOEDGE; + links_set(1 << 8); + secs(1); + struct sim_bpdu r = root_on(8); + for (int i = 0; i < 3; i++) { bpdu_in(&r); secs(2); } + links_set((1 << 1) | (1 << 8)); + tick(STP_HZ + STP_HZ / 2); + check_state(1, 1, "port 2 proposes from discarding"); + struct sim_bpdu agree = { .port = 1, .root_prio = 0x40, .root_cost = 22000, + .br_prio = 0x80, .port_id = 3, .flags = 0x44 }; + memcpy(agree.root_mac, ROOT_MAC, 6); + memcpy(agree.br_mac, PEER_MAC, 6); + bpdu_in(&agree); + tick(2); + check_state(1, 3, "the agreement in an alternate-role BPDU opens it"); +} + +static void scen_timer_relation(void) +{ + printf("42. bridge times must keep 2*(fwd-1) >= maxage >= 2*(hello+1)\n"); + reset_all(); + save_cmd = 1; + sim_cmd("stp fwd 30"); + sim_cmd("stp maxage 40"); + check(stp_fwddelay_s == 30 && stp_maxage_s == 40, "fwd 30 then maxage 40 is accepted"); + check(err_status == ERR_OK, "an accepted value reports no error, so it is kept in the command history"); + sim_cmd("stp fwd 20"); + check(stp_fwddelay_s == 30, "fwd 20 under maxage 40 is refused"); + check(err_status != ERR_OK, "a refused value reports an error, so it stays out of the command history"); + sim_cmd("stp port 3 cost x"); + check(err_status != ERR_OK, "a malformed command reports an error too"); + sim_cmd("stp maxage 6"); + sim_cmd("stp hello 3"); + check(stp_maxage_s == 6 && stp_hello_s == 2, "hello 3 under maxage 6 is refused"); + sim_cmd("stp fwd 4"); + sim_cmd("stp maxage 7"); + check(stp_fwddelay_s == 4 && stp_maxage_s == 6, "maxage 7 over fwd 4 is refused"); + stp_defaults(); + save_cmd = 0; + sim_cmd("stp maxage 40"); + sim_cmd("stp fwd 30"); + check(stp_maxage_s == 40 && stp_fwddelay_s == 30, "a saved config may pass through a broken order"); + stp_setup(); + check(stp_maxage_s == 40 && stp_fwddelay_s == 30, "and keeps its values when the result is consistent"); + sim_cmd("stp fwd 10"); + stp_setup(); + check(stp_hello_s == 2 && stp_maxage_s == 20 && stp_fwddelay_s == 15, "an inconsistent result falls back to 2/20/15 when STP starts"); + save_cmd = 1; +} + +static void scen_recent_root(void) +{ + printf("43. ports that were root a moment ago are held back when a new root port forwards\n"); + reset_all(); + stp_pflags[1] &= ~STP_PF_AUTOEDGE; + stp_pflags[2] &= ~STP_PF_AUTOEDGE; + links_set((1 << 1) | (1 << 8)); + secs(1); + struct sim_bpdu r9 = root_on(8); + for (int i = 0; i < 18; i++) { bpdu_in(&r9); secs(2); } + check(stp_root_port == 8, "port 9 is the root port"); + check_state(1, 3, "designated port 2 forwards"); + stp_rrwhile[1] = 10 * STP_HZ; + stp_pcost[2] = 2000; + links_set((1 << 1) | (1 << 2) | (1 << 8)); + secs(1); + struct sim_bpdu r3 = root_on(2); + bpdu_in(&r3); + tick(2); + check(stp_root_port == 2, "port 3, the cheaper path, is the new root port"); + check_state(2, 3, "and forwards at once"); + check_state(8, 1, "the old root port 9 is discarding"); + check_state(1, 1, "port 2, a recent root port too, is put back to discarding"); + struct sim_bpdu agree = { .port = 1, .root_prio = 0x40, .root_cost = 22000, + .br_prio = 0x80, .port_id = 3, .flags = 0x78 }; + memcpy(agree.root_mac, ROOT_MAC, 6); + memcpy(agree.br_mac, PEER_MAC, 6); + bpdu_in(&agree); + tick(2); + check_state(1, 1, "an agreement does not open it while it is a recent root"); + for (int i = 0; i < 6; i++) { bpdu_in(&r3); secs(2); } + bpdu_in(&agree); + tick(2); + check_state(1, 3, "once that time is over the agreement opens it"); + stp_pcost[2] = 0; +} + +static void scen_recent_backup(void) +{ + printf("44. a port that was a backup a moment ago does not rush into forwarding as root\n"); + reset_all(); + links_set(1 << 8); + secs(1); + struct sim_bpdu r = root_on(8); + for (int i = 0; i < 3; i++) { bpdu_in(&r); secs(2); } + stp_pflags[3] &= ~STP_PF_AUTOEDGE; + stp_pcost[3] = 2000; + stp_rbwhile[3] = 3 * STP_HZ; + links_set((1 << 3) | (1 << 8)); + secs(1); + struct sim_bpdu r4 = root_on(3); + bpdu_in(&r4); + tick(2); + check(stp_root_port == 3, "port 4 is the new root port"); + check_state(3, 1, "but stays discarding while it is a recent backup"); + for (int i = 0; i < 4; i++) { bpdu_in(&r4); secs(1); } + check_state(3, 3, "and forwards once that time is over"); + stp_pcost[3] = 0; +} + +static void scen_short_bpdu(void) +{ + printf("45. BPDUs shorter than their type allows are ignored\n"); + reset_all(); + links_set(1 << 8); + secs(1); + struct sim_bpdu r = root_on(8); + r.msg_len = 38; + bpdu_in(&r); + check(stp_root_port == 0xff, "an RST BPDU of 38 bytes is ignored"); + r.msg_len = 0; + bpdu_in(&r); + check(stp_root_port == 8, "at 39 bytes it is taken"); + reset_all(); + links_set(1 << 8); + secs(1); + struct sim_bpdu c = root_on(8); c.legacy = 1; c.msg_len = 37; + bpdu_in(&c); + check(stp_root_port == 0xff, "a Config BPDU of 37 bytes is ignored"); + c.msg_len = 0; + bpdu_in(&c); + check(stp_root_port == 8, "at 38 bytes it is taken"); + int n = tx_frames[8]; + struct sim_bpdu tcn = c; tcn.legacy = 2; tcn.msg_len = 6; + bpdu_in(&tcn); + check(tx_frames[8] == n, "a TCN of 6 bytes gets no TCA"); + tcn.msg_len = 0; + bpdu_in(&tcn); + check(tx_frames[8] > n, "a TCN of 7 bytes is acknowledged"); +} + +static void scen_port_roles(void) +{ + printf("46. the reported role covers every port, a port without link is disabled\n"); + struct sim_bpdu r, peer; + ring_with_uplink_on_9(&r, &peer); + check(stp_port_role(8) == 1, "port 9 is reported as root"); + check(stp_port_role(0) == 3, "port 1 as alternate"); + check(stp_port_role(4) == 0, "port 5, which has no link, as disabled"); + links_set((1 << 0) | (1 << 4) | (1 << 8)); + secs(2); + check(stp_port_role(4) == 2, "port 5 is designated once its link is up"); + stp_pflags[4] &= ~STP_PF_ENABLED; + check(stp_port_role(4) == 0, "and disabled again when STP is off on it"); + stp_pflags[4] |= STP_PF_ENABLED; + stp_pcost[8] = 0; +} + +static void scen_no_tx_without_link(void) +{ + printf("47. no BPDU goes out on a port without link\n"); + reset_all(); + links_set(1 << 8); + secs(1); + struct sim_bpdu r = root_on(8); + for (int i = 0; i < 5; i++) { bpdu_in(&r); secs(2); } + check(tx_frames[1] == 0, "port 2 without link sent nothing in ten seconds"); + stp_pflags[1] &= ~STP_PF_AUTOEDGE; + links_set((1 << 1) | (1 << 8)); + for (int i = 0; i < 2; i++) { bpdu_in(&r); secs(2); } + check(tx_frames[1] > 0, "once its link is up it sends"); +} + +static void scen_counters(void) +{ + printf("48. per-port BPDU and TC counters, and stp clear\n"); + reset_all(); + stp_pflags[1] &= ~STP_PF_AUTOEDGE; + links_set((1 << 1) | (1 << 8)); + struct sim_bpdu r = root_on(8); + for (int i = 0; i < 40; i++) { bpdu_in(&r); secs(1); } + check(stp_cnt[STP_CNT_RX][8] == 40 && stp_cnt[STP_CNT_RX][1] == 0, "each BPDU taken in is counted on its own port"); + check(stp_cnt[STP_CNT_TX][1] > 0 && stp_cnt[STP_CNT_TX][0] == 0, "BPDUs sent are counted, none on a port without link"); + int sent = 1; + for (int p = 0; p < NPORTS; p++) + sent &= stp_cnt[STP_CNT_TX][p] == (uint32_t)tx_frames[p]; + check(sent, "BPDUs sent match the frames on the wire on every port"); + check(stp_cnt[STP_CNT_TCRX][8] == 0, "a BPDU without the TC flag is not counted as TC"); + int same = 1, any = 0; + for (int p = 0; p < NPORTS; p++) { + same &= stp_cnt[STP_CNT_TCTX][p] == (uint32_t)(tc_frames[p] + tcn_frames[p]); + any |= tc_frames[p] + tcn_frames[p]; + } + check(any && same, "TC sent matches the TC and TCN frames on the wire"); + r.flags = 0x3d; + bpdu_in(&r); + check(stp_cnt[STP_CNT_TCRX][8] == 1, "a BPDU with the TC flag is counted as TC"); + struct sim_bpdu tcn = r; + tcn.legacy = 2; + bpdu_in(&tcn); + check(stp_cnt[STP_CNT_TCRX][8] == 2 && stp_cnt[STP_CNT_RX][8] == 42, "and so is a TCN"); + sim_cmd("stp clear"); + int zero = err_status == ERR_OK; + for (int k = 0; k < STP_CNT_N; k++) + for (int p = 0; p < NPORTS; p++) + zero &= stp_cnt[k][p] == 0; + check(zero, "stp clear zeroes every counter on every port"); +} + +static void scen_path_cost_method(void) +{ + printf("49. short path costs follow 802.1D-1998\n"); + reset_all(); + links_set(1 << 8); + struct sim_bpdu r = root_on(8); + for (int i = 0; i < 3; i++) { bpdu_in(&r); secs(1); } + check(root_bridge_cost == 20000, "long method: a 1G root port costs 20000"); + sim_cmd("stp pathcost short"); + check(err_status == ERR_OK && stp_pcost_short == 1, "stp pathcost short is accepted"); + for (int i = 0; i < 2; i++) { bpdu_in(&r); secs(1); } + check(root_bridge_cost == 4, "short method: the same port costs 4"); + sim_speed[8] = 4; + for (int i = 0; i < 2; i++) { bpdu_in(&r); secs(1); } + check(root_bridge_cost == 2, "and a 10G port costs 2"); + sim_cmd("stp pathcost medium"); + check(err_status != ERR_OK && stp_pcost_short == 1, "an unknown method is refused"); + sim_cmd("stp pathcost long"); + sim_speed[8] = 2; +} + +static void scen_bpdu_handling(void) +{ + printf("50. BPDU handling while STP is off: flood or filter\n"); + reset_all(); + stp_enabled = 0; + l2mc_calls = 0; + stp_off(); + check(l2mc_calls && (l2mc_pmask & PMASK_9) == PMASK_9, "by default BPDUs are flooded when STP stops"); + l2mc_calls = 0; + sim_cmd("stp bpdu filter"); + check(err_status == ERR_OK && l2mc_calls && l2mc_pmask == PMASK_CPU, + "filter keeps them on the CPU, at once while STP is off"); + stp_enabled = 1; + stp_setup(); + l2mc_calls = 0; + sim_cmd("stp bpdu flood"); + check(l2mc_calls == 0, "while STP runs the setting only waits"); + stp_enabled = 0; + stp_off(); + check((l2mc_pmask & PMASK_9) == PMASK_9, "and applies when STP stops"); + sim_cmd("stp bpdu drop"); + check(err_status != ERR_OK, "an unknown handling is refused"); + stp_enabled = 1; +} + +static void scen_last_tc(void) +{ + printf("51. time since the last topology change\n"); + reset_all(); + links_set(1 << 8); + struct sim_bpdu tcn = root_on(8); + tcn.legacy = 2; + secs(3); + uint16_t before = stp_tc_count; + bpdu_in(&tcn); + check(stp_tc_count != before, "a TCN moves the topology change counter"); + secs(5); + check(stp_tc_secs >= 4 && stp_tc_secs <= 5, "the clock counts the seconds since then"); + bpdu_in(&tcn); + secs(1); + check(stp_tc_secs <= 1, "and starts again at the next change"); +} + +static void scen_lag(void) +{ + printf("52. a link aggregation group is one port of the tree\n"); + reset_all(); + links_set((1 << 1) | (1 << 2) | (1 << 8)); + secs(1); + struct sim_bpdu before = root_on(2); + bpdu_in(&before); + check(stp_root_port == 2 && stp_info_while[2], "port 3 hears the root before it joins a group"); + sim_lag[0] = (1 << 1) | (1 << 2); + secs(1); + check(stp_info_while[2] == 0, "joining the group drops what the port heard on its own"); + check(stp_ent_id(STP_LAG_BASE) && !stp_ent_id(1) && !stp_ent_id(2), + "the group replaces its member ports"); + check(port_state(1) == 1 && port_state(2) == 1, "both members start discarding together"); + + struct sim_bpdu b = root_on(2); + bpdu_in(&b); + check(stp_root_port == STP_LAG_BASE, "a BPDU on either member reaches the group"); + struct sim_bpdu c = root_on(1); + c.root_cost = 4; + bpdu_in(&c); + check(stp_root_port == STP_LAG_BASE, "and the other member feeds the same port"); + secs(31); + bpdu_in(&b); + check(port_state(1) == 3 && port_state(2) == 3, "both members forward as the root port"); + + sim_cmd("stp port 2 cost 5"); + check(err_status != ERR_OK, "a member port cannot be set on its own"); + sim_cmd("stp lag 1 cost 5"); + check(err_status == ERR_OK && stp_pcost[STP_LAG_BASE] == 5, "the group takes the setting"); + sim_cmd("stp lag 5 cost 5"); + check(err_status != ERR_OK, "a group number past the last one is refused"); + + memset(tx_frames, 0, sizeof(tx_frames)); + tcn_frames[1] = 0; + struct sim_bpdu t = root_on(2); + t.legacy = 2; + bpdu_in(&t); + check(tx_frames[1] == 1 && tx_frames[2] == 0, "a reply leaves through the lowest member"); + check(last_tx[1].port_id == STP_LAG_BASE + 1, "with the group's own port id"); + + memset(flush_count, 0, sizeof(flush_count)); + links_set((1 << 2) | (1 << 8)); + secs(1); + check(port_state(2) == 3 && stp_root_port == STP_LAG_BASE, + "losing one member keeps the group forwarding"); + check(flush_count[2] == 0, "and is no topology change"); + memset(tx_frames, 0, sizeof(tx_frames)); + bpdu_in(&t); + check(tx_frames[1] == 0 && tx_frames[2] == 1, "replies move to the member that still has a link"); + + sim_lag[0] = 0; + links_set((1 << 1) | (1 << 2) | (1 << 8)); + secs(1); + check(!stp_ent_id(STP_LAG_BASE) && stp_ent_id(1) && stp_ent_id(2), + "dissolving the group gives the ports back"); + check(stp_root_port != STP_LAG_BASE, "and the group stops being the root port"); + check(port_state(1) == 1 && port_state(2) == 1, "the ports start over discarding"); +} + +int main(int argc, char **argv) +{ + verbose = argc > 1 && argv[1][0] == '-' ? (argv[1][1] == 'd' ? 2 : 1) : 0; + scen_edge_ports(); + scen_root_port(); + scen_ring(); + scen_ring_clears(); + scen_cheaper_path(); + scen_we_are_better(); + scen_alt_survives_link_bounce(); + scen_speed_cost(); + scen_root_times(); + scen_too_old(); + scen_info_expiry(); + scen_dport_tiebreak(); + scen_rxport_tiebreak(); + scen_inferior_info(); + scen_root_role_bpdu(); + scen_alternate_takes_over(); + scen_dbridge_tiebreak(); + scen_times_revert(); + scen_tc_via_root_port(); + scen_tc_window_rstp(); + scen_tc_propagation(); + scen_tc_legacy_tcn(); + scen_alt_forgets(); + scen_edge_no_tc(); + scen_link_loss_no_tc(); + scen_proposal_agreement(); + scen_no_agreement(); + scen_sync_on_proposal(); + scen_rapid_failover(); + scen_rapid_reroot(); + scen_agreement_other_root(); + scen_repeated_proposal(); + scen_dispute(); + scen_learning_state(); + scen_reply_to_inferior(); + scen_backup_role(); + scen_migrate_to_stp(); + scen_migrate_back(); + scen_legacy_root_tcn(); + scen_alternate_agrees(); + scen_agreement_from_alternate(); + scen_timer_relation(); + scen_recent_root(); + scen_recent_backup(); + scen_short_bpdu(); + scen_port_roles(); + scen_no_tx_without_link(); + scen_counters(); + scen_path_cost_method(); + scen_bpdu_handling(); + scen_last_tc(); + scen_lag(); + if (failures) { + printf("\n%d check(s) failed\n", failures); + return 1; + } + printf("\nall scenarios passed\n"); + return 0; +}