Merge pull request #455 from DrDoof/feat/storm-control

storm: limit broadcast, multicast and unknown unicast and multicast per port
This commit is contained in:
logicog authored and GitHub committed 2026-10-01 08:05:37 +02:00
commit f0aea3dcac
13 files changed
+324 -3

No files matched your search

+2 -1
View File
@@ -82,7 +82,8 @@ SRCS += \
rtl837x_phy.c \
rtl837x_pins.c\
rtl837x_port.c \
rtl837x_stp.c
rtl837x_stp.c \
rtl837x_storm.c
SRCS += \
httpd/httpd.c \
httpd/page_impl.c
+1
View File
@@ -360,4 +360,5 @@ The following documents give further documentation on specific features of the R
- [SFP+ ports](doc/sfp.md)
- [Trunking aka. port aggregation](doc/trunking.md)
- [VLAN](doc/vlan.md)
- [Storm control](doc/storm_control.md)
- [Modifications and Flash replacement](doc/mods.md)
+54
View File
@@ -16,6 +16,7 @@
#include "rtl837x_stp.h"
#include "rtl837x_igmp.h"
#include "rtl837x_bandwidth.h"
#include "rtl837x_storm.h"
#include "sfp.h"
#include "dhcp.h"
#include "syslog.h"
@@ -1573,6 +1574,57 @@ err:
cmd_error("bw [in|out|status] <port> [<hexvalue>|off|drop|fc]\n");
}
static __code char * __code storm_types[STORM_TYPES] = { "bcast", "mcast", "ucast", "umcast" };
void parse_storm(void)
{
uint8_t port, type, d;
__xdata uint32_t rate;
__xdata uint8_t *p;
if (cmd_words_len == 1)
return storm_show();
if (cmd_words_len < 4 || !cmd_parse_port_separator(cmd_words_b[1]))
goto err;
port = atoi_results_u8;
type = 0;
while (!cmd_compare(2, storm_types[type])) {
if (++type == STORM_TYPES)
goto err;
}
if (cmd_words_len == 4 && cmd_compare(3, "off"))
return storm_off(port, type);
if (cmd_words_len != 5)
goto err;
rate = 0;
p = &cmd_buffer[cmd_words_b[3]];
do {
d = *p++ - '0';
if (d > 9 || rate > 99999999)
goto err;
rate = (rate << 3) + (rate << 1) + d;
} while (*p != ' ' && *p != NUL);
if (cmd_compare(4, "pps")) {
if (!rate || rate > 0xfffff)
goto err;
storm_set(port, type, rate, 1);
} else if (cmd_compare(4, "kbps")) {
if (!rate || rate > 10000000)
goto err;
storm_set(port, type, rate, 0);
} else {
goto err;
}
return;
err:
print_string("usage: storm [<port> bcast|mcast|ucast|umcast <rate> pps|kbps|off]\n");
}
void parse_syslog(void)
{
if (cmd_words_len < 2) // no argument -> print status
@@ -1946,6 +1998,8 @@ void cmd_parser(void) __banked
parse_bw();
} else if (cmd_compare(0, "temp")) {
parse_temp();
} else if (cmd_compare(0, "storm")) {
parse_storm();
} else if (cmd_compare(0, "version")) {
print_sw_version();
} else if (cmd_compare(0, "time")) {
+54
View File
@@ -0,0 +1,54 @@
# Storm Control
Storm control limits the rate at which a port admits broadcast, multicast,
unknown unicast and unknown multicast frames. Frames of a type above its limit
are dropped at ingress, so a loop or a misbehaving host cannot flood the rest
of the network through that port. Known unicast traffic is not affected.
## Commands
```
storm <port> bcast|mcast|ucast|umcast <rate> pps
storm <port> bcast|mcast|ucast|umcast <rate> kbps
storm <port> bcast|mcast|ucast|umcast off
storm
```
`ucast` is unicast to a MAC address the switch has not learned yet, and
`umcast` is multicast without an entry in the address table. Multicast groups
get their entries from IGMP snooping, so without `igmp on` all multicast is
unknown and only the `umcast` limit applies to it; `mcast` covers the groups
IGMP has learned. A limit in
packets per second goes from 1 to 1048575, a limit in kbit/s from 1 to
10000000. `storm` without arguments prints the limits of every port. The
limits are kept in the configuration like any other setting, and the Bandwidth
page of the web interface has a Storm control card to set them.
## Registers
Each port and type has an enable bit and a meter index, and each limit uses
one of the 64 shared meters of the switch. RTLPlayground gives every port and
type its own meter, index `24 + port * 4 + type`, so one port's storm does not
eat into the limit of another. Storm control uses meters 24 to 63 and leaves
0 to 23 to the ACL. The limit of a meter is in `RTL837X_METER_RATE + 4 *
meter`, and bit `meter % 32` of `RTL837X_METER_MODE + 4 * (meter / 32)`
selects pps instead of kbit/s.
```
#define RTL837X_STORM_CTRL 0x54e4 /* + 4 * type, bit = port */
#define RTL837X_STORM_MIDX 0x54f4 /* + 8 * type + 4 * (port / 5), 6 bits per port */
#define RTL837X_METER_RATE 0x5cf0 /* + 4 * meter, 24 bits, kbit/s or pps */
#define RTL837X_METER_MODE 0x5ef0 /* + 4 * (meter / 32), bit set = pps */
```
In kbit/s mode one step of the rate is 1 kbit/s. Measured with 100 and 1000 byte
frames, the meter counts a frame with its FCS, but without preamble and
interframe gap. Every meter has a bit that adds them, in `0x5f08 + 4 * (meter
/ 32)`, which storm control leaves cleared. `IGBW_INC_IFG` and `EGBW_INC_IFG`
are the matching bits of the bandwidth control.
An ingress bandwidth limit on the same port works on the same arriving frames
as the storm limit rather than after it, and the two together pass far less
than either one: a 1 Mbit/s `umcast` limit with a 10 Mbit/s ingress limit let
0.12 to 0.16 Mbit/s of a 1.4 Gbit/s stream through, where each alone passed
1.17 and 10.2 Mbit/s.
The type is 0 for broadcast, 1 for multicast, 2 for unknown unicast and 3 for
unknown multicast. The burst size of a meter (0x5df0 + 4 * meter) counts packets
in pps mode; its default of 0x2000 lets 8192 frames through before a pps limit
bites, so a pps limit sets the burst to one second of traffic. A kbit/s limit
puts back the default.
+60 -2
View File
@@ -70,6 +70,9 @@ e_idle:"idle",e_na:"n/a",
bw_title:"Bandwidth limits",bw_h:"Mbit/s, 0.016-10000",bw_in:"Ingress limit",bw_out:"Egress limit",
bw_exceed:"When exceeded",bw_fc:"Flow control",bw_drop:"Drop",
bw_in_err:"Ingress limit must be 0.016-10000 Mbit/s",bw_out_err:"Egress limit must be 0.016-10000 Mbit/s",
sc_title:"Storm control",sc_h:"per port and traffic type, empty = off",sc_bcast:"Broadcast",sc_mcast:"Multicast",
sc_ucast:"Unknown unicast",sc_umcast:"Unknown multicast",sc_err:"Storm limit must be 1-1048575 pps or 1-10000000 kbit/s",
sc_note:"Frames of a type above its limit are dropped where they enter the switch.",
sy_network:"Network",sy_dhcp:"Use DHCP",sy_dhcp_t:"Request address via DHCP",sy_services:"Services",
sy_igmp:"IGMP snooping",sy_sysip:"server IP",sy_server:"Server",sy_port:"Port",
sy_sesstmo:"Session timeout (s)",sy_sesstmo_err:"Session timeout must be 1-65535 s",
@@ -677,6 +680,7 @@ var CONF_CMDS=[
/^stp\s+(port\s+\d{1,2}|lag\s+[1-4])\s+p2p\s+(auto|on|off)$/,
/^igmp\s+(on|off)$/,/^mtu\s+\d{1,2}\s+\d+$/,
/^bw\s+(in|out)\s+\d{1,2}\s+\S+$/,
/^storm\s+\d{1,2}\s+(bcast|mcast|ucast|umcast)\s+(off|\d{1,8}\s+(pps|kbps))$/,
];
function isConfCmd(line){
for(var i=0;i<CONF_CMDS.length;i++)if(CONF_CMDS[i].test(line))return true;
@@ -1725,7 +1729,61 @@ function bwApply(n){
}else cmds.push("bw out "+n+" off");
postCmds(cmds).then(bwLoad).catch(function(){});
}
tabHooks.bw={enter:function(){needPorts(function(){bwLoad().catch(function(){})})}};
var SC_TYPES=["bcast","mcast","ucast","umcast"],scSig="";
function scLoad(){
return getJSON("/storm.json").then(function(s){
var tb=$("sctable").tBodies[0];
byPort(s);
var sig=s.map(function(p){return p.portNum}).join();
if(sig!==scSig){
scSig=sig;tb.innerHTML="";
s.forEach(function(p){
var n=p.portNum,tr=tb.insertRow();
tr.id="scr"+n;
tr.addEventListener("input",function(){tr.dataset.dirty="1"});
tr.addEventListener("change",function(){tr.dataset.dirty="1"});
tr.insertCell().textContent=n;
SC_TYPES.forEach(function(ty){
tr.insertCell().appendChild(h("span",{style:"display:flex;gap:4px"},[
h("input",{class:"in sm",id:"scv"+ty+n,type:"number",min:"1",step:"1"}),
h("select",{class:"in",id:"scu"+ty+n},[
h("option",{value:"pps",text:"pps"}),
h("option",{value:"kbps",text:"kbit/s"}),
]),
]));
});
tr.insertCell().appendChild(h("button",{class:"ctl",text:t("c_apply"),onclick:function(){scApply(n)}}));
});
}
s.forEach(function(p){
var n=p.portNum;
if($("scr"+n).dataset.dirty)return;
SC_TYPES.forEach(function(ty,k){
var on=p.en.charAt(k)==="1",pps=p.pps.charAt(k)==="1",raw=parseInt(p.rate.substr(k*6,6),16);
var inp=$("scv"+ty+n),sel=$("scu"+ty+n);
inp.value=on?raw:"";
sel.value=on&&!pps?"kbps":"pps";
inp.dataset.cur=on?inp.value+" "+sel.value:"off";
});
});
});
}
function scApply(n){
var cmds=[];
for(var k=0;k<SC_TYPES.length;k++){
var ty=SC_TYPES[k],inp=$("scv"+ty+n),u=$("scu"+ty+n).value,v=inp.value.trim(),want="off";
if(v!==""&&v!=="0"){
var x=Number(v);
if(!Number.isInteger(x)||x<1||(u==="pps"&&x>1048575)||(u==="kbps"&&x>10000000)){toast(t("sc_err"),"err");return;}
want=x+" "+u;
}
if(want!==inp.dataset.cur)cmds.push("storm "+n+" "+ty+" "+want);
}
delete $("scr"+n).dataset.dirty;
if(!cmds.length)return;
postCmds(cmds).then(scLoad).catch(function(){});
}
tabHooks.bw={enter:function(){needPorts(function(){bwLoad().then(scLoad).catch(function(){})})}};
var IPRE=/^(\d{1,3}\.){3}\d{1,3}$/;
function okIp(s){
@@ -1870,7 +1928,7 @@ var CONF_OVERWRITE=[
/^lag\s+\d\b/,/^laghash\s+\d\b/,/^isolate\s+\d{1,2}\b/,
/^stp\s+(prio|hello|maxage|fwd|txhold|version)\b/,
/^stp\s+(port\s+\d{1,2}|lag\s+[1-4])\s+(edge|cost|prio|guard|filter|p2p)\b/,
/^igmp\b/,/^mtu\s+\d{1,2}\b/,
/^igmp\b/,/^mtu\s+\d{1,2}\b/,/^storm\s+\d{1,2}\s+(bcast|mcast|ucast|umcast)\b/,
];
var CONF_TOGGLE=[/^(syslog)\s+(on|off)$/,/^(stp)\s+(on|off)$/,/^(stp\s+(port\s+\d{1,2}|lag\s+[1-4]))\s+(on|off)$/];
function mergeConf(base,texts){
+6
View File
@@ -351,6 +351,12 @@ button.danger{background:var(--bad);color:#fff;border-color:transparent}
<thead><tr><th data-i18n="c_port">Port</th><th data-i18n="bw_in">Ingress limit</th><th>Mbit/s</th><th data-i18n="bw_exceed">When exceeded</th><th data-i18n="bw_out">Egress limit</th><th>Mbit/s</th><th></th></tr></thead>
<tbody></tbody></table></div>
</div>
<div class="card"><h2><span data-i18n="sc_title">Storm control</span> <span class="hint" data-i18n="sc_h">per port and traffic type, empty = off</span></h2>
<div class="scrollx"><table class="t" id="sctable">
<thead><tr><th data-i18n="c_port">Port</th><th data-i18n="sc_bcast">Broadcast</th><th data-i18n="sc_mcast">Multicast</th><th data-i18n="sc_ucast">Unknown unicast</th><th data-i18n="sc_umcast">Unknown multicast</th><th></th></tr></thead>
<tbody></tbody></table></div>
<p class="small mut" style="margin-top:8px" data-i18n="sc_note">Frames of a type above its limit are dropped where they enter the switch.</p>
</div>
</section>
<section class="tab" id="tab-system">
+2
View File
@@ -1016,6 +1016,8 @@ void httpd_appcall(void)
send_eee();
} else if (is_word(q, "/bandwidth.json")) {
send_bandwidth();
} else if (is_word(q, "/storm.json")) {
send_storm();
} else if (is_word(q, "/l2.json")) {
parse_short(q + 13); // e.g.: /l2.json?idx=10
send_l2(short_parsed);
+35
View File
@@ -3,6 +3,7 @@
#include "rtl837x_sfr.h"
#include "rtl837x_common.h"
#include "rtl837x_regs.h"
#include "rtl837x_storm.h"
#include "rtl837x_port.h"
#include "rtl837x_flash.h"
#include "rtl837x_pins.h"
@@ -828,6 +829,40 @@ void send_bandwidth(void)
}
void send_storm(void)
{
__xdata uint8_t i, t, idx;
slen = strtox(outbuf, HTTP_RESPONCE_JSON);
char_to_html('[');
for (i = machine.min_port; i <= machine.max_port; i++) {
slen += strtox(outbuf + slen, "{\"portNum\":");
itoa_html(machine.log_to_phys_port[i]);
slen += strtox(outbuf + slen, ",\"en\":\"");
for (t = 0; t < STORM_TYPES; t++)
char_to_html(reg_bit_test(RTL837X_STORM_CTRL + (t << 2), i) ? '1' : '0');
slen += strtox(outbuf + slen, "\",\"pps\":\"");
for (t = 0; t < STORM_TYPES; t++) {
idx = STORM_METER(i, t);
char_to_html(reg_bit_test(RTL837X_METER_MODE + ((idx >> 5) << 2), idx & 0x1f) ? '1' : '0');
}
slen += strtox(outbuf + slen, "\",\"rate\":\"");
for (t = 0; t < STORM_TYPES; t++) {
reg_read_m(RTL837X_METER_RATE + (STORM_METER(i, t) << 2));
byte_to_html(sfr_data[1]);
byte_to_html(sfr_data[2]);
byte_to_html(sfr_data[3]);
}
char_to_html('"');
char_to_html('}');
if (i < machine.max_port)
char_to_html(',');
else
char_to_html(']');
}
}
void send_mtu(void)
{
dbg_string("send_mtu called\n");
+1
View File
@@ -12,6 +12,7 @@ void send_status(void);
void send_vlan(uint16_t vlan);
void send_basic_info(void);
void send_bandwidth(void);
void send_storm(void);
void send_eee(void);
void send_l2(uint16_t idx);
void l2_delete(uint16_t idx);
+5
View File
@@ -327,6 +327,11 @@
#define RTL837X_IGBW_PORT_FC_CTRL 0x4C8C
#define RTL837X_EGBW_PORT_CTRL 0x1c34
#define RTL837X_EGBW_CTRL 0x447c
#define RTL837X_STORM_CTRL 0x54e4
#define RTL837X_STORM_MIDX 0x54f4
#define RTL837X_METER_RATE 0x5cf0
#define RTL837X_METER_BURST 0x5df0
#define RTL837X_METER_MODE 0x5ef0
#define EGBW_INC_IFG 0x02
#define EGBW_CPUMODE 0x01
+89
View File
@@ -0,0 +1,89 @@
#include <stdint.h>
#include "rtl837x_common.h"
#include "rtl837x_sfr.h"
#include "rtl837x_regs.h"
#include "rtl837x_storm.h"
#include "machine.h"
#pragma codeseg BANK3
#pragma constseg BANK3
extern __code struct machine machine;
extern __xdata uint8_t sfr_data[4];
/* RTL837X_STORM_MIDX words, row type * 2 + half: half 0 holds ports 0-4, half 1 ports 5-9,
* 6 bits per port, each set to STORM_METER(port, type) */
static const __code uint8_t storm_midx[8][4] = {
{ 0x28, 0x92, 0x07, 0x18 }, { 0x3c, 0xe3, 0x4c, 0x2c },
{ 0x29, 0x96, 0x17, 0x59 }, { 0x3d, 0xe7, 0x5c, 0x6d },
{ 0x2a, 0x9a, 0x27, 0x9a }, { 0x3e, 0xeb, 0x6c, 0xae },
{ 0x2b, 0x9e, 0x37, 0xdb }, { 0x3f, 0xef, 0x7c, 0xef },
};
static __code char * __code storm_names[STORM_TYPES] = { " bcast ", " mcast ", " ucast ", " umcast " };
void storm_set(uint8_t port, __xdata uint8_t type, __xdata uint32_t rate, __xdata uint8_t pps) __banked
{
__xdata uint8_t idx = STORM_METER(port, type);
__xdata uint8_t row = (type << 1) | (port >= 5 ? 1 : 0);
__xdata uint8_t * __xdata r = (uint8_t *)&rate;
sfr_data[0] = 0;
sfr_data[1] = r[2];
sfr_data[2] = r[1];
sfr_data[3] = r[0];
reg_write_m(RTL837X_METER_RATE + (idx << 2));
if (!pps) {
sfr_data[1] = 0;
sfr_data[2] = 0x20;
sfr_data[3] = 0;
}
reg_write_m(RTL837X_METER_BURST + (idx << 2));
if (pps)
reg_bit_set(RTL837X_METER_MODE + ((idx >> 5) << 2), idx & 0x1f);
else
reg_bit_clear(RTL837X_METER_MODE + ((idx >> 5) << 2), idx & 0x1f);
sfr_data[0] = storm_midx[row][0];
sfr_data[1] = storm_midx[row][1];
sfr_data[2] = storm_midx[row][2];
sfr_data[3] = storm_midx[row][3];
reg_write_m(RTL837X_STORM_MIDX + (row << 2));
reg_bit_set(RTL837X_STORM_CTRL + (type << 2), port);
}
void storm_off(uint8_t port, __xdata uint8_t type) __banked
{
reg_bit_clear(RTL837X_STORM_CTRL + (type << 2), port);
}
void storm_show(void) __banked
{
uint8_t i, t, idx;
for (i = machine.min_port; i <= machine.max_port; i++) {
print_string("port ");
write_char('0' + machine.log_to_phys_port[i]);
for (t = 0; t < STORM_TYPES; t++) {
print_string(storm_names[t]);
if (!reg_bit_test(RTL837X_STORM_CTRL + (t << 2), i)) {
print_string("off");
continue;
}
idx = STORM_METER(i, t);
reg_read_m(RTL837X_METER_RATE + (idx << 2));
print_byte(sfr_data[1]); print_byte(sfr_data[2]); print_byte(sfr_data[3]);
if (reg_bit_test(RTL837X_METER_MODE + ((idx >> 5) << 2), idx & 0x1f))
print_string(" pps");
else
print_string(" kbps");
}
write_char('\n');
}
}
+14
View File
@@ -0,0 +1,14 @@
#ifndef _RTL837X_STORM_H_
#define _RTL837X_STORM_H_
#include <stdint.h>
#define STORM_TYPES 4
#define STORM_METER_BASE 24
#define STORM_METER(port, type) (STORM_METER_BASE + (((port) << 2) | (type)))
void storm_set(uint8_t port, __xdata uint8_t type, __xdata uint32_t rate, __xdata uint8_t pps) __banked;
void storm_off(uint8_t port, __xdata uint8_t type) __banked;
void storm_show(void) __banked;
#endif
+1
View File
@@ -139,6 +139,7 @@ void send_status(void) { }
void send_vlan(uint16_t vlan) { (void)vlan; }
void send_basic_info(void) { }
void send_bandwidth(void) { }
void send_storm(void) { }
void send_eee(void) { }
void send_l2(uint16_t idx) { (void)idx; }
void l2_delete(uint16_t idx) { (void)idx; }