storm: limit broadcast, multicast and unknown unicast and multicast per port

This commit is contained in:
DrDoof committed 2026-09-29 21:46:56 +02:00
1 parent 13b75a73c9
commit 26547f3e01
13 files changed
+312 -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)
+59
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,62 @@ 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) __reentrant
{
static __xdata uint8_t port, type, d;
static __xdata uint32_t rate;
static __xdata uint8_t * __xdata p;
if (cmd_words_len == 1) {
storm_show();
return;
}
if (cmd_words_len < 4 || cmd_parse_port_separator(cmd_words_b[1]) == 0)
goto err;
port = atoi_results_u8;
for (type = 0; type < STORM_TYPES; type++) {
if (cmd_compare(2, storm_types[type]))
break;
}
if (type == STORM_TYPES)
goto err;
if (cmd_words_len == 4 && cmd_compare(3, "off")) {
storm_off(port, type);
return;
}
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 +2003,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")) {
+40
View File
@@ -0,0 +1,40 @@
# 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. 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 `port * 4 + type`, so one port's storm does not eat
into the limit of another.
```
#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.
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 = (i << 2) | 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 + ((((i << 2) | 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
+88
View File
@@ -0,0 +1,88 @@
#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];
/* Meter index fields of RTL837X_STORM_MIDX, [type << 1 | ports 5-9] */
static const __code uint8_t storm_midx[8][4] = {
{ 0x10, 0x30, 0x81, 0x00 }, { 0x24, 0x81, 0xc6, 0x14 },
{ 0x11, 0x34, 0x91, 0x41 }, { 0x25, 0x85, 0xd6, 0x55 },
{ 0x12, 0x38, 0xa1, 0x82 }, { 0x26, 0x89, 0xe6, 0x96 },
{ 0x13, 0x3c, 0xb1, 0xc3 }, { 0x27, 0x8d, 0xf6, 0xd7 },
};
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 = (port << 2) | 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
{
__xdata 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 = (i << 2) | 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');
}
}
+12
View File
@@ -0,0 +1,12 @@
#ifndef _RTL837X_STORM_H_
#define _RTL837X_STORM_H_
#include <stdint.h>
#define STORM_TYPES 4
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; }