mirror of
https://github.com/logicog/RTLPlayground.git
synced 2026-10-29 17:54:14 +08:00
Merge pull request #299 from DrDoof/feat/lacp
lacp: implement IEEE 802.3ad (LACP)
This commit is contained in:
21 files changed
+1377
-15
No files matched your search
@@ -110,6 +110,7 @@ SRCS += \
|
|||||||
rtl837x_stp.c \
|
rtl837x_stp.c \
|
||||||
rtl837x_lldp.c \
|
rtl837x_lldp.c \
|
||||||
rtl837x_storm.c \
|
rtl837x_storm.c \
|
||||||
|
rtl837x_lacp.c \
|
||||||
rtl837x_trap.c
|
rtl837x_trap.c
|
||||||
SRCS += \
|
SRCS += \
|
||||||
httpd/httpd.c \
|
httpd/httpd.c \
|
||||||
|
|||||||
@@ -359,6 +359,7 @@ The following documents give further documentation on specific features of the R
|
|||||||
- [IGMP (IP-MC streaming)](doc/igmp.md)
|
- [IGMP (IP-MC streaming)](doc/igmp.md)
|
||||||
- [SFP+ ports](doc/sfp.md)
|
- [SFP+ ports](doc/sfp.md)
|
||||||
- [Trunking aka. port aggregation](doc/trunking.md)
|
- [Trunking aka. port aggregation](doc/trunking.md)
|
||||||
|
- [LACP (802.3ad link aggregation)](doc/lacp.md)
|
||||||
- [VLAN](doc/vlan.md)
|
- [VLAN](doc/vlan.md)
|
||||||
- [Storm control](doc/storm_control.md)
|
- [Storm control](doc/storm_control.md)
|
||||||
- [Modifications and Flash replacement](doc/mods.md)
|
- [Modifications and Flash replacement](doc/mods.md)
|
||||||
+24
-1
@@ -14,6 +14,7 @@
|
|||||||
#include "rtl837x_regs.h"
|
#include "rtl837x_regs.h"
|
||||||
#include "rtl837x_sfr.h"
|
#include "rtl837x_sfr.h"
|
||||||
#include "rtl837x_stp.h"
|
#include "rtl837x_stp.h"
|
||||||
|
#include "rtl837x_lacp.h"
|
||||||
#include "rtl837x_igmp.h"
|
#include "rtl837x_igmp.h"
|
||||||
#include "rtl837x_bandwidth.h"
|
#include "rtl837x_bandwidth.h"
|
||||||
#include "rtl837x_lldp.h"
|
#include "rtl837x_lldp.h"
|
||||||
@@ -534,11 +535,21 @@ void parse_lag(void)
|
|||||||
goto err;
|
goto err;
|
||||||
|
|
||||||
if (cmd_compare(2, "d")) {
|
if (cmd_compare(2, "d")) {
|
||||||
|
lacp_lag_set(group, 0);
|
||||||
port_lag_members_set(group, 0);
|
port_lag_members_set(group, 0);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t w = 2;
|
uint8_t w = 2;
|
||||||
|
__xdata uint8_t lacp_mode = 0;
|
||||||
|
if (cmd_words_len > 2 && cmd_compare(2, "lacp")) {
|
||||||
|
lacp_mode = 1;
|
||||||
|
w = 3;
|
||||||
|
if (cmd_words_len > 3 && cmd_compare(3, "off")) {
|
||||||
|
lacp_lag_set(group, 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
while (w < cmd_words_len) {
|
while (w < cmd_words_len) {
|
||||||
// Parse port.
|
// Parse port.
|
||||||
if (cmd_parse_port_separator(cmd_words_b[w++]) == 0)
|
if (cmd_parse_port_separator(cmd_words_b[w++]) == 0)
|
||||||
@@ -546,10 +557,15 @@ void parse_lag(void)
|
|||||||
|
|
||||||
members |= ((uint16_t)1) << atoi_results_u8;
|
members |= ((uint16_t)1) << atoi_results_u8;
|
||||||
}
|
}
|
||||||
|
if (lacp_mode) {
|
||||||
|
lacp_lag_set(group, members);
|
||||||
|
} else {
|
||||||
|
lacp_lag_set(group, 0);
|
||||||
port_lag_members_set(group, members);
|
port_lag_members_set(group, members);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
err:
|
err:
|
||||||
cmd_error("lag (show | <1-4> (d | <port>...))\n");
|
cmd_error("lag (show | <1-4> (d | [lacp] <port>...))\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -1974,6 +1990,13 @@ void cmd_parser(void) __banked
|
|||||||
} else if (cmd_compare(0, "health")) {
|
} else if (cmd_compare(0, "health")) {
|
||||||
health_show();
|
health_show();
|
||||||
#endif
|
#endif
|
||||||
|
} else if (cmd_compare(0, "lacp")) {
|
||||||
|
if (cmd_compare(1, "show"))
|
||||||
|
lacp_show();
|
||||||
|
else if (cmd_compare(1, "off"))
|
||||||
|
lacp_off();
|
||||||
|
else
|
||||||
|
cmd_error("lacp (show | off); a group starts LACP with: lag <1-4> lacp <port>...\n");
|
||||||
} else if (cmd_compare(0, "pvid")) {
|
} else if (cmd_compare(0, "pvid")) {
|
||||||
if (cmd_words_len == 3 && cmd_parse_port_separator(cmd_words_b[1]) != 0
|
if (cmd_words_len == 3 && cmd_parse_port_separator(cmd_words_b[1]) != 0
|
||||||
&& atoi_short(cmd_words_b[2]) && atoi_results_short && atoi_results_short <= 4094)
|
&& atoi_short(cmd_words_b[2]) && atoi_results_short && atoi_results_short <= 4094)
|
||||||
|
|||||||
+155
@@ -0,0 +1,155 @@
|
|||||||
|
# LACP (Link Aggregation Control Protocol, IEEE 802.3ad)
|
||||||
|
|
||||||
|
A static link aggregation group works only if both ends are configured for the
|
||||||
|
same links, and nothing checks that they are. LACP lets the two ends agree on
|
||||||
|
which links reach the same partner, aggregates only those, and takes a link out
|
||||||
|
of the group when the partner stops answering.
|
||||||
|
|
||||||
|
The switch runs LACP in software on the 8051. LACP frames belong to the Slow
|
||||||
|
Protocols, with destination `01:80:C2:00:00:02` and ethertype `0x8809`; the
|
||||||
|
subtype of LACP frames is 1. The implementation has the Receive, Selection and
|
||||||
|
Mux machines of 802.3ad clause 43 and does not aggregate links whose partners
|
||||||
|
differ. Up to `LACP_NUM_LAGS` (4) groups can run LACP; the others stay static.
|
||||||
|
|
||||||
|
## Getting the frames to the CPU
|
||||||
|
|
||||||
|
While LACP runs, the reserved multicast action for `01:80:C2:00:00:02` is
|
||||||
|
*trap*, the same mechanism STP uses for its BPDUs, see [stp.md](stp.md). A
|
||||||
|
trapped LACPDU has no VLAN tag after the RTL tag, and `TRAP_RX_BODY()` gives the
|
||||||
|
offset of the LACPDU in the received frame. When the last group leaves LACP,
|
||||||
|
the action goes back to *drop*.
|
||||||
|
|
||||||
|
## What has to match across the members
|
||||||
|
|
||||||
|
802.3ad requires that every link in an aggregate be full duplex and run at the
|
||||||
|
same speed. Beyond the standard, anything that changes how a frame is treated
|
||||||
|
has to be identical across members, because the hash picks the member per frame
|
||||||
|
and a difference would make the behaviour depend on that choice:
|
||||||
|
|
||||||
|
* must match: speed and duplex, VLAN membership and pvid, ingress filtering,
|
||||||
|
port isolation
|
||||||
|
* may differ: EEE, LED configuration, counters, cable diagnostics
|
||||||
|
|
||||||
|
None of this is enforced yet — `port_lag_members_set()` writes a member mask and
|
||||||
|
a hash setting and validates nothing. See #377 for the discussion of whether a
|
||||||
|
mismatch should be rejected or applied to the whole group.
|
||||||
|
|
||||||
|
## Protocol timing
|
||||||
|
|
||||||
|
The timer values are in units of the LACP timer, which steps every four ticks
|
||||||
|
of the system clock, 50 times per second:
|
||||||
|
|
||||||
|
| Constant | Value | Time |
|
||||||
|
|----------------------|-------|------|
|
||||||
|
| `LACP_FAST_PERIODIC` | 50 | 1 s |
|
||||||
|
| `LACP_SLOW_PERIODIC` | 1500 | 30 s |
|
||||||
|
| `LACP_SHORT_TIMEOUT` | 200 | 4 s |
|
||||||
|
|
||||||
|
802.3ad specifies a fast periodic time of 1 s with a short timeout of 3 s, and a
|
||||||
|
slow periodic time of 30 s with a long timeout of 90 s. The switch transmits at
|
||||||
|
the rate the partner asks for with its TIMEOUT bit. Its own timeout is always
|
||||||
|
the short one, one second above the standard, so a partner that stops sending
|
||||||
|
while its link stays up leaves the trunk after 4 s.
|
||||||
|
|
||||||
|
The Partner block of a transmitted LACPDU repeats the partner's actor
|
||||||
|
information as it was last received: system priority, system, key, port
|
||||||
|
priority, port and state. A Linux partner sets our SYNC only when this block
|
||||||
|
matches what it sent.
|
||||||
|
|
||||||
|
## LACP API
|
||||||
|
|
||||||
|
```
|
||||||
|
void lacp_init(void) __banked;
|
||||||
|
void lacp_off(void) __banked;
|
||||||
|
void lacp_lag_set(uint8_t lag, __xdata uint16_t ports) __banked;
|
||||||
|
void lacp_in(void) __banked;
|
||||||
|
void lacp_timers(void) __banked;
|
||||||
|
void lacp_show(void) __banked;
|
||||||
|
```
|
||||||
|
|
||||||
|
`lacp_init()` has to run before the startup configuration replays, because xdata
|
||||||
|
is not zeroed and the port-to-LAG map uses `0xff` as "no LAG"; a stray zero would
|
||||||
|
make a port look like it belongs to LAG 0.
|
||||||
|
|
||||||
|
## LACP configuration on the serial console
|
||||||
|
|
||||||
|
A group is put under LACP by adding `lacp` to the `lag` command:
|
||||||
|
|
||||||
|
```
|
||||||
|
lag 1 lacp 7 8 # ports 7 and 8 become LACP candidates of group 1
|
||||||
|
lag 1 lacp off # group 1 leaves LACP, its trunk is cleared (so does "lag 1 lacp")
|
||||||
|
lag 1 d # group 1 disabled, whether it ran LACP or not
|
||||||
|
lag 1 7 8 # plain static aggregation, no protocol
|
||||||
|
```
|
||||||
|
|
||||||
|
A group put under LACP gives up the static trunk it had: the hardware trunk is
|
||||||
|
cleared and rebuilt from the ports that converge. The other way round, `lag 1 d`
|
||||||
|
and a static member list take the group out of LACP before they touch the
|
||||||
|
trunk. A port named in another LACP group moves to the new one. The protocol
|
||||||
|
starts with the first group put under LACP and stops when the last one is taken
|
||||||
|
out; there is no separate switch for it. Two commands act on the whole engine:
|
||||||
|
|
||||||
|
```
|
||||||
|
lacp off # every group leaves LACP, protocol stopped
|
||||||
|
lacp show
|
||||||
|
```
|
||||||
|
|
||||||
|
`lacp show` prints the engine state, then one line per group with its candidate
|
||||||
|
ports, the elected aggregator and the trunk members actually programmed into the
|
||||||
|
hardware, then one line per port with the actor and partner state bytes and the
|
||||||
|
receive-machine state.
|
||||||
|
|
||||||
|
## LACP configuration via the Web Interface
|
||||||
|
|
||||||
|
Each group on the LAG page has a Static/LACP mode next to its member ports;
|
||||||
|
for an LACP group it shows the elected aggregator and the active members. Below
|
||||||
|
the groups a table has one row per LACP port with its group, actor and partner
|
||||||
|
state, receive state, LACPDU count and partner system. The page reads
|
||||||
|
`/lacp.json` every three seconds while it is open. The member checkboxes of a
|
||||||
|
static group come from the hardware registers in `/lag.json`.
|
||||||
|
|
||||||
|
## A test with a Linux 802.3ad bond
|
||||||
|
|
||||||
|
Two ports of the switch to two interfaces of one host:
|
||||||
|
|
||||||
|
```
|
||||||
|
ip link add bond0 type bond mode 802.3ad
|
||||||
|
ip link set enp1s0 down && ip link set enp1s0 master bond0
|
||||||
|
ip link set enp2s0 down && ip link set enp2s0 master bond0
|
||||||
|
ip link set bond0 up
|
||||||
|
```
|
||||||
|
|
||||||
|
On the switch, with those two ports as the group:
|
||||||
|
|
||||||
|
```
|
||||||
|
lag 1 lacp 7 8
|
||||||
|
```
|
||||||
|
|
||||||
|
`lacp show` should then give both ports an actor state of `0x3f` (ACTIVITY,
|
||||||
|
TIMEOUT, AGGREGATION, SYNC, COLLECTING and DISTRIBUTING), a partner state with
|
||||||
|
SYNC set, and the partner's system MAC. `lag show` lists the trunk members as
|
||||||
|
programmed in the hardware and should show both ports.
|
||||||
|
|
||||||
|
On the host, `/proc/net/bonding/bond0` should show the same `Aggregator ID` for
|
||||||
|
both slaves; different IDs mean that each link is in a group of its own. Both
|
||||||
|
churn states should read `none`.
|
||||||
|
|
||||||
|
Further checks:
|
||||||
|
|
||||||
|
* `tcpdump -i enp1s0 ether proto 0x8809` on each slave shows the LACPDUs of the
|
||||||
|
switch and of that slave, never those of the other slave.
|
||||||
|
* The host log has no `illegal loopback`.
|
||||||
|
* The three reserved bytes after the actor state in a LACPDU from the switch
|
||||||
|
are zero.
|
||||||
|
|
||||||
|
When one slave goes down, the group narrows to the remaining member, and the
|
||||||
|
slave rejoins after it comes back.
|
||||||
|
|
||||||
|
## Known limits
|
||||||
|
|
||||||
|
* No Churn Detection machines and no Marker protocol.
|
||||||
|
* Coupled Mux: collecting and distributing are enabled together.
|
||||||
|
* One aggregator per group.
|
||||||
|
* Selection compares the partner's system only, not its key: two aggregates
|
||||||
|
that one partner system offers on the same group land in one trunk here.
|
||||||
|
* Member compatibility is not validated; see #377.
|
||||||
@@ -227,3 +227,7 @@ iperf Done.
|
|||||||
```
|
```
|
||||||
As you can see, the total throughput was 4.71 GBit/sec which is close to the
|
As you can see, the total throughput was 4.71 GBit/sec which is close to the
|
||||||
maximum possible with a single 5GBit link.
|
maximum possible with a single 5GBit link.
|
||||||
|
|
||||||
|
## LACP
|
||||||
|
|
||||||
|
Dynamic aggregation with LACP is described in [lacp.md](lacp.md).
|
||||||
+45
-8
@@ -67,6 +67,9 @@ iso_title:"Port isolation",iso_from:"From port",iso_to:"To port",iso_all:"Allow
|
|||||||
iso_intro:"A frame entering a port may only leave through the ports ticked in its row. Isolation is decided before forwarding, so a port that is not ticked is unreachable directly, whatever the address table says.",
|
iso_intro:"A frame entering a port may only leave through the ports ticked in its row. Isolation is decided before forwarding, so a port that is not ticked is unreachable directly, whatever the address table says.",
|
||||||
iso_legend:"The cell of the port itself is always allowed. A port number in red marks a port that is not fully open.",
|
iso_legend:"The cell of the port itself is always allowed. A port number in red marks a port that is not fully open.",
|
||||||
lag_hash:"Hash:",lag_note:"A LAG needs at least one member to be saved in the startup config; applying an empty group clears it.",
|
lag_hash:"Hash:",lag_note:"A LAG needs at least one member to be saved in the startup config; applying an empty group clears it.",
|
||||||
|
lacp_heading:"LACP (802.3ad)",lag_mode:"Mode",lag_static:"Static",lacp_neg:"negotiating",lacp_agg:"aggregator",
|
||||||
|
lacp_members:"active members",lacp_hint:"LACP: select candidate ports and press Apply to start negotiation",
|
||||||
|
lacp_actor:"Actor",lacp_partner:"Partner",lacp_rxstate:"RX state",lacp_rx:"LACPDUs received",lacp_psys:"Partner system",
|
||||||
lag_clear_q:"Clear LAG {n}?",lag_clear_d:"All member ports return to normal operation.",
|
lag_clear_q:"Clear LAG {n}?",lag_clear_d:"All member ports return to normal operation.",
|
||||||
e_title:"Energy Efficient Ethernet",e_adv:"Advertised",e_lp:"Link partner",e_active:"Active",e_enable:"Enable",
|
e_title:"Energy Efficient Ethernet",e_adv:"Advertised",e_lp:"Link partner",e_active:"Active",e_enable:"Enable",
|
||||||
e_note:"Advertised and link-partner flags per speed: 100M, 1G, 2.5G. SFP ports do not support EEE.",
|
e_note:"Advertised and link-partner flags per speed: 100M, 1G, 2.5G. SFP ports do not support EEE.",
|
||||||
@@ -808,7 +811,7 @@ var CONF_CMDS=[
|
|||||||
/^port\s+\d{1,2}\s+lldp\s+(permit|block)+$/,
|
/^port\s+\d{1,2}\s+lldp\s+(permit|block)+$/,
|
||||||
/^eee\s+(on|off)(\s+\d{1,2})?(\s+(100m|1g|2g5))?$/,
|
/^eee\s+(on|off)(\s+\d{1,2})?(\s+(100m|1g|2g5))?$/,
|
||||||
/^mirror(\s+\d{1,2})(\s+\d{1,2}[tr]?)+$/,/^mirror\s+off$/,
|
/^mirror(\s+\d{1,2})(\s+\d{1,2}[tr]?)+$/,/^mirror\s+off$/,
|
||||||
/^lag\s+[1-4](\s+\d{1,2})+$/,/^lag\s+[1-4]\s+d$/,/^laghash\s+[1-4](\s+\w+)+$/,
|
/^lag\s+[1-4](\s+\d{1,2})+$/,/^lag\s+[1-4]\s+d$/,/^lag\s+[1-4]\s+lacp(\s+\d{1,2})+$/,/^lag\s+[1-4]\s+lacp\s+off$/,/^laghash\s+[1-4](\s+\w+)+$/,
|
||||||
/^isolate\s+\d{1,2}(\s+(off|\d{1,2}))+$/,
|
/^isolate\s+\d{1,2}(\s+(off|\d{1,2}))+$/,
|
||||||
/^stp\s+(on|off)$/,/^stp\s+(prio|hello|maxage|fwd|txhold)\s+\d{1,2}$/,
|
/^stp\s+(on|off)$/,/^stp\s+(prio|hello|maxage|fwd|txhold)\s+\d{1,2}$/,
|
||||||
/^stp\s+version\s+(rstp|stp)$/,
|
/^stp\s+version\s+(rstp|stp)$/,
|
||||||
@@ -1814,6 +1817,15 @@ function buildLag(){
|
|||||||
for(var g=1;g<=4;g++)(function(g){
|
for(var g=1;g<=4;g++)(function(g){
|
||||||
var card=h("div",{class:"card"});
|
var card=h("div",{class:"card"});
|
||||||
card.appendChild(h("h2",{text:"LAG "+g}));
|
card.appendChild(h("h2",{text:"LAG "+g}));
|
||||||
|
var mr=h("div",{style:"display:flex;gap:10px;flex-wrap:wrap;align-items:center;margin-bottom:10px",class:"small"});
|
||||||
|
mr.appendChild(h("span",{class:"mut",text:t("lag_mode")}));
|
||||||
|
var ms=h("select",{class:"in",id:"lgm"+g});
|
||||||
|
ms.appendChild(h("option",{value:"static",text:t("lag_static")}));
|
||||||
|
ms.appendChild(h("option",{value:"lacp",text:"LACP"}));
|
||||||
|
ms.addEventListener("change",function(){$("lgs"+g).textContent=ms.value==="lacp"?t("lacp_hint"):""});
|
||||||
|
mr.appendChild(ms);
|
||||||
|
card.appendChild(mr);
|
||||||
|
card.appendChild(h("p",{class:"small mono",id:"lgs"+g,style:"margin-bottom:10px"}));
|
||||||
var pr=h("div",{style:"display:flex;gap:10px;flex-wrap:wrap;margin-bottom:10px"});
|
var pr=h("div",{style:"display:flex;gap:10px;flex-wrap:wrap;margin-bottom:10px"});
|
||||||
for(var p=1;p<=S.n;p++)pr.appendChild(h("label",null,[
|
for(var p=1;p<=S.n;p++)pr.appendChild(h("label",null,[
|
||||||
h("input",{type:"checkbox",id:"lg"+g+"p"+p}),document.createTextNode(" "+p+" "),
|
h("input",{type:"checkbox",id:"lg"+g+"p"+p}),document.createTextNode(" "+p+" "),
|
||||||
@@ -1826,14 +1838,34 @@ function buildLag(){
|
|||||||
});
|
});
|
||||||
card.appendChild(hr);
|
card.appendChild(hr);
|
||||||
card.appendChild(h("button",{class:"ctl pri",text:t("c_apply"),onclick:function(){lagApply(g)}}));
|
card.appendChild(h("button",{class:"ctl pri",text:t("c_apply"),onclick:function(){lagApply(g)}}));
|
||||||
|
card.addEventListener("change",function(){card.dataset.dirty="1"});
|
||||||
w.appendChild(card);
|
w.appendChild(card);
|
||||||
})(g);
|
})(g);
|
||||||
}
|
}
|
||||||
|
var lacpCfg=[0,0,0,0,0];
|
||||||
|
function maskPorts(m){var r=[];for(var p=1;p<=S.n;p++)if((m>>S.physToLog[p-1])&1)r.push(p);return r.length?r.join(" "):"-"}
|
||||||
function lagLoad(){
|
function lagLoad(){
|
||||||
return getJSON("/lag.json").then(function(s){
|
return getJSON("/lacp.json").then(function(c){
|
||||||
|
var tb=$("lacptbl").tBodies[0];
|
||||||
|
tb.innerHTML="";
|
||||||
|
c.lags.forEach(function(lg,i){
|
||||||
|
var g=i+1;
|
||||||
|
lacpCfg[g]=parseInt(lg.cfg,16);
|
||||||
|
if($("lgm"+g).closest(".card").dataset.dirty)return;
|
||||||
|
$("lgm"+g).value=lacpCfg[g]?"lacp":"static";
|
||||||
|
$("lgs"+g).textContent=lacpCfg[g]?"LACP: "+t("lacp_agg")+" "+(lg.aggValid?lg.agg:"("+t("lacp_neg")+")")+", "+t("lacp_members")+" "+maskPorts(parseInt(lg.members,16)):"";
|
||||||
|
});
|
||||||
|
if(c.on)c.ports.forEach(function(p){
|
||||||
|
if(p.lag===255)return;
|
||||||
|
var tr=tb.insertRow();
|
||||||
|
[p.p,p.lag+1,p.a,p.pt,p.rs,parseInt(p.rx,16),p.psys].forEach(function(v){tr.insertCell().textContent=v});
|
||||||
|
});
|
||||||
|
return getJSON("/lag.json");
|
||||||
|
}).then(function(s){
|
||||||
s.forEach(function(l){
|
s.forEach(function(l){
|
||||||
var g=l.lagNum+1;
|
var g=l.lagNum+1;
|
||||||
var members=parseInt(l.members,2);
|
if($("lgm"+g).closest(".card").dataset.dirty)return;
|
||||||
|
var members=lacpCfg[g]||parseInt(l.members,2);
|
||||||
for(var p=1;p<=S.n;p++)
|
for(var p=1;p<=S.n;p++)
|
||||||
$("lg"+g+"p"+p).checked=!!((members>>S.physToLog[p-1])&1);
|
$("lg"+g+"p"+p).checked=!!((members>>S.physToLog[p-1])&1);
|
||||||
var hash=parseInt(l.hash,16);
|
var hash=parseInt(l.hash,16);
|
||||||
@@ -1842,21 +1874,25 @@ function lagLoad(){
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
function lagApply(g){
|
function lagApply(g){
|
||||||
var cmd="lag "+g,n=0;
|
var lacp=$("lgm"+g).value==="lacp",cmd="lag "+g+(lacp?" lacp":""),n=0,pre=[];
|
||||||
|
delete $("lgm"+g).closest(".card").dataset.dirty;
|
||||||
for(var p=1;p<=S.n;p++)if($("lg"+g+"p"+p).checked){cmd+=" "+p;n++;}
|
for(var p=1;p<=S.n;p++)if($("lg"+g+"p"+p).checked){cmd+=" "+p;n++;}
|
||||||
if(!n){
|
if(!lacp&&lacpCfg[g])pre.push("lag "+g+" lacp off");
|
||||||
|
if(lacp&&!n)cmd="lag "+g+" lacp off";
|
||||||
|
if(!n&&!lacp){
|
||||||
confirmModal(t("lag_clear_q",{n:g}),t("lag_clear_d"),function(){
|
confirmModal(t("lag_clear_q",{n:g}),t("lag_clear_d"),function(){
|
||||||
postCmd("lag "+g+" d").then(lagLoad).catch(function(){});
|
postCmds(pre.concat(["lag "+g+" d"])).then(lagLoad).catch(function(){});
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var hcmd="laghash "+g,nh=0;
|
var hcmd="laghash "+g,nh=0;
|
||||||
HASHF.forEach(function(f){if($("lg"+g+"h"+f).checked){hcmd+=" "+f;nh++;}});
|
HASHF.forEach(function(f){if($("lg"+g+"h"+f).checked){hcmd+=" "+f;nh++;}});
|
||||||
var cmds=[cmd];
|
var cmds=pre.concat([cmd]);
|
||||||
if(nh)cmds.push(hcmd);
|
if(nh)cmds.push(hcmd);
|
||||||
postCmds(cmds).then(lagLoad).catch(function(){});
|
postCmds(cmds).then(lagLoad).catch(function(){});
|
||||||
}
|
}
|
||||||
tabHooks.lag={enter:function(){needPorts(function(){buildLag();lagLoad().catch(function(){})})}};
|
var lagPoller=new Poller(function(){return lagLoad().catch(function(){})},3000);
|
||||||
|
tabHooks.lag={enter:function(){needPorts(function(){buildLag();lagPoller.start()})},leave:function(){lagPoller.stop()}};
|
||||||
|
|
||||||
|
|
||||||
function lldpLoad(){
|
function lldpLoad(){
|
||||||
@@ -2245,6 +2281,7 @@ function mergeConf(base,texts){
|
|||||||
else drop(/^eee /);
|
else drop(/^eee /);
|
||||||
conf.push(line);return;
|
conf.push(line);return;
|
||||||
}
|
}
|
||||||
|
if((m=line.match(/^lag (\d) lacp off$/))){drop(new RegExp("^lag "+m[1]+" lacp( |$)"));return;}
|
||||||
if(line==="mirror off"){drop(/^mirror /);return;}
|
if(line==="mirror off"){drop(/^mirror /);return;}
|
||||||
if(!isConfCmd(line))return;
|
if(!isConfCmd(line))return;
|
||||||
if((m=line.match(/^ingress (.+)$/))){
|
if((m=line.match(/^ingress (.+)$/))){
|
||||||
|
|||||||
@@ -356,6 +356,11 @@ button.danger{background:var(--bad);color:#fff;border-color:transparent}
|
|||||||
|
|
||||||
<section class="tab" id="tab-lag">
|
<section class="tab" id="tab-lag">
|
||||||
<div id="lagwrap" class="grid2"></div>
|
<div id="lagwrap" class="grid2"></div>
|
||||||
|
<div class="card" style="margin-top:14px"><h2 data-i18n="lacp_heading">LACP (802.3ad)</h2>
|
||||||
|
<div class="scrollx"><table class="t" id="lacptbl">
|
||||||
|
<thead><tr><th data-i18n="c_port">Port</th><th>LAG</th><th data-i18n="lacp_actor">Actor</th><th data-i18n="lacp_partner">Partner</th><th data-i18n="lacp_rxstate">RX state</th><th data-i18n="lacp_rx">LACPDUs received</th><th data-i18n="lacp_psys">Partner system</th></tr></thead>
|
||||||
|
<tbody></tbody></table></div>
|
||||||
|
</div>
|
||||||
<p class="small mut" style="margin-top:10px" data-i18n="lag_note">A LAG needs at least one member to be saved in the startup config; applying an empty group clears it.</p>
|
<p class="small mut" style="margin-top:10px" data-i18n="lag_note">A LAG needs at least one member to be saved in the startup config; applying an empty group clears it.</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|||||||
@@ -1036,6 +1036,8 @@ void httpd_appcall(void)
|
|||||||
send_stp();
|
send_stp();
|
||||||
} else if (is_word(q, "/lldp.json")) {
|
} else if (is_word(q, "/lldp.json")) {
|
||||||
send_lldp();
|
send_lldp();
|
||||||
|
} else if (is_word(q, "/lacp.json")) {
|
||||||
|
send_lacp();
|
||||||
} else if (is_word(q, "/vlanlist")) {
|
} else if (is_word(q, "/vlanlist")) {
|
||||||
send_vlanlist();
|
send_vlanlist();
|
||||||
} else if (is_word(q, "/config")) {
|
} else if (is_word(q, "/config")) {
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
#include "version.h"
|
#include "version.h"
|
||||||
#include "machine.h"
|
#include "machine.h"
|
||||||
#include "rtl837x_stp.h"
|
#include "rtl837x_stp.h"
|
||||||
|
#include "rtl837x_lacp.h"
|
||||||
#include "page_impl.h"
|
#include "page_impl.h"
|
||||||
#include "syslog.h"
|
#include "syslog.h"
|
||||||
|
|
||||||
@@ -736,6 +737,53 @@ void send_stp(void)
|
|||||||
slen += strtox(outbuf + slen, "]}");
|
slen += strtox(outbuf + slen, "]}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void send_lacp(void)
|
||||||
|
{
|
||||||
|
dbg_string("send_lacp called\n");
|
||||||
|
slen = strtox(outbuf, HTTP_RESPONCE_JSON);
|
||||||
|
|
||||||
|
slen += strtox(outbuf + slen, "{\"on\":");
|
||||||
|
bool_to_html(lacpEnabled);
|
||||||
|
slen += strtox(outbuf + slen, ",\"lags\":[");
|
||||||
|
for (uint8_t l = 0; l < LACP_NUM_LAGS; l++) {
|
||||||
|
slen += strtox(outbuf + slen, "{\"cfg\":\"");
|
||||||
|
byte_to_html(lacp_lag_ports[l] >> 8);
|
||||||
|
byte_to_html(lacp_lag_ports[l]);
|
||||||
|
slen += strtox(outbuf + slen, "\",\"aggValid\":");
|
||||||
|
bool_to_html(lacp_agg_valid[l]);
|
||||||
|
slen += strtox(outbuf + slen, ",\"agg\":\"");
|
||||||
|
for (uint8_t j = 0; j < MAC_ADDR_LEN; j++)
|
||||||
|
byte_to_html(lacp_agg_sys[l][j]);
|
||||||
|
slen += strtox(outbuf + slen, "\",\"members\":\"");
|
||||||
|
byte_to_html(lacp_members_last[l] >> 8);
|
||||||
|
byte_to_html(lacp_members_last[l]);
|
||||||
|
slen += strtox(outbuf + slen, "\"},");
|
||||||
|
}
|
||||||
|
slen -= 1; // remove comma
|
||||||
|
slen += strtox(outbuf + slen, "],\"ports\":[");
|
||||||
|
for (uint8_t i = machine.min_port; i <= machine.max_port; i++) {
|
||||||
|
slen += strtox(outbuf + slen, "{\"p\":");
|
||||||
|
itoa_html(machine.log_to_phys_port[i]);
|
||||||
|
slen += strtox(outbuf + slen, ",\"lag\":");
|
||||||
|
itoa_html(lacp_port_lag[i]);
|
||||||
|
slen += strtox(outbuf + slen, ",\"a\":\"");
|
||||||
|
byte_to_html(lacp_actor_state[i]);
|
||||||
|
slen += strtox(outbuf + slen, "\",\"pt\":\"");
|
||||||
|
byte_to_html(lacp_partner_state[i]);
|
||||||
|
slen += strtox(outbuf + slen, "\",\"rs\":");
|
||||||
|
itoa_html(lacp_rx_state[i]);
|
||||||
|
slen += strtox(outbuf + slen, ",\"rx\":\"");
|
||||||
|
byte_to_html(lacp_rx_count[i] >> 8);
|
||||||
|
byte_to_html(lacp_rx_count[i]);
|
||||||
|
slen += strtox(outbuf + slen, "\",\"psys\":\"");
|
||||||
|
for (uint8_t j = 0; j < MAC_ADDR_LEN; j++)
|
||||||
|
byte_to_html(lacp_partner_sys[i][j]);
|
||||||
|
slen += strtox(outbuf + slen, "\"},");
|
||||||
|
}
|
||||||
|
slen -= 1; // remove comma
|
||||||
|
slen += strtox(outbuf + slen, "]}");
|
||||||
|
}
|
||||||
|
|
||||||
void send_lldp(void)
|
void send_lldp(void)
|
||||||
{
|
{
|
||||||
dbg_string("send_lldp called\n");
|
dbg_string("send_lldp called\n");
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ void send_cmd_log(void);
|
|||||||
void send_lag(void);
|
void send_lag(void);
|
||||||
void send_stp(void);
|
void send_stp(void);
|
||||||
void send_lldp(void);
|
void send_lldp(void);
|
||||||
|
void send_lacp(void);
|
||||||
void send_vlanlist(void);
|
void send_vlanlist(void);
|
||||||
|
|
||||||
/* Convert only the lower nibble to ascii HEX char.
|
/* Convert only the lower nibble to ascii HEX char.
|
||||||
|
|||||||
@@ -16,6 +16,8 @@
|
|||||||
#define TICKS_DUE(last, period) ((uint8_t)((uint8_t)ticks - (last)) >= (period))
|
#define TICKS_DUE(last, period) ((uint8_t)((uint8_t)ticks - (last)) >= (period))
|
||||||
|
|
||||||
#define CPU_PORT 9
|
#define CPU_PORT 9
|
||||||
|
#define MAC_ADDR_LEN 6
|
||||||
|
#define PORT_BIT(p) ((uint16_t)1 << (p))
|
||||||
#define NUL '\0'
|
#define NUL '\0'
|
||||||
|
|
||||||
// Define Port-masks for 9-port devices and 6-port devices
|
// Define Port-masks for 9-port devices and 6-port devices
|
||||||
|
|||||||
+2
-2
@@ -22,8 +22,8 @@ extern __xdata uint8_t igmpEnabled;
|
|||||||
#include "uip.h"
|
#include "uip.h"
|
||||||
#include "uip_arp.h"
|
#include "uip_arp.h"
|
||||||
|
|
||||||
#pragma codeseg BANK1
|
#pragma codeseg BANK3
|
||||||
#pragma constseg BANK1
|
#pragma constseg BANK3
|
||||||
|
|
||||||
extern __xdata uint8_t cpuPort;
|
extern __xdata uint8_t cpuPort;
|
||||||
extern __xdata uint8_t sfr_data[4];
|
extern __xdata uint8_t sfr_data[4];
|
||||||
|
|||||||
+504
@@ -0,0 +1,504 @@
|
|||||||
|
/*
|
||||||
|
* LACP (IEEE 802.3ad Clause 43) driver for the RTL837x platform.
|
||||||
|
* This code is in the Public Domain.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// #define DEBUG
|
||||||
|
|
||||||
|
#pragma codeseg BANK3
|
||||||
|
#pragma constseg BANK3
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include "rtl837x_common.h"
|
||||||
|
#include "rtl837x_regs.h"
|
||||||
|
#include "rtl837x_lacp.h"
|
||||||
|
#include "rtl837x_trap.h"
|
||||||
|
#include "rtl837x_port.h"
|
||||||
|
#include "uip.h"
|
||||||
|
#include "machine.h"
|
||||||
|
|
||||||
|
extern __code const struct machine machine;
|
||||||
|
extern __xdata struct machine_runtime machine_detected;
|
||||||
|
extern __xdata uint16_t management_vlan;
|
||||||
|
extern __xdata struct uip_eth_addr uip_ethaddr;
|
||||||
|
extern __xdata uint8_t uip_buf[UIP_CONF_BUFFER_SIZE + 2];
|
||||||
|
|
||||||
|
/* 20-byte Actor/Partner information body (802.3ad 43.4.2.2), network byte order */
|
||||||
|
struct lacp_info {
|
||||||
|
uint16_t sys_prio;
|
||||||
|
uint8_t sys[MAC_ADDR_LEN];
|
||||||
|
uint16_t key;
|
||||||
|
uint16_t port_prio;
|
||||||
|
uint16_t port;
|
||||||
|
uint8_t state;
|
||||||
|
uint8_t reserved[3];
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Outgoing LACPDU (no VLAN tag on TX, as in stp_pkt) */
|
||||||
|
struct lacpdu {
|
||||||
|
uint8_t dst[MAC_ADDR_LEN];
|
||||||
|
uint8_t src[MAC_ADDR_LEN];
|
||||||
|
struct rtl_tag rtl_tag;
|
||||||
|
uint16_t ethertype;
|
||||||
|
uint8_t subtype;
|
||||||
|
uint8_t version;
|
||||||
|
uint8_t tlv_actor;
|
||||||
|
uint8_t actor_len;
|
||||||
|
struct lacp_info actor;
|
||||||
|
uint8_t tlv_partner;
|
||||||
|
uint8_t partner_len;
|
||||||
|
struct lacp_info partner;
|
||||||
|
uint8_t tlv_collector;
|
||||||
|
uint8_t collector_len;
|
||||||
|
uint16_t collector_max_delay;
|
||||||
|
uint8_t collector_reserved[12];
|
||||||
|
uint8_t tlv_terminator;
|
||||||
|
uint8_t terminator_len;
|
||||||
|
uint8_t terminator_reserved[50];
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Incoming LACPDU, split because a VLAN tag sits between the two only when the
|
||||||
|
* frame was forwarded: a trapped one carries none (cf. stp_pkt_in) */
|
||||||
|
struct lacpdu_in_hdr {
|
||||||
|
uint8_t dst[MAC_ADDR_LEN];
|
||||||
|
uint8_t src[MAC_ADDR_LEN];
|
||||||
|
struct rtl_tag rtl_tag;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct lacpdu_in {
|
||||||
|
uint16_t ethertype;
|
||||||
|
uint8_t subtype;
|
||||||
|
uint8_t version;
|
||||||
|
uint8_t tlv_actor;
|
||||||
|
uint8_t actor_len;
|
||||||
|
struct lacp_info actor;
|
||||||
|
uint8_t tlv_partner;
|
||||||
|
uint8_t partner_len;
|
||||||
|
struct lacp_info partner;
|
||||||
|
};
|
||||||
|
|
||||||
|
__xdata uint8_t lacp_rxbody;
|
||||||
|
|
||||||
|
#define LACP_O ((__xdata struct lacpdu *)&uip_buf[RTL_FRAME_DESC_SIZE])
|
||||||
|
#define LACP_H ((__xdata struct lacpdu_in_hdr *)&uip_buf[0])
|
||||||
|
#define LACP_I ((__xdata struct lacpdu_in *)&uip_buf[lacp_rxbody])
|
||||||
|
|
||||||
|
__xdata uint8_t lacp_actor_state[10];
|
||||||
|
__xdata uint8_t lacp_rx_state[10];
|
||||||
|
__xdata uint8_t lacp_partner_state[10];
|
||||||
|
__xdata uint8_t lacp_partner_sys[10][MAC_ADDR_LEN];
|
||||||
|
__xdata uint16_t lacp_partner_key[10];
|
||||||
|
__xdata uint16_t lacp_partner_port[10];
|
||||||
|
__xdata uint16_t lacp_partner_sys_prio[10];
|
||||||
|
__xdata uint16_t lacp_partner_port_prio[10];
|
||||||
|
__xdata uint16_t lacp_periodic[10];
|
||||||
|
__xdata uint16_t lacp_timeout[10];
|
||||||
|
__xdata uint8_t lacp_ntt[10];
|
||||||
|
|
||||||
|
#define LACP_TICK_STEP (SYS_TICK_HZ / 50)
|
||||||
|
#define LACP_CATCH_UP 8
|
||||||
|
__xdata uint8_t lacp_tick_last;
|
||||||
|
extern volatile __xdata uint32_t ticks;
|
||||||
|
|
||||||
|
__xdata uint16_t lacp_lag_ports[LACP_NUM_LAGS];
|
||||||
|
__xdata uint8_t lacp_port_lag[10];
|
||||||
|
__xdata uint8_t lacp_agg_sys[LACP_NUM_LAGS][MAC_ADDR_LEN];
|
||||||
|
__xdata uint8_t lacp_agg_valid[LACP_NUM_LAGS];
|
||||||
|
__xdata uint16_t lacp_members_last[LACP_NUM_LAGS];
|
||||||
|
|
||||||
|
__xdata uint16_t lacp_scratch_mask;
|
||||||
|
__xdata uint8_t lacp_scratch_flag;
|
||||||
|
__xdata uint8_t lacp_scratch_lag;
|
||||||
|
|
||||||
|
__xdata uint16_t lacp_rx_count[10];
|
||||||
|
|
||||||
|
|
||||||
|
static uint8_t lacp_sys_eq(__xdata uint8_t *a, __xdata uint8_t *b)
|
||||||
|
{
|
||||||
|
for (uint8_t i = 0; i < MAC_ADDR_LEN; i++) {
|
||||||
|
if (a[i] != b[i])
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static uint8_t lacp_port_selected(uint8_t port)
|
||||||
|
{
|
||||||
|
uint8_t lag = lacp_port_lag[port];
|
||||||
|
if (lag == LACP_LAG_NONE)
|
||||||
|
return 0;
|
||||||
|
if (lacp_rx_state[port] != LACP_RX_CURRENT)
|
||||||
|
return 0;
|
||||||
|
if (!(lacp_partner_state[port] & LACP_STATE_AGGREGATION))
|
||||||
|
return 0;
|
||||||
|
if (!lacp_agg_valid[lag])
|
||||||
|
return 0;
|
||||||
|
return lacp_sys_eq(lacp_partner_sys[port], lacp_agg_sys[lag]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static uint8_t lacp_mux_machine(uint8_t port)
|
||||||
|
{
|
||||||
|
uint8_t old = lacp_actor_state[port];
|
||||||
|
uint8_t new = old;
|
||||||
|
|
||||||
|
if (lacp_port_selected(port)) {
|
||||||
|
new |= LACP_STATE_SYNC;
|
||||||
|
if (lacp_partner_state[port] & LACP_STATE_SYNC)
|
||||||
|
new |= LACP_STATE_COLLECTING | LACP_STATE_DISTRIBUTING;
|
||||||
|
else
|
||||||
|
new &= ~(LACP_STATE_COLLECTING | LACP_STATE_DISTRIBUTING);
|
||||||
|
} else {
|
||||||
|
new &= ~(LACP_STATE_SYNC | LACP_STATE_COLLECTING | LACP_STATE_DISTRIBUTING);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (new == old)
|
||||||
|
return 0;
|
||||||
|
lacp_actor_state[port] = new;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void lacp_mux_update(void) __banked
|
||||||
|
{
|
||||||
|
for (uint8_t lag = 0; lag < LACP_NUM_LAGS; lag++) {
|
||||||
|
if (!lacp_lag_ports[lag])
|
||||||
|
continue;
|
||||||
|
lacp_scratch_mask = 0;
|
||||||
|
for (uint8_t i = machine.min_port; i <= machine.max_port; i++) {
|
||||||
|
if (lacp_port_lag[i] != lag)
|
||||||
|
continue;
|
||||||
|
if ((lacp_actor_state[i] & LACP_STATE_FULL) == LACP_STATE_FULL
|
||||||
|
&& (lacp_partner_state[i] & LACP_STATE_SYNC))
|
||||||
|
lacp_scratch_mask |= PORT_BIT(i);
|
||||||
|
}
|
||||||
|
if (lacp_scratch_mask != lacp_members_last[lag]) {
|
||||||
|
lacp_members_last[lag] = lacp_scratch_mask;
|
||||||
|
port_lag_members_set(lag, lacp_scratch_mask);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void lacp_send(uint8_t port) __banked
|
||||||
|
{
|
||||||
|
uint16_t saved_mgmt_vlan;
|
||||||
|
|
||||||
|
memset((__xdata uint8_t *)LACP_O, 0, sizeof(struct lacpdu));
|
||||||
|
LACP_O->dst[0] = 0x01; LACP_O->dst[1] = 0x80; LACP_O->dst[2] = 0xc2;
|
||||||
|
LACP_O->dst[3] = 0x00; LACP_O->dst[4] = 0x00; LACP_O->dst[5] = LACP_DST5;
|
||||||
|
memcpy(LACP_O->src, uip_ethaddr.addr, MAC_ADDR_LEN);
|
||||||
|
|
||||||
|
LACP_O->rtl_tag.tag = HTONS(RTL_FRAME_TAG_ID);
|
||||||
|
LACP_O->rtl_tag.version = RTL_FRAME_TAG_VERSION;
|
||||||
|
LACP_O->rtl_tag.reason = 0x00;
|
||||||
|
LACP_O->rtl_tag.flags = HTONS(RTL_TAG_LEARN_DIS | RTL_TAG_KEEP);
|
||||||
|
LACP_O->rtl_tag.pmask = HTONS(PORT_BIT(port));
|
||||||
|
|
||||||
|
LACP_O->ethertype = HTONS(SLOW_PROTO_ETHERTYPE);
|
||||||
|
LACP_O->subtype = SLOW_PROTO_SUBTYPE_LACP;
|
||||||
|
LACP_O->version = LACP_VERSION;
|
||||||
|
|
||||||
|
LACP_O->tlv_actor = LACP_TLV_ACTOR;
|
||||||
|
LACP_O->actor_len = LACP_TLV_LEN_INFO;
|
||||||
|
LACP_O->actor.sys_prio = HTONS(LACP_SYS_PRIO);
|
||||||
|
memcpy(LACP_O->actor.sys, uip_ethaddr.addr, MAC_ADDR_LEN);
|
||||||
|
LACP_O->actor.key = HTONS((uint16_t)(lacp_port_lag[port] + 1));
|
||||||
|
LACP_O->actor.port_prio = HTONS(LACP_DEF_PORT_PRIO);
|
||||||
|
LACP_O->actor.port = HTONS((uint16_t)port + 1);
|
||||||
|
LACP_O->actor.state = lacp_actor_state[port];
|
||||||
|
|
||||||
|
LACP_O->tlv_partner = LACP_TLV_PARTNER;
|
||||||
|
LACP_O->partner_len = LACP_TLV_LEN_INFO;
|
||||||
|
LACP_O->partner.sys_prio = HTONS(lacp_partner_sys_prio[port]);
|
||||||
|
memcpy(LACP_O->partner.sys, lacp_partner_sys[port], MAC_ADDR_LEN);
|
||||||
|
LACP_O->partner.key = HTONS(lacp_partner_key[port]);
|
||||||
|
LACP_O->partner.port_prio = HTONS(lacp_partner_port_prio[port]);
|
||||||
|
LACP_O->partner.port = HTONS(lacp_partner_port[port]);
|
||||||
|
LACP_O->partner.state = lacp_partner_state[port];
|
||||||
|
|
||||||
|
LACP_O->tlv_collector = LACP_TLV_COLLECTOR;
|
||||||
|
LACP_O->collector_len = LACP_TLV_LEN_COLLECTOR;
|
||||||
|
LACP_O->collector_max_delay = 0;
|
||||||
|
LACP_O->tlv_terminator = LACP_TLV_TERMINATOR;
|
||||||
|
LACP_O->terminator_len = 0x00;
|
||||||
|
|
||||||
|
lacp_ntt[port] = 0;
|
||||||
|
saved_mgmt_vlan = management_vlan;
|
||||||
|
management_vlan = 0;
|
||||||
|
uip_len = sizeof(struct lacpdu);
|
||||||
|
tcpip_output();
|
||||||
|
management_vlan = saved_mgmt_vlan;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void lacp_in(void) __banked
|
||||||
|
{
|
||||||
|
__xdata uint8_t port, lag;
|
||||||
|
|
||||||
|
lacp_rxbody = TRAP_RX_BODY();
|
||||||
|
if (uip_len < lacp_rxbody + sizeof(struct lacpdu_in)) {
|
||||||
|
uip_len = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
uip_len = 0;
|
||||||
|
|
||||||
|
if (LACP_I->ethertype != HTONS(SLOW_PROTO_ETHERTYPE)
|
||||||
|
|| LACP_I->subtype != SLOW_PROTO_SUBTYPE_LACP)
|
||||||
|
return;
|
||||||
|
|
||||||
|
port = ((uint8_t)HTONS(LACP_H->rtl_tag.pmask)) & 0x0f;
|
||||||
|
if (port < machine.min_port || port > machine.max_port)
|
||||||
|
return;
|
||||||
|
|
||||||
|
lag = lacp_port_lag[port];
|
||||||
|
if (lag == LACP_LAG_NONE)
|
||||||
|
return;
|
||||||
|
if (lacp_sys_eq(LACP_I->actor.sys, uip_ethaddr.addr))
|
||||||
|
return;
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef DEBUG
|
||||||
|
print_string("LACP in, port "); print_byte(port);
|
||||||
|
print_string(" partner_state "); print_byte(LACP_I->actor.state); write_char('\n');
|
||||||
|
#endif
|
||||||
|
|
||||||
|
lacp_rx_count[port]++;
|
||||||
|
|
||||||
|
memcpy(lacp_partner_sys[port], LACP_I->actor.sys, MAC_ADDR_LEN);
|
||||||
|
lacp_scratch_mask = HTONS(LACP_I->actor.key);
|
||||||
|
lacp_partner_key[port] = lacp_scratch_mask;
|
||||||
|
lacp_scratch_mask = HTONS(LACP_I->actor.port);
|
||||||
|
lacp_partner_port[port] = lacp_scratch_mask;
|
||||||
|
lacp_scratch_mask = HTONS(LACP_I->actor.sys_prio);
|
||||||
|
lacp_partner_sys_prio[port] = lacp_scratch_mask;
|
||||||
|
lacp_scratch_mask = HTONS(LACP_I->actor.port_prio);
|
||||||
|
lacp_partner_port_prio[port] = lacp_scratch_mask;
|
||||||
|
lacp_partner_state[port] = LACP_I->actor.state;
|
||||||
|
|
||||||
|
lacp_rx_state[port] = LACP_RX_CURRENT;
|
||||||
|
lacp_timeout[port] = LACP_SHORT_TIMEOUT;
|
||||||
|
|
||||||
|
if (!lacp_agg_valid[lag]) {
|
||||||
|
memcpy(lacp_agg_sys[lag], LACP_I->actor.sys, MAC_ADDR_LEN);
|
||||||
|
lacp_agg_valid[lag] = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lacp_mux_machine(port))
|
||||||
|
lacp_ntt[port] = 1;
|
||||||
|
|
||||||
|
if (LACP_I->partner.state != lacp_actor_state[port]
|
||||||
|
|| HTONS(LACP_I->partner.port) != (uint16_t)port + 1)
|
||||||
|
lacp_ntt[port] = 1;
|
||||||
|
|
||||||
|
lacp_mux_update();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
static void lacp_step(void)
|
||||||
|
{
|
||||||
|
for (uint8_t i = machine.min_port; i <= machine.max_port; i++) {
|
||||||
|
if (lacp_port_lag[i] == LACP_LAG_NONE)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (lacp_periodic[i]) {
|
||||||
|
lacp_periodic[i]--;
|
||||||
|
} else {
|
||||||
|
lacp_periodic[i] = (lacp_partner_state[i] & LACP_STATE_TIMEOUT) ?
|
||||||
|
LACP_FAST_PERIODIC : LACP_SLOW_PERIODIC;
|
||||||
|
lacp_ntt[i] = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lacp_timeout[i]) {
|
||||||
|
if (!--lacp_timeout[i]) {
|
||||||
|
lacp_rx_state[i] = LACP_RX_DEFAULTED;
|
||||||
|
lacp_partner_state[i] = LACP_STATE_DEFAULTED;
|
||||||
|
if (lacp_mux_machine(i))
|
||||||
|
lacp_ntt[i] = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lacp_ntt[i])
|
||||||
|
lacp_send(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint8_t lag = 0; lag < LACP_NUM_LAGS; lag++) {
|
||||||
|
uint8_t any_current = 0;
|
||||||
|
|
||||||
|
if (!lacp_agg_valid[lag])
|
||||||
|
continue;
|
||||||
|
for (uint8_t i = machine.min_port; i <= machine.max_port; i++) {
|
||||||
|
if (lacp_port_lag[i] == lag && lacp_rx_state[i] == LACP_RX_CURRENT
|
||||||
|
&& lacp_sys_eq(lacp_partner_sys[i], lacp_agg_sys[lag])) {
|
||||||
|
any_current = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!any_current)
|
||||||
|
lacp_agg_valid[lag] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
lacp_mux_update();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void lacp_timers(void) __banked
|
||||||
|
{
|
||||||
|
uint8_t n = LACP_CATCH_UP;
|
||||||
|
while (n-- && TICKS_DUE(lacp_tick_last, LACP_TICK_STEP)) {
|
||||||
|
lacp_tick_last += LACP_TICK_STEP;
|
||||||
|
lacp_step();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void lacp_port_init(uint8_t port)
|
||||||
|
{
|
||||||
|
lacp_actor_state[port] = LACP_STATE_ACTIVITY | LACP_STATE_AGGREGATION |
|
||||||
|
LACP_STATE_TIMEOUT;
|
||||||
|
lacp_rx_state[port] = LACP_RX_INITIALIZE;
|
||||||
|
lacp_partner_state[port] = LACP_STATE_DEFAULTED;
|
||||||
|
memset(lacp_partner_sys[port], 0, MAC_ADDR_LEN);
|
||||||
|
lacp_partner_key[port] = 0;
|
||||||
|
lacp_partner_port[port] = 0;
|
||||||
|
lacp_periodic[port] = LACP_FAST_PERIODIC;
|
||||||
|
lacp_timeout[port] = 0;
|
||||||
|
lacp_ntt[port] = 1;
|
||||||
|
lacp_rx_count[port] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void lacp_port_release(uint8_t port)
|
||||||
|
{
|
||||||
|
lacp_actor_state[port] = 0;
|
||||||
|
lacp_ntt[port] = 0;
|
||||||
|
lacp_partner_state[port] = 0;
|
||||||
|
lacp_rx_state[port] = LACP_RX_INITIALIZE;
|
||||||
|
lacp_rx_count[port] = 0;
|
||||||
|
memset(lacp_partner_sys[port], 0, MAC_ADDR_LEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint8_t lacp_any_lag(void)
|
||||||
|
{
|
||||||
|
for (uint8_t l = 0; l < LACP_NUM_LAGS; l++)
|
||||||
|
if (lacp_lag_ports[l])
|
||||||
|
return 1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void lacp_engine_on(void)
|
||||||
|
{
|
||||||
|
lacp_tick_last = (uint8_t)ticks;
|
||||||
|
lacpEnabled = 1;
|
||||||
|
trap_rma_set(RTL837X_RMA2_CONF, RTL837X_RMA_ACT_TRAP);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void lacp_engine_off(void)
|
||||||
|
{
|
||||||
|
trap_rma_set(RTL837X_RMA2_CONF, RTL837X_RMA_ACT_DROP);
|
||||||
|
lacpEnabled = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void lacp_init(void) __banked
|
||||||
|
{
|
||||||
|
for (uint8_t i = 0; i < 10; i++) {
|
||||||
|
lacp_port_lag[i] = LACP_LAG_NONE;
|
||||||
|
lacp_actor_state[i] = 0;
|
||||||
|
lacp_ntt[i] = 0;
|
||||||
|
}
|
||||||
|
for (uint8_t l = 0; l < LACP_NUM_LAGS; l++) {
|
||||||
|
lacp_lag_ports[l] = 0;
|
||||||
|
lacp_agg_valid[l] = 0;
|
||||||
|
lacp_members_last[l] = 0;
|
||||||
|
}
|
||||||
|
lacpEnabled = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void lacp_lag_set(uint8_t lag, __xdata uint16_t ports) __banked
|
||||||
|
{
|
||||||
|
if (lag >= LACP_NUM_LAGS)
|
||||||
|
return;
|
||||||
|
if (ports && ports == lacp_lag_ports[lag])
|
||||||
|
return;
|
||||||
|
lacp_scratch_flag = lacp_any_lag();
|
||||||
|
|
||||||
|
for (uint8_t i = machine.min_port; i <= machine.max_port; i++) {
|
||||||
|
if (lacp_port_lag[i] == lag && !(ports & PORT_BIT(i))) {
|
||||||
|
lacp_port_lag[i] = LACP_LAG_NONE;
|
||||||
|
lacp_port_release(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (lacp_members_last[lag] || ports) {
|
||||||
|
lacp_members_last[lag] = 0;
|
||||||
|
port_lag_members_set(lag, 0);
|
||||||
|
}
|
||||||
|
lacp_agg_valid[lag] = 0;
|
||||||
|
lacp_lag_ports[lag] = ports;
|
||||||
|
|
||||||
|
if (ports && !lacp_scratch_flag)
|
||||||
|
lacp_engine_on();
|
||||||
|
|
||||||
|
for (uint8_t i = machine.min_port; i <= machine.max_port; i++) {
|
||||||
|
if (!(ports & PORT_BIT(i)))
|
||||||
|
continue;
|
||||||
|
lacp_scratch_lag = lacp_port_lag[i];
|
||||||
|
lacp_port_lag[i] = lag;
|
||||||
|
if (lacp_scratch_lag != LACP_LAG_NONE && lacp_scratch_lag != lag) {
|
||||||
|
lacp_scratch_mask = lacp_lag_ports[lacp_scratch_lag] & ~PORT_BIT(i);
|
||||||
|
lacp_lag_ports[lacp_scratch_lag] = lacp_scratch_mask;
|
||||||
|
if (!lacp_scratch_mask) {
|
||||||
|
lacp_agg_valid[lacp_scratch_lag] = 0;
|
||||||
|
if (lacp_members_last[lacp_scratch_lag]) {
|
||||||
|
lacp_members_last[lacp_scratch_lag] = 0;
|
||||||
|
port_lag_members_set(lacp_scratch_lag, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lacp_port_init(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!lacp_any_lag() && lacp_scratch_flag)
|
||||||
|
lacp_engine_off();
|
||||||
|
|
||||||
|
lacp_mux_update();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void lacp_off(void) __banked
|
||||||
|
{
|
||||||
|
for (uint8_t l = 0; l < LACP_NUM_LAGS; l++)
|
||||||
|
if (lacp_lag_ports[l])
|
||||||
|
lacp_lag_set(l, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void lacp_show(void) __banked
|
||||||
|
{
|
||||||
|
print_string("LACP "); print_string(lacpEnabled ? "on" : "off"); write_char('\n');
|
||||||
|
for (uint8_t l = 0; l < LACP_NUM_LAGS; l++) {
|
||||||
|
if (!lacp_lag_ports[l])
|
||||||
|
continue;
|
||||||
|
print_string("lag "); print_byte(l + 1);
|
||||||
|
print_string(" ports "); print_short(lacp_lag_ports[l]);
|
||||||
|
print_string(" aggregator ");
|
||||||
|
if (lacp_agg_valid[l]) {
|
||||||
|
for (uint8_t j = 0; j < MAC_ADDR_LEN; j++)
|
||||||
|
print_byte(lacp_agg_sys[l][j]);
|
||||||
|
} else {
|
||||||
|
print_string("(none)");
|
||||||
|
}
|
||||||
|
print_string(" members "); print_short(lacp_members_last[l]); write_char('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint8_t i = machine.min_port; i <= machine.max_port; i++) {
|
||||||
|
print_string("port "); print_byte(machine.log_to_phys_port[i]);
|
||||||
|
print_string(" actor "); print_byte(lacp_actor_state[i]);
|
||||||
|
print_string(" partner "); print_byte(lacp_partner_state[i]);
|
||||||
|
print_string(" rxst "); print_byte(lacp_rx_state[i]);
|
||||||
|
print_string(" rx "); print_short(lacp_rx_count[i]);
|
||||||
|
print_string(" psys ");
|
||||||
|
for (uint8_t j = 0; j < MAC_ADDR_LEN; j++)
|
||||||
|
print_byte(lacp_partner_sys[i][j]);
|
||||||
|
write_char('\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
#ifndef _RTL837X_LACP_H_
|
||||||
|
#define _RTL837X_LACP_H_
|
||||||
|
|
||||||
|
/*
|
||||||
|
* LACP (IEEE 802.3ad Clause 43) for the RTL837x platform.
|
||||||
|
* This code is in the Public Domain.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include "rtl837x_common.h"
|
||||||
|
|
||||||
|
void lacp_init(void) __banked;
|
||||||
|
void lacp_in(void) __banked;
|
||||||
|
void lacp_timers(void) __banked;
|
||||||
|
void lacp_off(void) __banked;
|
||||||
|
void lacp_show(void) __banked;
|
||||||
|
void lacp_lag_set(uint8_t lag, __xdata uint16_t ports) __banked;
|
||||||
|
|
||||||
|
#define LACP_NUM_LAGS 4
|
||||||
|
#define LACP_LAG_NONE 0xff
|
||||||
|
|
||||||
|
extern __xdata uint8_t lacpEnabled;
|
||||||
|
extern __xdata uint8_t lacp_actor_state[10];
|
||||||
|
extern __xdata uint8_t lacp_partner_state[10];
|
||||||
|
extern __xdata uint8_t lacp_rx_state[10];
|
||||||
|
extern __xdata uint8_t lacp_partner_sys[10][MAC_ADDR_LEN];
|
||||||
|
extern __xdata uint16_t lacp_rx_count[10];
|
||||||
|
extern __xdata uint8_t lacp_port_lag[10];
|
||||||
|
extern __xdata uint16_t lacp_lag_ports[LACP_NUM_LAGS];
|
||||||
|
extern __xdata uint8_t lacp_agg_sys[LACP_NUM_LAGS][MAC_ADDR_LEN];
|
||||||
|
extern __xdata uint8_t lacp_agg_valid[LACP_NUM_LAGS];
|
||||||
|
extern __xdata uint16_t lacp_members_last[LACP_NUM_LAGS];
|
||||||
|
|
||||||
|
#define SLOW_PROTO_ETHERTYPE 0x8809
|
||||||
|
#define SLOW_PROTO_SUBTYPE_LACP 0x01
|
||||||
|
#define LACP_VERSION 0x01
|
||||||
|
#define LACP_DST5 0x02
|
||||||
|
|
||||||
|
#define LACP_TLV_ACTOR 0x01
|
||||||
|
#define LACP_TLV_PARTNER 0x02
|
||||||
|
#define LACP_TLV_COLLECTOR 0x03
|
||||||
|
#define LACP_TLV_TERMINATOR 0x00
|
||||||
|
#define LACP_TLV_LEN_INFO 0x14
|
||||||
|
#define LACP_TLV_LEN_COLLECTOR 0x10
|
||||||
|
|
||||||
|
#define LACP_SYS_PRIO 0xffff
|
||||||
|
#define LACP_DEF_PORT_PRIO 0x00ff
|
||||||
|
|
||||||
|
#define LACP_STATE_ACTIVITY 0x01 /* active LACP, sends periodically */
|
||||||
|
#define LACP_STATE_TIMEOUT 0x02 /* short timeout requested */
|
||||||
|
#define LACP_STATE_AGGREGATION 0x04 /* link can be aggregated */
|
||||||
|
#define LACP_STATE_SYNC 0x08 /* in sync with the partner */
|
||||||
|
#define LACP_STATE_COLLECTING 0x10
|
||||||
|
#define LACP_STATE_DISTRIBUTING 0x20
|
||||||
|
#define LACP_STATE_DEFAULTED 0x40 /* partner information is the default */
|
||||||
|
#define LACP_STATE_EXPIRED 0x80
|
||||||
|
|
||||||
|
#define LACP_STATE_FULL (LACP_STATE_SYNC | LACP_STATE_COLLECTING | LACP_STATE_DISTRIBUTING)
|
||||||
|
|
||||||
|
#define LACP_FAST_PERIODIC 50
|
||||||
|
#define LACP_SLOW_PERIODIC 1500
|
||||||
|
#define LACP_SHORT_TIMEOUT 200
|
||||||
|
|
||||||
|
#define LACP_RX_INITIALIZE 0
|
||||||
|
#define LACP_RX_PORT_DISABLED 1
|
||||||
|
#define LACP_RX_EXPIRED 2
|
||||||
|
#define LACP_RX_DEFAULTED 3
|
||||||
|
#define LACP_RX_CURRENT 4
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -7,7 +7,6 @@
|
|||||||
#define LLDP_ETHERTYPE 0x88cc
|
#define LLDP_ETHERTYPE 0x88cc
|
||||||
#define LLDP_ETHERTYPE_LENGTH 2
|
#define LLDP_ETHERTYPE_LENGTH 2
|
||||||
#define LLDP_MAX_FRAME 256
|
#define LLDP_MAX_FRAME 256
|
||||||
#define MAC_ADDR_LEN 6
|
|
||||||
#define LLDP_TX_INTERVAL_TICKS 6000
|
#define LLDP_TX_INTERVAL_TICKS 6000
|
||||||
|
|
||||||
#define LLDP_PORT_ID_TLV_TYPE 2
|
#define LLDP_PORT_ID_TLV_TYPE 2
|
||||||
|
|||||||
+12
-2
@@ -13,6 +13,7 @@
|
|||||||
#include "rtl837x_port.h"
|
#include "rtl837x_port.h"
|
||||||
#include "rtl837x_trap.h"
|
#include "rtl837x_trap.h"
|
||||||
#include "rtl837x_stp.h"
|
#include "rtl837x_stp.h"
|
||||||
|
#include "rtl837x_lacp.h"
|
||||||
#include "rtl837x_igmp.h"
|
#include "rtl837x_igmp.h"
|
||||||
#include "rtl837x_leds.h"
|
#include "rtl837x_leds.h"
|
||||||
#include "rtl837x_bandwidth.h"
|
#include "rtl837x_bandwidth.h"
|
||||||
@@ -133,6 +134,7 @@ __xdata bool stp_enabled;
|
|||||||
__xdata uint8_t igmpEnabled;
|
__xdata uint8_t igmpEnabled;
|
||||||
__xdata bool lldp_enabled;
|
__xdata bool lldp_enabled;
|
||||||
__xdata char hostname[24]; /* device hostname, default set at boot, see rtl837x_common.h */
|
__xdata char hostname[24]; /* device hostname, default set at boot, see rtl837x_common.h */
|
||||||
|
__xdata uint8_t lacpEnabled;
|
||||||
|
|
||||||
__code const uint16_t bit_mask[16] = {
|
__code const uint16_t bit_mask[16] = {
|
||||||
0x0001, 0x0002, 0x0004, 0x0008, 0x0010, 0x0020, 0x0040, 0x0080,
|
0x0001, 0x0002, 0x0004, 0x0008, 0x0010, 0x0020, 0x0040, 0x0080,
|
||||||
@@ -1059,13 +1061,17 @@ void handle_rx(void)
|
|||||||
print_byte(uip_buf[3]); print_byte(uip_buf[4]); print_byte(uip_buf[5]); write_char('\n');
|
print_byte(uip_buf[3]); print_byte(uip_buf[4]); print_byte(uip_buf[5]); write_char('\n');
|
||||||
print_string(" MGMT-VLAN: "); print_short(management_vlan); write_char('\n');
|
print_string(" MGMT-VLAN: "); print_short(management_vlan); write_char('\n');
|
||||||
#endif
|
#endif
|
||||||
if (stp_enabled && uip_buf[0] == 0x01 && uip_buf[1] == 0x80 && uip_buf[2] == 0xc2 // STP packet?
|
if ((stp_enabled || lacpEnabled) && uip_buf[0] == 0x01 && uip_buf[1] == 0x80
|
||||||
&& uip_buf[3] == 0x00 && uip_buf[4] == 0x00 && uip_buf[5] == 0x00) {
|
&& uip_buf[2] == 0xc2 && uip_buf[3] == 0x00 && uip_buf[4] == 0x00) {
|
||||||
|
if (stp_enabled && uip_buf[5] == 0x00) {
|
||||||
stp_in();
|
stp_in();
|
||||||
if (uip_len) {
|
if (uip_len) {
|
||||||
print_string("STP TX\n");
|
print_string("STP TX\n");
|
||||||
tcpip_output();
|
tcpip_output();
|
||||||
}
|
}
|
||||||
|
} else if (lacpEnabled && uip_buf[5] == 0x02) {
|
||||||
|
lacp_in();
|
||||||
|
}
|
||||||
} else if (igmpEnabled && uip_buf[0] == 0x01 && uip_buf[1] == 0x00 && uip_buf[2] == 0x5e // IPv4-MC packet?
|
} else if (igmpEnabled && uip_buf[0] == 0x01 && uip_buf[1] == 0x00 && uip_buf[2] == 0x5e // IPv4-MC packet?
|
||||||
&& igmp_packet_handler()) {
|
&& igmp_packet_handler()) {
|
||||||
if (uip_len) {
|
if (uip_len) {
|
||||||
@@ -1290,6 +1296,8 @@ static void handle_tick(void)
|
|||||||
stp_timers();
|
stp_timers();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (lacpEnabled)
|
||||||
|
lacp_timers();
|
||||||
health_phase(HEALTH_PH_STP);
|
health_phase(HEALTH_PH_STP);
|
||||||
lldp_tick();
|
lldp_tick();
|
||||||
health_phase(HEALTH_LLDP);
|
health_phase(HEALTH_LLDP);
|
||||||
@@ -1792,6 +1800,8 @@ void main(void)
|
|||||||
#endif
|
#endif
|
||||||
stp_enabled = 0;
|
stp_enabled = 0;
|
||||||
stp_defaults(); /* 802.1D/w default config before any "stp ..." replay */
|
stp_defaults(); /* 802.1D/w default config before any "stp ..." replay */
|
||||||
|
lacpEnabled = 0;
|
||||||
|
lacp_init();
|
||||||
nic_setup();
|
nic_setup();
|
||||||
REG_SET(RTL837X_NIC_INT_MSK, NIC_INT_RXIE);
|
REG_SET(RTL837X_NIC_INT_MSK, NIC_INT_RXIE);
|
||||||
trap_init();
|
trap_init();
|
||||||
|
|||||||
+6
-1
@@ -28,7 +28,8 @@ TESTS = $(BUILDDIR)/test_cmd_editor \
|
|||||||
$(BUILDDIR)/test_port_tables \
|
$(BUILDDIR)/test_port_tables \
|
||||||
$(BUILDDIR)/test_page_json \
|
$(BUILDDIR)/test_page_json \
|
||||||
$(BUILDDIR)/test_httpd_tx \
|
$(BUILDDIR)/test_httpd_tx \
|
||||||
$(BUILDDIR)/test_tick_gate
|
$(BUILDDIR)/test_tick_gate \
|
||||||
|
$(BUILDDIR)/test_lacp
|
||||||
|
|
||||||
# The table tests link the register mock and one environment file; stub/
|
# The table tests link the register mock and one environment file; stub/
|
||||||
# stands in for the two headers a firmware build generates.
|
# stands in for the two headers a firmware build generates.
|
||||||
@@ -54,6 +55,10 @@ $(BUILDDIR)/test_tick_gate: test_tick_gate.c support.c $(SRCROOT)/cmd_editor.c |
|
|||||||
$(BUILDDIR)/test_page_json: test_page_json.c $(TABLE_ENV) hw_mock.h | $(BUILDDIR)
|
$(BUILDDIR)/test_page_json: test_page_json.c $(TABLE_ENV) hw_mock.h | $(BUILDDIR)
|
||||||
$(CC) $(CFLAGS) $(filter %.c,$^) -o $@ $(LDFLAGS)
|
$(CC) $(CFLAGS) $(filter %.c,$^) -o $@ $(LDFLAGS)
|
||||||
|
|
||||||
|
# rtl837x_lacp.c is compiled UNMODIFIED from the firmware tree.
|
||||||
|
$(BUILDDIR)/test_lacp: test_lacp.c $(SRCROOT)/rtl837x_lacp.c | $(BUILDDIR)
|
||||||
|
$(CC) $(CFLAGS) $^ -o $@ $(LDFLAGS)
|
||||||
|
|
||||||
# httpd/httpd.c and uip/uip.c are compiled UNMODIFIED from the firmware tree;
|
# httpd/httpd.c and uip/uip.c are compiled UNMODIFIED from the firmware tree;
|
||||||
# this test carries the environment below them itself.
|
# this test carries the environment below them itself.
|
||||||
$(BUILDDIR)/test_httpd_tx: test_httpd_tx.c $(SRCROOT)/httpd/httpd.c $(SRCROOT)/uip/uip.c | $(BUILDDIR)
|
$(BUILDDIR)/test_httpd_tx: test_httpd_tx.c $(SRCROOT)/httpd/httpd.c $(SRCROOT)/uip/uip.c | $(BUILDDIR)
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ fires — so it drops straight into CI.
|
|||||||
| `test_port_tables` | `rtl837x_port.c` | VLAN entry layout and round trip, PVID register sharing, static multicast and management entries, per-port flush, trunk membership and hash seed |
|
| `test_port_tables` | `rtl837x_port.c` | VLAN entry layout and round trip, PVID register sharing, static multicast and management entries, per-port flush, trunk membership and hash seed |
|
||||||
| `test_page_json` | `httpd/page_impl.c` + `rtl837x_port.c` | `/vlan.json`, `/vlanlist`, `/l2.json` (walk, wrap marker, paging inside `outbuf`), 64-bit counters in `/status.json` and `/counters.json` |
|
| `test_page_json` | `httpd/page_impl.c` + `rtl837x_port.c` | `/vlan.json`, `/vlanlist`, `/l2.json` (walk, wrap marker, paging inside `outbuf`), 64-bit counters in `/status.json` and `/counters.json` |
|
||||||
| `test_httpd_tx` | `httpd/httpd.c` + `uip/uip.c` | a GET of a static file against a client that moves its receive window: ACK accounting, continuation chunks out of flash, retransmission |
|
| `test_httpd_tx` | `httpd/httpd.c` + `uip/uip.c` | a GET of a static file against a client that moves its receive window: ACK accounting, continuation chunks out of flash, retransmission |
|
||||||
|
| `test_lacp` | `rtl837x_lacp.c` | state machines against a simulated 802.3ad partner: convergence, mis-cabling, expiry, re-election, two groups, delivery by trap |
|
||||||
|
|
||||||
## Adding a test for another module
|
## Adding a test for another module
|
||||||
1. Write `test_<module>.c` with `main()` driving the module's entry points and
|
1. Write `test_<module>.c` with `main()` driving the module's entry points and
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
#include "syslog.h"
|
#include "syslog.h"
|
||||||
#include "rtl837x_phy.h"
|
#include "rtl837x_phy.h"
|
||||||
#include "support.h"
|
#include "support.h"
|
||||||
|
#include "rtl837x_lacp.h"
|
||||||
|
|
||||||
/* ---- console: everything lands in out_buf (support.c) ---- */
|
/* ---- console: everything lands in out_buf (support.c) ---- */
|
||||||
void print_byte(uint8_t v)
|
void print_byte(uint8_t v)
|
||||||
@@ -124,3 +125,15 @@ uint8_t stp_prio = 0x80, stp_root_port = 0xff, stp_rstp = 1, stp_txhold = 6;
|
|||||||
uint8_t sfp_buf[16];
|
uint8_t sfp_buf[16];
|
||||||
bool sfp_read_block(uint8_t slot, uint8_t reg, uint8_t len) { (void)slot; (void)reg; (void)len; return false; }
|
bool sfp_read_block(uint8_t slot, uint8_t reg, uint8_t len) { (void)slot; (void)reg; (void)len; return false; }
|
||||||
void print_phys_port(uint8_t port) { itoa_short(machine.log_to_phys_port[port]); }
|
void print_phys_port(uint8_t port) { itoa_short(machine.log_to_phys_port[port]); }
|
||||||
|
|
||||||
|
uint8_t lacpEnabled;
|
||||||
|
uint8_t lacp_actor_state[10];
|
||||||
|
uint8_t lacp_partner_state[10];
|
||||||
|
uint8_t lacp_rx_state[10];
|
||||||
|
uint8_t lacp_partner_sys[10][MAC_ADDR_LEN];
|
||||||
|
uint16_t lacp_rx_count[10];
|
||||||
|
uint8_t lacp_port_lag[10];
|
||||||
|
uint16_t lacp_lag_ports[LACP_NUM_LAGS];
|
||||||
|
uint8_t lacp_agg_sys[LACP_NUM_LAGS][MAC_ADDR_LEN];
|
||||||
|
uint8_t lacp_agg_valid[LACP_NUM_LAGS];
|
||||||
|
uint16_t lacp_members_last[LACP_NUM_LAGS];
|
||||||
@@ -148,6 +148,7 @@ void send_mirror(void) { }
|
|||||||
void send_mtu(void) { }
|
void send_mtu(void) { }
|
||||||
void send_config(void) { }
|
void send_config(void) { }
|
||||||
void send_cmd_log(void) { }
|
void send_cmd_log(void) { }
|
||||||
|
void send_lacp(void) { }
|
||||||
void send_lag(void) { }
|
void send_lag(void) { }
|
||||||
void send_stp(void) { }
|
void send_stp(void) { }
|
||||||
void send_stp_counters(void) { }
|
void send_stp_counters(void) { }
|
||||||
|
|||||||
@@ -0,0 +1,480 @@
|
|||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
#include "rtl837x_common.h"
|
||||||
|
#include "machine.h"
|
||||||
|
#include "uip.h"
|
||||||
|
#include "rtl837x_regs.h"
|
||||||
|
#include "rtl837x_lacp.h"
|
||||||
|
|
||||||
|
/* ---------- platform mocks ---------- */
|
||||||
|
|
||||||
|
uint8_t uip_buf[UIP_CONF_BUFFER_SIZE + 2];
|
||||||
|
uint16_t uip_len;
|
||||||
|
struct uip_eth_addr uip_ethaddr = { .addr = {0x02,0x11,0x22,0x33,0x44,0x55} };
|
||||||
|
const struct machine machine = { .min_port = 0, .max_port = 7,
|
||||||
|
.log_to_phys_port = {5, 1, 2, 3, 4, 6, 7, 8, 9} };
|
||||||
|
struct machine_runtime machine_detected = { .isRTL8373 = 1 };
|
||||||
|
uint8_t lacpEnabled;
|
||||||
|
uint16_t management_vlan; /* mocked; slow-protocol path zeroes it per-frame */
|
||||||
|
|
||||||
|
static int verbose = 0;
|
||||||
|
void print_string(const 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 write_char(char c) { if (verbose) putchar(c); }
|
||||||
|
|
||||||
|
/* Trunk programming mock: record last mask per LAG + call count.
|
||||||
|
* hw_members mirrors LAG 0 so the single-aggregator scenarios read naturally. */
|
||||||
|
static uint16_t hw_members_lag[4];
|
||||||
|
static int hw_set_calls;
|
||||||
|
#define hw_members hw_members_lag[0]
|
||||||
|
void port_lag_members_set(uint8_t lag, uint16_t members)
|
||||||
|
{
|
||||||
|
if (lag < 4)
|
||||||
|
hw_members_lag[lag] = members;
|
||||||
|
hw_set_calls++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Reserved-multicast action mock: delivery of a LACPDU to the CPU rests on
|
||||||
|
* the action of the slow-protocols entry alone, so record what the engine
|
||||||
|
* sets. The real trap_rma_set() touches only the action field. */
|
||||||
|
static uint16_t hw_rma_reg;
|
||||||
|
static uint8_t hw_rma_act;
|
||||||
|
static int hw_rma_calls;
|
||||||
|
void trap_rma_set(uint16_t reg, uint8_t act);
|
||||||
|
void trap_rma_set(uint16_t reg, uint8_t act)
|
||||||
|
{
|
||||||
|
hw_rma_reg = reg;
|
||||||
|
hw_rma_act = act;
|
||||||
|
hw_rma_calls++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------- on-wire mirrors (independent re-statement of the layout) ---------- */
|
||||||
|
|
||||||
|
struct wire_info {
|
||||||
|
uint16_t sys_prio;
|
||||||
|
uint8_t sys[MAC_ADDR_LEN];
|
||||||
|
uint16_t key;
|
||||||
|
uint16_t port_prio;
|
||||||
|
uint16_t port;
|
||||||
|
uint8_t state;
|
||||||
|
uint8_t reserved[3];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct wire_pdu_out { /* what lacp_send() writes at uip_buf+12 */
|
||||||
|
uint8_t dst[MAC_ADDR_LEN];
|
||||||
|
uint8_t src[MAC_ADDR_LEN];
|
||||||
|
struct rtl_tag rtl_tag;
|
||||||
|
uint16_t ethertype;
|
||||||
|
uint8_t subtype;
|
||||||
|
uint8_t version;
|
||||||
|
uint8_t tlv_actor, actor_len;
|
||||||
|
struct wire_info actor;
|
||||||
|
uint8_t tlv_partner, partner_len;
|
||||||
|
struct wire_info partner;
|
||||||
|
uint8_t tlv_collector, collector_len;
|
||||||
|
uint16_t collector_max_delay;
|
||||||
|
uint8_t collector_reserved[12];
|
||||||
|
uint8_t tlv_terminator, terminator_len;
|
||||||
|
uint8_t terminator_reserved[50];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct wire_pdu_in { /* forwarded form: the hardware inserts a VLAN tag */
|
||||||
|
uint8_t dst[MAC_ADDR_LEN];
|
||||||
|
uint8_t src[MAC_ADDR_LEN];
|
||||||
|
struct rtl_tag rtl_tag;
|
||||||
|
struct vlan_tag vlan_tag;
|
||||||
|
uint16_t ethertype;
|
||||||
|
uint8_t subtype;
|
||||||
|
uint8_t version;
|
||||||
|
uint8_t tlv_actor, actor_len;
|
||||||
|
struct wire_info actor;
|
||||||
|
uint8_t tlv_partner, partner_len;
|
||||||
|
struct wire_info partner;
|
||||||
|
};
|
||||||
|
|
||||||
|
_Static_assert(sizeof(struct wire_info) == 18, "info TLV body must be 18");
|
||||||
|
_Static_assert(sizeof(struct wire_pdu_out) == 132, "TX pdu must be 132");
|
||||||
|
_Static_assert(sizeof(struct rtl_tag) == 8, "rtl tag must be 8");
|
||||||
|
|
||||||
|
/* ---------- TX capture ---------- */
|
||||||
|
|
||||||
|
static struct wire_pdu_out last_tx[10];
|
||||||
|
static int tx_count[10];
|
||||||
|
|
||||||
|
void tcpip_output(void)
|
||||||
|
{
|
||||||
|
struct wire_pdu_out *p = (struct wire_pdu_out *)&uip_buf[RTL_FRAME_DESC_SIZE];
|
||||||
|
uint16_t pmask = HTONS(p->rtl_tag.pmask);
|
||||||
|
for (int port = 0; port < 10; port++) {
|
||||||
|
if (pmask & (1u << port)) {
|
||||||
|
last_tx[port] = *p;
|
||||||
|
tx_count[port]++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
uip_len = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------- simulated partner ---------- */
|
||||||
|
|
||||||
|
/* Deliver a LACPDU into the switch on `port`, from partner system `sys`.
|
||||||
|
* pstate = partner's Actor_State flags. If `echo`, the partner echoes the
|
||||||
|
* switch's last-seen actor block back (fresh view) - required for quiescence. */
|
||||||
|
static void partner_frame_full(uint8_t port, const uint8_t sys[MAC_ADDR_LEN], uint8_t pstate,
|
||||||
|
int echo, uint16_t len, int tagged)
|
||||||
|
{
|
||||||
|
struct wire_pdu_in in;
|
||||||
|
memset(&in, 0, sizeof(in));
|
||||||
|
|
||||||
|
in.dst[0]=0x01; in.dst[1]=0x80; in.dst[2]=0xc2; in.dst[5]=0x02;
|
||||||
|
memcpy(in.src, sys, MAC_ADDR_LEN);
|
||||||
|
in.rtl_tag.tag = HTONS(RTL_FRAME_TAG_ID);
|
||||||
|
in.rtl_tag.pmask = HTONS(port); /* RX: 4-bit ingress port */
|
||||||
|
in.vlan_tag.svlan = HTONS(0x8100); /* the tag's TPID, which marks the body offset */
|
||||||
|
in.ethertype = HTONS(0x8809);
|
||||||
|
in.subtype = 0x01;
|
||||||
|
in.version = 0x01;
|
||||||
|
|
||||||
|
in.tlv_actor = 0x01; in.actor_len = 0x14;
|
||||||
|
in.actor.sys_prio = HTONS(0x8000);
|
||||||
|
memcpy(in.actor.sys, sys, MAC_ADDR_LEN);
|
||||||
|
in.actor.key = HTONS(0x0011);
|
||||||
|
in.actor.port_prio = HTONS(0x00ff);
|
||||||
|
in.actor.port = HTONS((uint16_t)port + 101);
|
||||||
|
in.actor.state = pstate;
|
||||||
|
|
||||||
|
in.tlv_partner = 0x02; in.partner_len = 0x14;
|
||||||
|
if (echo) { /* fresh view of us, from our last TX */
|
||||||
|
in.partner.sys_prio = last_tx[port].actor.sys_prio;
|
||||||
|
memcpy(in.partner.sys, last_tx[port].actor.sys, MAC_ADDR_LEN);
|
||||||
|
in.partner.key = last_tx[port].actor.key;
|
||||||
|
in.partner.port = last_tx[port].actor.port;
|
||||||
|
in.partner.state = last_tx[port].actor.state;
|
||||||
|
} /* else: zeros = stale view */
|
||||||
|
|
||||||
|
memcpy(uip_buf, &in, sizeof(in));
|
||||||
|
if (!tagged) { /* trapped: no VLAN tag, so the body sits earlier */
|
||||||
|
uint8_t *body = uip_buf + RTL_FRAME_DESC_SIZE + RTL_TAG_SIZE;
|
||||||
|
memmove(body, body + VLAN_TAG_SIZE,
|
||||||
|
sizeof(in) - (RTL_FRAME_DESC_SIZE + RTL_TAG_SIZE + VLAN_TAG_SIZE));
|
||||||
|
len -= VLAN_TAG_SIZE;
|
||||||
|
}
|
||||||
|
uip_len = len;
|
||||||
|
lacp_in();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void partner_frame_len(uint8_t port, const uint8_t sys[MAC_ADDR_LEN], uint8_t pstate, int echo, uint16_t len)
|
||||||
|
{
|
||||||
|
partner_frame_full(port, sys, pstate, echo, len, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void partner_frame(uint8_t port, const uint8_t sys[MAC_ADDR_LEN], uint8_t pstate, int echo)
|
||||||
|
{
|
||||||
|
partner_frame_full(port, sys, pstate, echo, sizeof(struct wire_pdu_in), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The same LACPDU as the reserved-multicast trap delivers it. */
|
||||||
|
static void partner_frame_trap(uint8_t port, const uint8_t sys[MAC_ADDR_LEN], uint8_t pstate, int echo)
|
||||||
|
{
|
||||||
|
partner_frame_full(port, sys, pstate, echo, sizeof(struct wire_pdu_in), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Advance time by n system ticks; the LACP timers step every fourth one */
|
||||||
|
volatile uint32_t ticks;
|
||||||
|
static void step(int n) { while (n--) { ticks++; lacp_timers(); } }
|
||||||
|
|
||||||
|
/* ---------- scenario runner ---------- */
|
||||||
|
|
||||||
|
static int failures;
|
||||||
|
#define CHECK(cond, name) do { \
|
||||||
|
if (cond) printf("PASS %s\n", name); \
|
||||||
|
else { printf("FAIL %s\n", name); failures++; } \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
#define P_ACT LACP_STATE_ACTIVITY
|
||||||
|
#define P_AGG LACP_STATE_AGGREGATION
|
||||||
|
#define P_SYNC LACP_STATE_SYNC
|
||||||
|
#define P_COL LACP_STATE_COLLECTING
|
||||||
|
#define P_DIST LACP_STATE_DISTRIBUTING
|
||||||
|
#define P_TO LACP_STATE_TIMEOUT /* partner requests short (fast) timeout */
|
||||||
|
|
||||||
|
static const uint8_t SYS_A[MAC_ADDR_LEN] = {0x02,0xaa,0xaa,0xaa,0xaa,0x01};
|
||||||
|
static const uint8_t SYS_B[MAC_ADDR_LEN] = {0x02,0xbb,0xbb,0xbb,0xbb,0x02};
|
||||||
|
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
verbose = (argc > 1 && !strcmp(argv[1], "-v"));
|
||||||
|
|
||||||
|
/* Boot init first: on host, zeroed globals would otherwise read as "every
|
||||||
|
* port belongs to LAG 0" (the firmware calls this from main() the same way) */
|
||||||
|
lacp_init();
|
||||||
|
|
||||||
|
/* T1: enable announces on every port with sane field contents */
|
||||||
|
lacp_lag_set(0, 0x00ff);
|
||||||
|
step(8);
|
||||||
|
int all_tx = 1, sane = 1;
|
||||||
|
for (int i = machine.min_port; i <= machine.max_port; i++) {
|
||||||
|
if (!tx_count[i]) all_tx = 0;
|
||||||
|
if (last_tx[i].ethertype != HTONS(0x8809) || last_tx[i].subtype != 1
|
||||||
|
|| last_tx[i].dst[5] != 0x02
|
||||||
|
|| !(last_tx[i].actor.state & (P_ACT|P_AGG|P_TO)))
|
||||||
|
sane = 0;
|
||||||
|
}
|
||||||
|
CHECK(all_tx && sane, "T1 enable: LACPDU announced on all ports, well-formed");
|
||||||
|
|
||||||
|
/* T2: partner without SYNC -> actor SYNC only, no members yet */
|
||||||
|
partner_frame(0, SYS_A, P_ACT|P_AGG|P_TO, 1);
|
||||||
|
partner_frame(1, SYS_A, P_ACT|P_AGG|P_TO, 1);
|
||||||
|
step(8);
|
||||||
|
CHECK((lacp_actor_state[0] & P_SYNC) && !(lacp_actor_state[0] & (P_COL|P_DIST))
|
||||||
|
&& hw_members == 0,
|
||||||
|
"T2 partner not in sync: actor SYNC only, trunk empty");
|
||||||
|
|
||||||
|
/* T3: partner in sync on ports 0,1 -> full converge, members 0x0003 */
|
||||||
|
partner_frame(0, SYS_A, P_ACT|P_AGG|P_TO|P_SYNC, 1);
|
||||||
|
partner_frame(1, SYS_A, P_ACT|P_AGG|P_TO|P_SYNC, 1);
|
||||||
|
step(8);
|
||||||
|
CHECK(hw_members == 0x0003
|
||||||
|
&& (lacp_actor_state[0] & (P_SYNC|P_COL|P_DIST)) == (P_SYNC|P_COL|P_DIST)
|
||||||
|
&& (lacp_actor_state[1] & (P_SYNC|P_COL|P_DIST)) == (P_SYNC|P_COL|P_DIST),
|
||||||
|
"T3 convergence: both ports collecting/distributing, trunk 0x0003");
|
||||||
|
|
||||||
|
/* T3b: partner-block echo must be VERBATIM. A Linux 802.3ad partner accepts
|
||||||
|
* our SYNC only if our Partner TLV mirrors its actor identity exactly; a
|
||||||
|
* hardcoded sys_prio/port_prio (instead of the recorded value) makes Linux
|
||||||
|
* clear partner-SYNC and the bond never distributes. Verify we echo the
|
||||||
|
* partner's own priorities back, not a constant. */
|
||||||
|
CHECK(HTONS(last_tx[0].partner.sys_prio) == 0x8000
|
||||||
|
&& HTONS(last_tx[0].partner.port_prio) == 0x00ff
|
||||||
|
&& HTONS(last_tx[0].partner.key) == 0x0011
|
||||||
|
&& HTONS(last_tx[0].partner.port) == (uint16_t)0 + 101,
|
||||||
|
"T3b partner echo: recorded partner priorities/key/port echoed verbatim");
|
||||||
|
|
||||||
|
/* T4: mis-cabling - port 2 sees a DIFFERENT system: must stay out */
|
||||||
|
partner_frame(2, SYS_B, P_ACT|P_AGG|P_TO|P_SYNC, 1);
|
||||||
|
step(8);
|
||||||
|
CHECK(hw_members == 0x0003 && !(lacp_actor_state[2] & P_SYNC),
|
||||||
|
"T4 mis-cabling: port with different partner system stays out");
|
||||||
|
|
||||||
|
/* T5: register-write economy: stable state must not rewrite the trunk */
|
||||||
|
int calls_before = hw_set_calls;
|
||||||
|
partner_frame(0, SYS_A, P_ACT|P_AGG|P_TO|P_SYNC, 1);
|
||||||
|
partner_frame(1, SYS_A, P_ACT|P_AGG|P_TO|P_SYNC, 1);
|
||||||
|
step(64);
|
||||||
|
CHECK(hw_set_calls == calls_before,
|
||||||
|
"T5 stable state: no redundant trunk register writes");
|
||||||
|
|
||||||
|
/* T6: stale echo triggers retransmit (update_NTT) */
|
||||||
|
int txc = tx_count[0];
|
||||||
|
partner_frame(0, SYS_A, P_ACT|P_AGG|P_TO|P_SYNC, 0 /* stale view of us */);
|
||||||
|
step(8);
|
||||||
|
CHECK(tx_count[0] > txc, "T6 update_NTT: stale partner view causes retransmit");
|
||||||
|
|
||||||
|
/* T7: expiry - partner silent past the short timeout: trunk drains,
|
||||||
|
* aggregator identity is released */
|
||||||
|
step(4 * LACP_SHORT_TIMEOUT + 64); /* past the short timeout */
|
||||||
|
CHECK(hw_members == 0 && lacp_agg_valid[0] == 0,
|
||||||
|
"T7 expiry: members drop to 0 and aggregator is released");
|
||||||
|
|
||||||
|
/* T8: re-convergence with a NEW partner system after release */
|
||||||
|
/* (needs a fresh announce first so the echo below carries current state) */
|
||||||
|
step(4 * 0x0100 + 8); /* let periodic TX refresh last_tx */
|
||||||
|
partner_frame(3, SYS_B, P_ACT|P_AGG|P_TO|P_SYNC, 1);
|
||||||
|
partner_frame(3, SYS_B, P_ACT|P_AGG|P_TO|P_SYNC, 1);
|
||||||
|
step(8);
|
||||||
|
CHECK(hw_members == 0x0008 && (lacp_actor_state[3] & P_COL),
|
||||||
|
"T8 re-election: new partner system forms a fresh aggregate");
|
||||||
|
|
||||||
|
/* T9: lacp off clears the trunk */
|
||||||
|
lacp_off();
|
||||||
|
CHECK(hw_members == 0 && lacpEnabled == 0, "T9 disable: trunk cleared");
|
||||||
|
|
||||||
|
/* ---- per-LAG scenarios: two independent aggregators ---- */
|
||||||
|
|
||||||
|
/* T10: lag 1 lacp {0,1} + lag 2 lacp {2,3}, both to the SAME partner
|
||||||
|
* system (worst case for sibling scoping): each converges into its own
|
||||||
|
* hardware trunk, and the engine reports enabled. */
|
||||||
|
lacp_lag_set(1, 0x0003); /* ports 0,1 */
|
||||||
|
lacp_lag_set(2, 0x000c); /* ports 2,3 */
|
||||||
|
step(8); /* announce */
|
||||||
|
for (int r = 0; r < 2; r++) { /* two rounds so echoes carry fresh state */
|
||||||
|
for (int p = 0; p < 4; p++)
|
||||||
|
partner_frame(p, SYS_A, P_ACT|P_AGG|P_TO|P_SYNC, 1);
|
||||||
|
step(8);
|
||||||
|
}
|
||||||
|
CHECK(lacpEnabled == 1
|
||||||
|
&& hw_members_lag[1] == 0x0003 && hw_members_lag[2] == 0x000c,
|
||||||
|
"T10 two LAGs: independent aggregates converge on their own trunks");
|
||||||
|
|
||||||
|
/* T11: removing one LAG releases only its trunk; the other keeps running */
|
||||||
|
lacp_lag_set(1, 0);
|
||||||
|
CHECK(hw_members_lag[1] == 0 && hw_members_lag[2] == 0x000c
|
||||||
|
&& lacpEnabled == 1,
|
||||||
|
"T11 remove one LAG: its trunk drains, the other LAG unaffected");
|
||||||
|
|
||||||
|
/* T12: a port in no LACP LAG ignores LACPDUs entirely */
|
||||||
|
int rx5_before = tx_count[5];
|
||||||
|
partner_frame(5, SYS_B, P_ACT|P_AGG|P_TO|P_SYNC, 1);
|
||||||
|
step(8);
|
||||||
|
CHECK(!(lacp_actor_state[5] & P_SYNC) && hw_members_lag[2] == 0x000c,
|
||||||
|
"T12 unassigned port: LACPDU ignored, no state change");
|
||||||
|
(void)rx5_before;
|
||||||
|
|
||||||
|
/* T13: removing the last LACP LAG shuts the engine down */
|
||||||
|
lacp_lag_set(2, 0);
|
||||||
|
CHECK(lacpEnabled == 0 && hw_members_lag[2] == 0
|
||||||
|
&& hw_rma_reg == RTL837X_RMA2_CONF && hw_rma_act == RTL837X_RMA_ACT_DROP,
|
||||||
|
"T13 last LAG removed: engine off, trunk drained, address dropped");
|
||||||
|
|
||||||
|
/* T14: while the engine runs, the slow-protocols address is trapped to
|
||||||
|
* the CPU. The trap keys on the address alone, so delivery needs no L2
|
||||||
|
* entry and nothing written per VLAN or per PVID. */
|
||||||
|
hw_rma_calls = 0;
|
||||||
|
lacp_lag_set(0, 0x0003);
|
||||||
|
CHECK(hw_rma_calls == 1 && hw_rma_reg == RTL837X_RMA2_CONF
|
||||||
|
&& hw_rma_act == RTL837X_RMA_ACT_TRAP,
|
||||||
|
"T14 engine on traps 01:80:c2:00:00:02 to the CPU");
|
||||||
|
|
||||||
|
/* T15: a trapped LACPDU arrives without the VLAN tag a forwarded one
|
||||||
|
* carries, so its body sits four bytes earlier. Both forms must parse. */
|
||||||
|
uint16_t rx_before = lacp_rx_count[0];
|
||||||
|
lacp_partner_state[0] = 0; /* all three are written past the ethertype gate */
|
||||||
|
memset(lacp_partner_sys[0], 0, MAC_ADDR_LEN);
|
||||||
|
partner_frame_trap(0, SYS_A, P_ACT|P_AGG|P_TO|P_SYNC, 1);
|
||||||
|
int trap_ok = lacp_rx_count[0] == (uint16_t)(rx_before + 1)
|
||||||
|
&& lacp_partner_state[0] == (P_ACT|P_AGG|P_TO|P_SYNC)
|
||||||
|
&& !memcmp(lacp_partner_sys[0], SYS_A, MAC_ADDR_LEN);
|
||||||
|
partner_frame(0, SYS_A, P_ACT|P_AGG|P_TO|P_SYNC, 1);
|
||||||
|
CHECK(trap_ok && lacp_rx_count[0] == (uint16_t)(rx_before + 2),
|
||||||
|
"T15 trapped LACPDU parses like a forwarded one");
|
||||||
|
|
||||||
|
/* T16: the partner changes one cable at a time. Port 1 starts hearing B
|
||||||
|
* while port 0 still hears A, then A goes quiet and B appears on port 0
|
||||||
|
* too. The aggregator must let go of A once no port hears it, even though
|
||||||
|
* port 1 was CURRENT the whole time, or the LAG never aggregates with B. */
|
||||||
|
lacp_lag_set(0, 0);
|
||||||
|
lacp_lag_set(0, 0x0003);
|
||||||
|
step(8);
|
||||||
|
for (int r = 0; r < 3; r++) {
|
||||||
|
partner_frame(0, SYS_A, P_ACT|P_AGG|P_TO|P_SYNC, 1);
|
||||||
|
partner_frame(1, SYS_A, P_ACT|P_AGG|P_TO|P_SYNC, 1);
|
||||||
|
step(8);
|
||||||
|
}
|
||||||
|
for (int t = 0; t < 40; t++) { /* port 1 moved to B, port 0 still on A */
|
||||||
|
partner_frame(0, SYS_A, P_ACT|P_AGG|P_TO|P_SYNC, 1);
|
||||||
|
partner_frame(1, SYS_B, P_ACT|P_AGG|P_TO|P_SYNC, 1);
|
||||||
|
step(4 * 40);
|
||||||
|
}
|
||||||
|
int stayed_out = hw_members == 0x0001 && !(lacp_actor_state[1] & P_SYNC);
|
||||||
|
for (int t = 0; t < 60; t++) /* port 0 unplugged from A, past the short timeout */
|
||||||
|
{ partner_frame(1, SYS_B, P_ACT|P_AGG|P_TO|P_SYNC, 1); step(4 * 40); }
|
||||||
|
for (int t = 0; t < 40; t++) { /* port 0 now on B as well */
|
||||||
|
partner_frame(0, SYS_B, P_ACT|P_AGG|P_TO|P_SYNC, 1);
|
||||||
|
partner_frame(1, SYS_B, P_ACT|P_AGG|P_TO|P_SYNC, 1);
|
||||||
|
step(4 * 40);
|
||||||
|
}
|
||||||
|
CHECK(stayed_out && hw_members == 0x0003 && !memcmp(lacp_agg_sys[0], SYS_B, MAC_ADDR_LEN),
|
||||||
|
"T16 re-cabling one port at a time: aggregator moves from A to B");
|
||||||
|
|
||||||
|
/* T17: a group that already has a STATIC trunk is put under LACP with the
|
||||||
|
* same ports (the LAG page's Static -> LACP switch sends only "lag N lacp").
|
||||||
|
* The static trunk must go: until the partner converges the ports are not
|
||||||
|
* an aggregate, and after a reboot the config replays only the LACP line. */
|
||||||
|
hw_members_lag[1] = 0x0030; /* static lag 2: ports 4,5 */
|
||||||
|
lacp_lag_set(1, 0x0030);
|
||||||
|
step(8);
|
||||||
|
CHECK(hw_members_lag[1] == 0 && lacp_members_last[1] == 0 && lacp_lag_ports[1] == 0x0030,
|
||||||
|
"T17 static trunk taken over: cleared while the group negotiates");
|
||||||
|
lacp_lag_set(1, 0);
|
||||||
|
|
||||||
|
/* T18: a port named in a second group moves there. The first group's
|
||||||
|
* candidate set drops it, so taking the second group out later leaves no
|
||||||
|
* orphan that group 1 still shows as a candidate but never runs LACP on. */
|
||||||
|
lacp_lag_set(1, 0x0030); /* ports 4,5 */
|
||||||
|
step(8);
|
||||||
|
for (int r = 0; r < 2; r++) {
|
||||||
|
partner_frame(4, SYS_A, P_ACT|P_AGG|P_TO|P_SYNC, 1);
|
||||||
|
partner_frame(5, SYS_A, P_ACT|P_AGG|P_TO|P_SYNC, 1);
|
||||||
|
step(8);
|
||||||
|
}
|
||||||
|
int converged = hw_members_lag[1] == 0x0030;
|
||||||
|
lacp_lag_set(2, 0x0060); /* ports 5,6: port 5 moves from group 2 to 3 */
|
||||||
|
int moved = lacp_lag_ports[1] == 0x0010 && lacp_port_lag[5] == 2
|
||||||
|
&& hw_members_lag[1] == 0x0010;
|
||||||
|
lacp_lag_set(2, 0);
|
||||||
|
int no_orphan = lacp_port_lag[5] == LACP_LAG_NONE && lacp_lag_ports[1] == 0x0010;
|
||||||
|
lacp_lag_set(3, 0x0010); /* the last port leaves group 2 as well */
|
||||||
|
CHECK(converged && moved && no_orphan && lacp_lag_ports[1] == 0 && lacp_agg_valid[1] == 0,
|
||||||
|
"T18 port moves between groups: old group drops it from candidates and trunk");
|
||||||
|
lacp_lag_set(3, 0);
|
||||||
|
|
||||||
|
/* T20: taking a group out of LACP leaves nothing of the partner behind
|
||||||
|
* for "lacp show" to report as if the port still heard it. */
|
||||||
|
lacp_lag_set(1, 0x0030);
|
||||||
|
step(8);
|
||||||
|
partner_frame(4, SYS_A, P_ACT|P_AGG|P_TO|P_SYNC, 1);
|
||||||
|
step(8);
|
||||||
|
int had_partner = lacp_rx_state[4] == LACP_RX_CURRENT && lacp_rx_count[4] > 0;
|
||||||
|
lacp_lag_set(1, 0);
|
||||||
|
CHECK(had_partner && lacp_partner_state[4] == 0 && lacp_rx_state[4] == LACP_RX_INITIALIZE
|
||||||
|
&& lacp_rx_count[4] == 0 && !memcmp(lacp_partner_sys[4], "\0\0\0\0\0\0", MAC_ADDR_LEN),
|
||||||
|
"T20 release: partner state, rx state, counter and system cleared");
|
||||||
|
|
||||||
|
/* T21: applying the same candidate set again (the LAG page resends it
|
||||||
|
* with every Apply) must not tear the trunk down and renegotiate. */
|
||||||
|
lacp_lag_set(1, 0x0030);
|
||||||
|
step(8);
|
||||||
|
for (int r = 0; r < 2; r++) {
|
||||||
|
partner_frame(4, SYS_A, P_ACT|P_AGG|P_TO|P_SYNC, 1);
|
||||||
|
partner_frame(5, SYS_A, P_ACT|P_AGG|P_TO|P_SYNC, 1);
|
||||||
|
step(8);
|
||||||
|
}
|
||||||
|
int before = hw_set_calls;
|
||||||
|
lacp_lag_set(1, 0x0030);
|
||||||
|
CHECK(hw_members_lag[1] == 0x0030 && hw_set_calls == before
|
||||||
|
&& (lacp_actor_state[4] & P_COL),
|
||||||
|
"T21 same ports applied again: trunk and state untouched");
|
||||||
|
lacp_lag_set(1, 0);
|
||||||
|
|
||||||
|
/* T22: a runt on the slow-protocols address must not be parsed as a
|
||||||
|
* LACPDU; the partner block would be read from stale buffer bytes. */
|
||||||
|
lacp_lag_set(1, 0x0030);
|
||||||
|
step(8);
|
||||||
|
partner_frame_len(4, SYS_A, P_ACT|P_AGG|P_TO|P_SYNC, 1, 40); /* cut before the partner block */
|
||||||
|
CHECK(lacp_rx_count[4] == 0 && lacp_rx_state[4] == LACP_RX_INITIALIZE,
|
||||||
|
"T22 truncated LACPDU ignored: the port does not record it");
|
||||||
|
lacp_lag_set(1, 0);
|
||||||
|
|
||||||
|
/* T23: the partner asks for the long timeout (Linux's default slow rate).
|
||||||
|
* current_while is armed from OUR LACP_Timeout, which is short, so a
|
||||||
|
* partner that stops speaking LACP with the link up is dropped after the
|
||||||
|
* short timeout, not after two minutes. */
|
||||||
|
lacp_lag_set(1, 0x0030);
|
||||||
|
step(8);
|
||||||
|
for (int r = 0; r < 2; r++) {
|
||||||
|
partner_frame(4, SYS_A, P_ACT|P_AGG|P_SYNC, 1);
|
||||||
|
partner_frame(5, SYS_A, P_ACT|P_AGG|P_SYNC, 1);
|
||||||
|
step(8);
|
||||||
|
}
|
||||||
|
int slow_converged = hw_members_lag[1] == 0x0030;
|
||||||
|
step(4 * LACP_SHORT_TIMEOUT + 64);
|
||||||
|
CHECK(slow_converged && hw_members_lag[1] == 0,
|
||||||
|
"T23 partner on slow rate goes quiet: dropped after our short timeout");
|
||||||
|
lacp_lag_set(1, 0);
|
||||||
|
|
||||||
|
/* T24: a LACPDU carrying our own system id (two ports of one group
|
||||||
|
* cabled to each other) must not be taken for a partner. */
|
||||||
|
lacp_lag_set(1, 0x0030);
|
||||||
|
step(8);
|
||||||
|
partner_frame(4, uip_ethaddr.addr, P_ACT|P_AGG|P_TO|P_SYNC, 1);
|
||||||
|
CHECK(lacp_rx_count[4] == 0 && lacp_rx_state[4] == LACP_RX_INITIALIZE,
|
||||||
|
"T24 own system id: LACPDU ignored");
|
||||||
|
lacp_lag_set(1, 0);
|
||||||
|
|
||||||
|
printf("\n%s (%d failure%s)\n", failures ? "SANDBOX: FAILURES" : "SANDBOX: ALL PASS",
|
||||||
|
failures, failures == 1 ? "" : "s");
|
||||||
|
return failures ? 1 : 0;
|
||||||
|
}
|
||||||
Reference in new issue
Block a user