mirror of
https://github.com/logicog/RTLPlayground.git
synced 2026-10-29 17:54:14 +08:00
Merge pull request #444 from DrDoof/feat/rstp
stp: full RSTP and a reworked Spanning Tree page
This commit is contained in:
14 files changed
+3923
-677
No files matched your search
@@ -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 $< $@
|
||||
|
||||
+152
-50
@@ -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 <n>` 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,81 @@ encodes. All configured values are in seconds:
|
||||
| `stp fwd <n>` | 15 | 4–30 |
|
||||
| `stp txhold <n>` | 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 <n> 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 forwarding root or designated port is passed on the
|
||||
same way to the other ports, not back to the port it came in on; a port that
|
||||
is still discarding or learning drops it. 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 +167,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 <seconds>
|
||||
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 +176,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 +197,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 +214,76 @@ 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 <n>` 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 <n> 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.
|
||||
|
||||
The console's `age[s]` column (previously labelled `bpdu`) is the elapsed
|
||||
time since the last BPDU, capped at 255 seconds; it is not a receive count.
|
||||
Use the web counters or `/stpcnt.json` for full receive counts.
|
||||
|
||||
An own BPDU received back on another port can temporarily hold its sending
|
||||
port in discarding, even if that sending port has received no BPDUs itself.
|
||||
When the hold expires, auto-edge is restored if the sending port has remained
|
||||
silent. A continuing loop keeps the hold active.
|
||||
|
||||
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.
|
||||
+44
-10
@@ -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()}};
|
||||
|
||||
|
||||
+8
-1
@@ -264,6 +264,8 @@ button.danger{background:var(--bad);color:#fff;border-color:transparent}
|
||||
<p class="small mono" id="stpids">-</p>
|
||||
<div id="stpbridge" style="display:flex;gap:14px;flex-wrap:wrap;align-items:flex-end;margin-top:10px">
|
||||
<label><span class="small mut" data-i18n="stp_version">Version</span><br><select class="in" id="stpver"><option value="rstp">RSTP</option><option value="stp" data-i18n="stp_v_stp">STP compatible</option></select></label>
|
||||
<label><span class="small mut" data-i18n="stp_pcs">Path cost</span><br><select class="in" id="stppcs"><option value="long" data-i18n="stp_pcs_long">Long (32 bit)</option><option value="short" data-i18n="stp_pcs_short">Short (16 bit)</option></select></label>
|
||||
<label><span class="small mut" data-i18n="stp_bh">BPDUs while disabled</span><br><select class="in" id="stpbh"><option value="flood" data-i18n="stp_bh_flood">Flood</option><option value="filter" data-i18n="stp_bh_filter">Filter</option></select></label>
|
||||
<label><span class="small mut" data-i18n="stp_prio">Bridge priority</span><br><select class="in" id="stpprio"></select></label>
|
||||
<label><span class="small mut" data-i18n="stp_hello">Hello [s]</span><br><input class="in sm" id="stphello" type="number" min="1" max="10"></label>
|
||||
<label><span class="small mut" data-i18n="stp_maxage">Max age [s]</span><br><input class="in sm" id="stpmaxage" type="number" min="6" max="40"></label>
|
||||
@@ -281,9 +283,14 @@ button.danger{background:var(--bad);color:#fff;border-color:transparent}
|
||||
</div>
|
||||
<div class="card"><h2 data-i18n="stp_status">Port status</h2>
|
||||
<div class="scrollx"><table class="t" id="stpstat">
|
||||
<thead><tr><th data-i18n="c_port">Port</th><th data-i18n="stp_role">Role</th><th data-i18n="c_state">State</th><th data-i18n="stp_db">Designated bridge</th><th data-i18n="stp_dp">Designated port</th><th data-i18n="stp_dc">Designated cost</th><th data-i18n="stp_oedge">Oper. edge</th><th data-i18n="stp_op2p">Oper. P2P</th></tr></thead>
|
||||
<thead><tr><th data-i18n="c_port">Port</th><th data-i18n="stp_role">Role</th><th data-i18n="c_state">State</th><th data-i18n="stp_db">Designated bridge</th><th data-i18n="stp_dp">Designated port</th><th data-i18n="stp_dc">Designated cost</th><th data-i18n="stp_oedge">Oper. edge</th><th data-i18n="stp_op2p">Oper. P2P</th><th data-i18n="stp_proto">Protocol</th><th></th></tr></thead>
|
||||
<tbody></tbody></table></div>
|
||||
</div>
|
||||
<div class="card"><details id="stpcntd"><summary><h2 style="display:inline" data-i18n="stp_cnt">Counters</h2></summary>
|
||||
<div class="scrollx"><table class="t" id="stpcnt">
|
||||
<thead><tr><th data-i18n="c_port">Port</th><th data-i18n="stp_crx">BPDUs received</th><th data-i18n="stp_ctx">BPDUs sent</th><th data-i18n="stp_ctrx">TC received</th><th data-i18n="stp_cttx">TC sent</th><th data-i18n="stp_cage">Last BPDU [s]</th></tr></thead>
|
||||
<tbody></tbody></table></div></details>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="tab" id="tab-stats">
|
||||
|
||||
@@ -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")) {
|
||||
|
||||
+1
-137
@@ -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");
|
||||
|
||||
+2
-1
@@ -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.
|
||||
|
||||
@@ -0,0 +1,195 @@
|
||||
#include "rtl837x_sfr.h"
|
||||
#include "rtl837x_common.h"
|
||||
#include "rtl837x_regs.h"
|
||||
#include "rtl837x_port.h"
|
||||
#include "uip.h"
|
||||
#include <stdint.h>
|
||||
#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, "]}");
|
||||
}
|
||||
+858
-468
File diff suppressed because it is too large.
Load diff
+31
-7
@@ -2,13 +2,21 @@
|
||||
#define _RTL837X_STP_H_
|
||||
|
||||
#include <stdint.h>
|
||||
#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
|
||||
@@ -0,0 +1,228 @@
|
||||
/*
|
||||
* Command line interface of the Spanning Tree Protocol for the RTL837x platform
|
||||
* This code is in the Public Domain
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#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");
|
||||
}
|
||||
@@ -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];
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
CC ?= cc
|
||||
SRCROOT = ../..
|
||||
CFLAGS = -std=c11 -g -O1 -Wall -Wextra -Werror -Wno-unused-parameter -Wno-unknown-pragmas \
|
||||
-Wno-pointer-sign \
|
||||
-fpack-struct=1 \
|
||||
-DRTLP_HOST_TEST \
|
||||
-fsanitize=address,undefined -fno-omit-frame-pointer \
|
||||
-include ../sdcc_shim.h -I../stub -I$(SRCROOT) -I$(SRCROOT)/httpd -I$(SRCROOT)/uip
|
||||
ifeq ($(findstring clang,$(shell $(CC) --version 2>/dev/null)),)
|
||||
CFLAGS += -Wno-builtin-declaration-mismatch
|
||||
endif
|
||||
LDFLAGS = -fsanitize=address,undefined
|
||||
|
||||
SRCS = stp_sim.c $(SRCROOT)/rtl837x_stp.c $(SRCROOT)/rtl837x_stp_cli.c
|
||||
|
||||
all: build/stp_sim
|
||||
./build/stp_sim
|
||||
|
||||
verbose: build/stp_sim
|
||||
./build/stp_sim -v
|
||||
|
||||
build/stp_sim: $(SRCS) $(SRCROOT)/rtl837x_stp.h
|
||||
mkdir -p build
|
||||
$(CC) $(CFLAGS) $(SRCS) -o $@ $(LDFLAGS)
|
||||
|
||||
clean:
|
||||
rm -rf build
|
||||
|
||||
.PHONY: all verbose clean
|
||||
+2362
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user