Merge pull request #83 from logicog/l2

Add support for L2 entry display and deletion in the web interface
This commit is contained in:
René van Dorst
2026-01-17 16:00:37 +00:00
committed by GitHub
9 changed files with 417 additions and 19 deletions
+19
View File
@@ -0,0 +1,19 @@
<!DOCTYPE html>
<html>
<script src="/main.js"></script>
<link rel="stylesheet" href="style.css">
<title>FreeSwitchOS L2 Configuration</title>
</head>
<body>
<nav id="sidebar"></nav>
<div style="margin-left:16%;padding:1px 16px;height:1000px;">
<div id="ports"></div>
<h1>L2 Configuration</h1>
<table id="l2table">
<tr> <th>Port</th> <th>MAC</th> <th>VLAN</th> <th>Type</th> <th>Remove Entry</th></tr>
<script src="/l2.js"></script>
</table>
</div>
</body>
<script src="/navigation.js"></script>
</html>
+139
View File
@@ -0,0 +1,139 @@
var l2GetInterval;
var l2Entries = [];
var l2CurrentEntry = 0;
function fillStats() {
var tbl = document.getElementById('statstable');
if (!numPorts)
return;
if (tbl.rows.length > 1) {
for (let i = 0; i < numPorts; i++) {
console.log("Table Update row: " + i + " state " + pState[i] + " is " + linkS[pState[i] +1]);
tbl.rows[i+1].cells[1].innerHTML = `${linkS[pState[i]+1]}`;
tbl.rows[i+1].cells[2].innerHTML = `${txG[i]} pkts`;
tbl.rows[i+1].cells[3].innerHTML = `${txB[i]} pkts`;
tbl.rows[i+1].cells[4].innerHTML = `${rxG[i]} pkts`;
tbl.rows[i+1].cells[5].innerHTML = `${rxB[i]} pkts`;
}
} else {
for (let i = 0; i < numPorts; i++) {
console.log("Table row: " + i);
const tr = tbl.insertRow();
let td = tr.insertCell(); td.appendChild(document.createTextNode(`Port ${i+1}`));
td = tr.insertCell(); td.appendChild(document.createTextNode(`${linkS[pState[i]+1]}`));
td = tr.insertCell(); td.appendChild(document.createTextNode(`${txG[i]} pkts`));
td = tr.insertCell();td.appendChild(document.createTextNode(`${txB[i]} pkts`));
td = tr.insertCell();td.appendChild(document.createTextNode(`${rxG[i]} pkts`));
td = tr.insertCell();td.appendChild(document.createTextNode(`${rxB[i]} pkts`));
}
}
}
function l2CMP(a, b)
{
if (a.port < b.port)
return -1;
if (a.port > b.port)
return 1;
if (a.mac < b.mac)
return -1;
if (a.mac > b.mac)
return 1;
if (a.vlan < b.vlan)
return -1;
if (a.vlan > b.vlan)
return 1;
return 0;
}
function uniq(a) {
return a.filter(function(item, pos, ary) {
return !pos || item.idx != ary[pos - 1].idx;
});
}
function delL2(idx) {
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
var s = JSON.parse(xhttp.responseText);
console.log("Entry deletion result: ", s.result);
}
};
xhttp.open("GET", "/l2_del.json?idx=" + idx, true);
xhttp.timeout = 1500; xhttp.send();
}
function fillL2(s)
{
var tbl = document.getElementById('l2table');
if (!s.length)
return;
s.sort(l2CMP);
s = uniq(s);
var s = s.map(function(e) { e.port = e.port != 9 ? e.port : "CPU"; return e; });
console.log("L2: ", JSON.stringify(s));
for (let i = 0; i < s.length; i++) {
var e = s[i];
console.log(i, e);
if (tbl.rows[i+1]) {
tbl.rows[i+1].cells[0].innerHTML = `${e.port}`;
tbl.rows[i+1].cells[1].innerHTML = `${e.mac}`;
tbl.rows[i+1].cells[2].innerHTML = `${e.vlan}`;
tbl.rows[i+1].cells[4].innerHTML = '<button type="button" onclick="delL2(' + e.idx + ');">Delete</button>';
} else {
const tr = tbl.insertRow();
let td = tr.insertCell(); td.innerHTML = `${e.port}`;
td = tr.insertCell(); td.innerHTML = `${e.mac}`;
td = tr.insertCell(); td.innerHTML = `${e.vlan}`;
td = tr.insertCell(); td.innerHTML = `${e.type}`;
td = tr.insertCell(); td.innerHTML = '<button type="button" onclick="delL2(' + e.idx + ');">Delete</button>';
}
}
for (let i = tbl.rows.length - 1; i > s.length; i--)
tbl.deleteRow(i);
l2Entries = [];
}
function getL2() {
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
var s = JSON.parse(xhttp.responseText);
var s = s.map(function(e) {
e.vlan = parseInt(e.vlan, 16);
e.idx = parseInt(e.idx, 16);
e.type = e.type == "s" ? "static" : "learned";
e.port = e.port == 9 ? 9 : logToPhysPort[e.port];
return e;
});
l2Entries.push(...s);
if (l2Entries >= 4096) {
l2Entries = [];
l2CurrentEntry = 0;
clearInterval(l2GetInterval);
return;
}
var w = 0;
for (var i = l2Entries.length-1; i > 0; i--) {
if (l2Entries[0].idx == l2Entries[i].idx) {
w = 1;
break;
}
}
if (w) {
l2CurrentEntry = 0;
fillL2(l2Entries);
} else {
l2CurrentEntry = s[s.length-1].idx + 1;
}
}
};
xhttp.open("GET", "/l2.json?idx=" + l2CurrentEntry, true);
xhttp.timeout = 1500; xhttp.send();
}
window.addEventListener("load", function() {
l2GetInterval = setInterval(getL2, 1000);
});
+1
View File
@@ -3,6 +3,7 @@ document.getElementById('sidebar').innerHTML =
+ "<li><a href='ports.html'>Port Configuration</a></li>" + "<li><a href='ports.html'>Port Configuration</a></li>"
+ "<li><a href='stat.html'>Port Statistics</a></li>" + "<li><a href='stat.html'>Port Statistics</a></li>"
+ "<li><a href='vlan.html'>VLAN</a></li>" + "<li><a href='vlan.html'>VLAN</a></li>"
+ "<li><a href='l2.html'>L2 Configuration</a></li>"
+ "<li><a href='mirror.html'>Mirroring</a></li>" + "<li><a href='mirror.html'>Mirroring</a></li>"
+ "<li><a href='lag.html'>Link Aggregation</a></li>" + "<li><a href='lag.html'>Link Aggregation</a></li>"
+ "<li><a href='eee.html'>EEE</a></li>" + "<li><a href='eee.html'>EEE</a></li>"
+6
View File
@@ -573,6 +573,12 @@ void httpd_appcall(void)
send_counters(q[20]-'0'); send_counters(q[20]-'0');
} else if (is_word(q, "/eee.json")) { } else if (is_word(q, "/eee.json")) {
send_eee(); send_eee();
} else if (is_word(q, "/l2.json")) {
parse_short(q + 13); // e.g.: /l2.json?idx=10
send_l2(short_parsed);
} else if (is_word(q, "/l2_del.json")) {
parse_short(q + 17);
l2_delete(short_parsed);
} else if (is_word(q, "/mirror.json")) { } else if (is_word(q, "/mirror.json")) {
send_mirror(); send_mirror();
} else if (is_word(q, "/mtu.json")) { } else if (is_word(q, "/mtu.json")) {
+145
View File
@@ -16,6 +16,8 @@
// #define DEBUG // #define DEBUG
#include "debug.h" #include "debug.h"
#define L2_MAX_TRANSFER 30
#pragma codeseg BANK1 #pragma codeseg BANK1
#pragma constseg BANK1 #pragma constseg BANK1
@@ -283,6 +285,149 @@ void send_counters(char port)
} }
void send_l2(uint16_t idx)
{
slen = strtox(outbuf, HTTP_RESPONCE_JSON);
dbg_string("sending L2\n");
dbg_short(idx);
__xdata uint8_t entries_left = L2_MAX_TRANSFER;
do {
reg_read_m(RTL837X_TBL_CTRL);
} while (sfr_data[3] & TBL_EXECUTE);
/* The L2 table in the ASIC can hold up to 4096 (0x1000) entries, which
* are accessed using an index. The index is the hash of the MAC address
* and forwarding ID (basically VID). The hash-table is 4-way associative,
* i.e. for a given hash value, 4 entries with that same hash can be stored
* (i.e. the hash points to a bucket with up to 4 entries).
* When the table or a hash bucket is full, further entries will lead to
* L2 flooding.
* To find all entries, we start with entry-index 0 and iteratively search for
* the next entry (with the next higher index), until we arrive again at the first
* entry. The indices are sorted, so if an entry has a smaller index than
* the previous one, we know that we have wrapped around the entire table.
*/
__xdata uint16_t entry = idx & 0xfff;
__xdata uint16_t first_entry = 0xffff; // An illegal entry index
char_to_html('[');
while (1) {
entries_left--;
uint8_t port = 0;
reg_read_m(RTL837x_TBL_DATA_0);
REG_WRITE(RTL837x_TBL_DATA_0, sfr_data[0], sfr_data[1] & 0xfc, sfr_data[2] | (TBL_LUTREAD_NEXT_L2UC << 6), sfr_data[3]);
REG_WRITE(RTL837X_TBL_CTRL, entry >> 8, entry, TBL_L2_UNICAST, TBL_EXECUTE);
do {
reg_read_m(RTL837X_TBL_CTRL);
} while (sfr_data[3] & TBL_EXECUTE);
reg_read_m(RTL837x_L2_DATA_OUT_B);
if ((sfr_data[0] & 0x20)) { // Check entry is valid
// MAC
slen += strtox(outbuf + slen, "{\"mac\":\"");
byte_to_html(sfr_data[2]); char_to_html(':');
byte_to_html(sfr_data[3]); char_to_html(':');
port = (sfr_data[0] >> 6) & 0x3;
reg_read_m(RTL837x_L2_DATA_OUT_A);
byte_to_html(sfr_data[0]); char_to_html(':');
byte_to_html(sfr_data[1]); char_to_html(':');
byte_to_html(sfr_data[2]); char_to_html(':');
byte_to_html(sfr_data[3]);
// VLAN
slen += strtox(outbuf + slen, "\",\"vlan\":\"");
reg_read_m(RTL837x_L2_DATA_OUT_B);
charhex_to_html(sfr_data[0] & 0x0f);
byte_to_html(sfr_data[1]);
// type
reg_read_m(RTL837x_L2_DATA_OUT_C);
if (sfr_data[2] & 0x1)
slen += strtox(outbuf + slen, "\",\"type\":\"s\",\"port\":");
else
slen += strtox(outbuf + slen, "\",\"type\":\"l\",\"port\":");
port |= (sfr_data[3] & 0x3) << 2;
itoa_html(port);
// Index
reg_read_m(RTL837x_TBL_DATA_0);
entry = (((uint16_t)sfr_data[2] & 0x0f) << 8) | sfr_data[3];
slen += strtox(outbuf + slen, ",\"idx\":\"");
byte_to_html(entry >> 8);
byte_to_html(entry);
char_to_html('"');
char_to_html('}');
entry += 1; // We want the next entry following after the current entry
} else {
reg_read_m(RTL837x_TBL_DATA_0);
entry = (((uint16_t)sfr_data[2] & 0x0f) << 8) | sfr_data[3] + 1;
}
if (first_entry == 0xffff) {
char_to_html(',');
first_entry = entry;
} else {
if (first_entry == entry || !entries_left) {
char_to_html(']');
break;
} else {
char_to_html(',');
}
}
}
}
void l2_delete(uint16_t idx)
{
slen = strtox(outbuf, HTTP_RESPONCE_JSON);
dbg_string("L2 DELETE\n");
dbg_short(idx);
__xdata uint8_t entries_left = L2_MAX_TRANSFER;
do {
reg_read_m(RTL837X_TBL_CTRL);
} while (sfr_data[3] & TBL_EXECUTE);
slen += strtox(outbuf + slen, "{\"result\":");
// First, search for the entry based on the index
reg_read_m(RTL837x_TBL_DATA_0);
REG_WRITE(RTL837x_TBL_DATA_0, sfr_data[0], sfr_data[1] & 0xfc, sfr_data[2] | (TBL_LUTREAD_NEXT_L2UC << 6), sfr_data[3]);
REG_WRITE(RTL837X_TBL_CTRL, (idx >> 8) & 0xf, idx, TBL_L2_UNICAST, TBL_EXECUTE);
do {
reg_read_m(RTL837X_TBL_CTRL);
} while (sfr_data[3] & 0x1);
reg_read_m(RTL837x_L2_DATA_OUT_B);
if (!(sfr_data[0] & 0x20)) {
char_to_html('0');
} else {
sfr_data[0] &= 0x3f; // Clear SPA
reg_write_m(RTL837x_TBL_DATA_IN_B);
// Second half of MAC is copied
reg_read_m(RTL837x_L2_DATA_OUT_A);
reg_write_m(RTL837x_TBL_DATA_IN_A);
reg_read_m(RTL837x_L2_DATA_OUT_C);
sfr_data[3] &= 0xc0; // Clear age, auth and second part of ports
sfr_data[1] &= 0xfe; // Clear nosalearn
reg_write_m(RTL837x_TBL_DATA_IN_C);
reg_read_m(RTL837x_TBL_DATA_0);
REG_WRITE(RTL837x_TBL_DATA_0, sfr_data[0], sfr_data[1], TBL_L2_UNICAST, sfr_data[3]);
REG_WRITE(RTL837X_TBL_CTRL, idx >> 8, idx, TBL_L2_UNICAST, TBL_WRITE | TBL_EXECUTE);
do {
reg_read_m(RTL837X_TBL_CTRL);
} while (sfr_data[3] & TBL_EXECUTE);
char_to_html('1');
}
char_to_html('}');
}
void send_mirror(void) void send_mirror(void)
{ {
dbg_string("send_mirror called\n"); dbg_string("send_mirror called\n");
+2
View File
@@ -6,6 +6,8 @@ void send_status(void);
void send_vlan(uint16_t vlan); void send_vlan(uint16_t vlan);
void send_basic_info(void); void send_basic_info(void);
void send_eee(void); void send_eee(void);
void send_l2(uint16_t idx);
void l2_delete(uint16_t idx);
void send_mirror(void); void send_mirror(void);
void send_mtu(void); void send_mtu(void);
void send_config(void); void send_config(void);
+17 -19
View File
@@ -263,14 +263,23 @@ void port_l2_learned(void) __banked
__xdata uint16_t first_entry = 0xffff; // Table does not have that many entries __xdata uint16_t first_entry = 0xffff; // Table does not have that many entries
while (1) { while (1) {
uint8_t port = 0, other = 0; uint8_t port = 0;
reg_read_m(RTL837x_TBL_DATA_0); reg_read_m(RTL837x_TBL_DATA_0);
REG_WRITE(RTL837x_TBL_DATA_0, sfr_data[0], sfr_data[1],sfr_data[2] | 0xc0, sfr_data[3]); REG_WRITE(RTL837x_TBL_DATA_0, sfr_data[0], sfr_data[1],sfr_data[2] | 0xc0, sfr_data[3]);
REG_WRITE(RTL837X_TBL_CTRL, entry >> 8, entry, TBL_L2_UNICAST, 0x1); REG_WRITE(RTL837X_TBL_CTRL, (entry >> 8) & 0xf, entry, TBL_L2_UNICAST, TBL_EXECUTE);
do { do {
reg_read_m(RTL837X_TBL_CTRL); reg_read_m(RTL837X_TBL_CTRL);
} while (sfr_data[3] & 0x1); } while (sfr_data[3] & TBL_EXECUTE);
reg_read_m(RTL837x_TBL_DATA_0);
entry = (((uint16_t)sfr_data[2] & 0x0f) << 8) | sfr_data[3];
if (first_entry == 0xffff) {
first_entry = entry;
} else {
if (first_entry == entry)
break;
}
// MAC // MAC
reg_read_m(RTL837x_L2_DATA_OUT_B); reg_read_m(RTL837x_L2_DATA_OUT_B);
@@ -278,7 +287,6 @@ void port_l2_learned(void) __banked
print_byte(sfr_data[2]); write_char(':'); print_byte(sfr_data[2]); write_char(':');
print_byte(sfr_data[3]); write_char(':'); print_byte(sfr_data[3]); write_char(':');
port = (sfr_data[0] >> 6) & 0x3; port = (sfr_data[0] >> 6) & 0x3;
other = sfr_data[0];
reg_read_m(RTL837x_L2_DATA_OUT_A); reg_read_m(RTL837x_L2_DATA_OUT_A);
print_byte(sfr_data[0]); write_char(':'); print_byte(sfr_data[0]); write_char(':');
print_byte(sfr_data[1]); write_char(':'); print_byte(sfr_data[1]); write_char(':');
@@ -287,7 +295,7 @@ void port_l2_learned(void) __banked
// VLAN // VLAN
reg_read_m(RTL837x_L2_DATA_OUT_B); reg_read_m(RTL837x_L2_DATA_OUT_B);
print_short( ((uint16_t) (sfr_data[0] & 0x0f)) | sfr_data[1]); // VLAN print_short( (((uint16_t) (sfr_data[0] & 0x0f)) << 8) | sfr_data[1]); // VLAN
// type // type
reg_read_m(RTL837x_L2_DATA_OUT_C); reg_read_m(RTL837x_L2_DATA_OUT_C);
@@ -298,22 +306,12 @@ void port_l2_learned(void) __banked
port |= (sfr_data[3] & 0x3) << 2; port |= (sfr_data[3] & 0x3) << 2;
if (port < 9) if (port < 9)
write_char('1' + port); write_char(machine.log_to_phys_port[port] + '0');
else else
print_string("10"); print_string("CPU");
} }
reg_read_m(RTL837x_TBL_DATA_0);
entry = (((uint16_t)sfr_data[2] & 0x0f) << 8) | sfr_data[3] + 1; entry++;
if (first_entry == 0xffff) {
first_entry = entry;
} else {
if (first_entry == entry)
break;
}
#ifdef DEBUG
write_char(' '); print_sfr_data();
write_char(' '); print_byte(other);
#endif
print_string("\n"); print_string("\n");
} }
} }
+9
View File
@@ -127,6 +127,15 @@
// Table types // Table types
#define TBL_L2_UNICAST 0x04 #define TBL_L2_UNICAST 0x04
#define TBL_VLAN 0x03 #define TBL_VLAN 0x03
// Table read methods for the L2 table (TBL_L2_UNICAST):
#define TBL_LUTREAD_MAC 0
#define TBL_LUTREAD_ADDRESS 1
#define TBL_LUTREAD_NEXT_ADDRESS 2
#define TBL_LUTREAD_NEXT_L2UC 3
#define TBL_LUTREAD_NEXT_L2MC 4
#define TBL_LUTREAD_NEXT_L3MC 5
#define TBL_LUTREAD_NEXT_L2L3MC 6
#define TBL_LUTREAD_NEXT_L2UCSPA 7
#define RTL837X_L2_CTRL 0x5350 #define RTL837X_L2_CTRL 0x5350
#define L2_CTRL_LUT_IPMC_HASH 3 #define L2_CTRL_LUT_IPMC_HASH 3
+79
View File
@@ -26,6 +26,31 @@ const uint8_t physToLogPort[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8};
const uint8_t physToLogPort[] = { 4, 5, 6, 7, 3, 8}; const uint8_t physToLogPort[] = { 4, 5, 6, 7, 3, 8};
#endif #endif
struct l2_entry {
char mac[18];
char vlan[4];
char type;
uint8_t port;
uint16_t idx;
};
#define L2_ENTRY_NUM 12
#define L2_MAX_TRANSFER 5
const struct l2_entry l2_entries[12] = {
{ "00:01:2f:00:00:01", "001", 'l', 7, 0x10 },
{ "00:06:78:00:00:02", "001", 'l', 7, 0x20 },
{ "00:01:2e:00:00:03", "001", 's', 7, 0x30 },
{ "60:7d:09:00:00:04", "001", 'l', 7, 0x40 },
{ "1c:2a:a3:00:00:05", "001", 'l', 7, 0x50 },
{ "1c:2a:a3:00:00:06", "001", 's', 7, 0x60 },
{ "02:e0:4c:00:00:07", "001", 'l', 7, 0x70 },
{ "00:e0:4c:00:00:08", "001", 'l', 4, 0x80 },
{ "1c:2a:a3:00:00:09", "001", 'l', 9, 0x90 }, // CPU-Port
{ "3c:8c:f8:00:00:0a", "001", 'l', 7, 0xa0 },
{ "00:01:2e:00:00:0b", "001", 'l', 7, 0xb0 },
{ "00:01:2f:00:01:01", "001", 'l', 7, 0xc0 }
};
time_t last_called; time_t last_called;
time_t last_session_use; time_t last_session_use;
@@ -33,6 +58,7 @@ uint64_t txG[PORTS], txB[PORTS], rxG[PORTS], rxB[PORTS];
char txG_buff[20], txB_buff[20], rxG_buff[20], rxB_buff[20], counter_buf[20], counter_name[20]; char txG_buff[20], txB_buff[20], rxG_buff[20], rxB_buff[20], counter_buf[20], counter_name[20];
char sfp_temp[8], sfp_vcc[8], sfp_txbias[8], sfp_txpower[8], sfp_rxpower[8], sfp_laser[8], sfp_options[6]; char sfp_temp[8], sfp_vcc[8], sfp_txbias[8], sfp_txpower[8], sfp_rxpower[8], sfp_laser[8], sfp_options[6];
char num_buff[20]; char num_buff[20];
char l2_idx_buff[20];
char upload_buffer[4194304]; // 4MB char upload_buffer[4194304]; // 4MB
char *content_type = NULL; char *content_type = NULL;
@@ -286,6 +312,51 @@ void send_mirror(int s)
} }
void send_l2(int s, int idx)
{
struct json_object *entries, *v;
const char *jstring;
char *header = "HTTP/1.1 200 OK\r\n"
"Content-Type: application/json; charset=UTF-8\r\n\r\n";
entries = json_object_new_array();
// We find the next entry to transfer based on the index
printf("Starting with idx %04x\n", idx);
int j = 0;
while (j < L2_ENTRY_NUM && l2_entries[j].idx < idx)
j++;
printf("First entry %d, idx: %04x\n", j, l2_entries[j].idx);
int transferred = 0;
while ((j < L2_ENTRY_NUM + 1) && (transferred < L2_MAX_TRANSFER)) {
// if (rand() / (RAND_MAX / 2) >= 1)
// continue;
int i = j % L2_ENTRY_NUM;
printf("Entry %d, idx: %04x, transferred %d\n", i, l2_entries[i].idx, transferred);
v = json_object_new_object();
json_object_object_add(v, "mac", json_object_new_string(l2_entries[i].mac));
json_object_object_add(v, "vlan", json_object_new_string(l2_entries[i].vlan));
if (l2_entries[i].type == 'l')
json_object_object_add(v, "type", json_object_new_string("l"));
else
json_object_object_add(v, "type", json_object_new_string("s"));
json_object_object_add(v, "port", json_object_new_int(l2_entries[i].port));
sprintf(l2_idx_buff, "%04x", l2_entries[i].idx);
json_object_object_add(v, "idx", json_object_new_string(l2_idx_buff));
json_object_array_add(entries, v);
j++;
transferred++;
printf("j %d, transferred %d\n", j, transferred);
}
write(s, header, strlen(header));
jstring = json_object_to_json_string_ext(entries, JSON_C_TO_STRING_PLAIN);
write(s, jstring, strlen(jstring));
json_object_put(entries);
}
void send_lag(int s) void send_lag(int s)
{ {
char lag_buf[20]; char lag_buf[20];
@@ -543,6 +614,14 @@ void launch(struct Server *server)
else else
send_lag(new_socket); send_lag(new_socket);
goto done; goto done;
} else if (!strncmp(&buffer[4], "/l2.json?idx=", 13)) {
int idx = atoi(&buffer[17]);
printf("L2 request\n");
if (!authenticated)
send_unauthorized(new_socket);
else
send_l2(new_socket, idx);
goto done;
} else if (!strncmp(&buffer[4], "/mtu.json", 9)) { } else if (!strncmp(&buffer[4], "/mtu.json", 9)) {
printf("MTU request\n"); printf("MTU request\n");
if (!authenticated) if (!authenticated)