Commit Graph
1283 Commits
Author SHA1 Message Date
René van Dorst 9c3433cf6b Merge pull request #406 from DrDoof/fix/dhcp-xid-check
dhcp: fix transaction-id check so it actually compares
2026-09-06 18:37:42 +00:00
d00f 97d44fbf4b dhcp: drop the 16-bit swap on the 32-bit transaction id
The id was built and checked with HTONS(), which casts to 16 bits, so the
top half of the 32-bit transaction id was thrown away and the rest byte
swapped. The id is opaque: the client only has to receive its own value
back, so there is no byte order to honour and no reason to narrow it.

Assign and compare the field directly, so the full 32-bit id round-trips.
2026-09-06 20:30:40 +02:00
René van Dorst 00ee3d1325 Merge pull request #408 from DrDoof/fix/isr-no-console-write
Do not call write_char() from the external interrupt handlers
2026-09-06 18:11:30 +00:00
d00f efdf93a97a Do not call write_char() from the external interrupt handlers
The EX0..EX3 handlers each emitted a single debug character through
write_char(), which busy-waits on the serial TX-empty flag that is set by
the serial interrupt. If one of these handlers runs while a byte is being
transmitted, it waits for a flag that the equal-or-lower-priority serial
interrupt cannot deliver, hanging the handler with the system tick stopped.

The characters were only ever debug markers, so drop them; the handlers now
just acknowledge the interrupt. No interrupt service routine should call the
blocking console path.
2026-09-06 18:07:16 +02:00
d00f dc5cbcda16 dhcp: fix transaction-id check so it actually compares
The guard read `if (!DHCP_P->tid == HTONS(...))`. Unary `!` binds tighter
than `==`, so this evaluated `(!tid) == HTONS(id)`, i.e. `(tid ? 0 : 1) ==
HTONS(id)`, which is false for any non-zero id and never returns. The reply
was therefore accepted regardless of its transaction id, so a broadcast
reply meant for another client on the segment was parsed as ours.

Compare the field directly. The id is stored network-order in the packet
and host-order in the state, matching the HTONS() used when the request is
built, so `DHCP_P->tid != HTONS(dhcp_state.transaction_id)` is the intended
test.
2026-09-06 18:03:18 +02:00
René van Dorst 12c15603ed Merge pull request #395 from vDorst/fix_l2_cli_static_entry
Fix l2 cli static entry and use wait_table_ready() in more places
2026-09-06 14:17:41 +00:00
René van Dorst 3a7da77a99 Move wait_table_ready() up and replace all the same code for this function. 2026-09-06 10:06:02 +02:00
René van Dorst 13509d56e5 Fix l2 table, same as #355 but for CLI. 2026-09-06 10:06:02 +02:00
logicog 54b5e90976 Merge pull request #400 from DrDoof/feat/l2-show-lag
l2: name the group a learned address sits behind
2026-09-06 09:09:41 +02:00
d00f 8b7306f995 l2: name the group a learned address sits behind
A device on an aggregate is learned against whichever member the frame came in
on, and the hash moves that around: the same address changes port every time
the flow mix changes. The table was telling the truth and there was no way to
read it, since nothing on the page or the console says those two ports are one
link.

port_lag_of() already exists and already knows the answer, so both views ask
it. The console prints LAG1 in place of the member, and /l2.json carries a
"lag" field beside the port, 0 when the port stands alone, which is the shape
/stp.json already uses.

Costs 151 bytes of BANK1.
2026-09-05 17:29:13 +02:00
René van Dorst 8a7b146892 Merge pull request #396 from feelfree69/fix/syslog_port
Fix printing of syslog portNumber in WebUI
2026-09-04 11:35:33 +02:00
feelfree69 0998a9dfaa Fix printing of syslog portNumber in WebUI 2026-09-04 11:19:46 +02:00
René van Dorst aded07730e Merge pull request #365 from janh/zx-swtgw215as
Add PCB V2.1 to ZX-SWTGW215AS documentation and use MAC address on flash
2026-09-04 09:07:31 +02:00
René van Dorst 58d059384d Merge branch 'main' into zx-swtgw215as 2026-09-04 09:07:08 +02:00
d00f a7692022f1 httpd: reap idle TCP connections (#390)
* uip: time out an ESTABLISHED connection whose peer went silent

uIP has no keepalive and no idle timeout for an ESTABLISHED connection, so
a peer that vanishes without FIN or RST holds it open forever. A crashed
client does it, and so does a SYN caught while the switch is still
booting. With UIP_CONF_MAX_CONNECTIONS = 1 that wedges the single slot:
the web UI stops answering, port 80 no longer even SYN-ACKs, and only a
power cycle recovers it. Every other TCP state already has an exit path
(UIP_MAXRTX, UIP_TIME_WAIT_TIMEOUT); idle ESTABLISHED was the one hole.

Close it inside uIP rather than in httpd, so any future TCP service is
covered too. The retransmission timer is unused while nothing is in
flight, so the idle count lives in it: a 1 Hz strobe derived from the
uip_periodic() rate ages it, any arriving segment clears it, and past the
limit the connection is aborted through the same path as UIP_MAXRTX. Two
new options in uipopt.h, default off; this project enables 30 s.

httpd also joins uip_timedout() to its cleanup: it only handled closed
and aborted, so both this timeout and the existing UIP_MAXRTX one would
have left the transfer state stuck on a dead connection.

* uip: keep the tuning values out of the prose

The comment named the timeout and the tick rate, which go stale the
moment either constant is edited. It now says why an idle connection
is aged out and leaves the numbers to the defines under it.
2026-09-04 07:11:49 +02:00
René van Dorst e26693826e Merge pull request #393 from bloqaudio/fix/l2-delete-own-mac
httpd: refuse to delete the switch's own L2 entry
2026-09-03 21:09:16 +00:00
René van Dorst be6bd2a5b3 Merge pull request #380 from vDorst/fix_vlan_docs
Fix VLAN command documenation. 'u'-suffix is not accepted anymore.
2026-09-03 21:03:23 +00:00
René van Dorst 6c43091b7a Merge pull request #329 from DrDoof/feat/cmd-output
httpd: answer /cmd with what the command printed, and show it in the console tab
2026-09-03 20:33:26 +00:00
d00f b57b7d1a3d httpd: answer /cmd with the shared text header
The /cmd path carried its own copy of the plain-text response header,
which was the same string page_impl.c already keeps in
HTTP_RESPONCE_TXT, minus one difference: the shared one never said
Connection: close.

This httpd closes after every response, so a reply that does not
advertise it invites the browser to reuse the socket and lose the next
request. HTTP_RESPONCE_JSON has always said so; the text one was the
odd one out, and send_config() and send_cmd_log() were answering
without it.

The copy is gone and the shared string carries the header. The check
for a command that printed nothing now compares against the length
strtox() returned instead of sizeof() on the macro.
2026-09-03 22:18:45 +02:00
René van Dorst fc86ea8be5 Merge pull request #333 from DrDoof/feat/syslog-hostname-port
syslog: send a hostname, allow a port other than 514, and stop the save from dropping the address
2026-09-03 20:02:41 +00:00
René van Dorst 91780110f2 Merge pull request #389 from DrDoof/fix/login-page-i18n
httpd: let the login page fetch its translations
2026-09-03 20:00:17 +00:00
bloqaudio f0e9953081 web: hide the L2 delete button for the CPU entry and drop a deleted row
The firmware now refuses to delete the switch's own entry, so the
button on the CPU row can only fail; leave it out. When a delete
succeeds, remove the entry from the cached table and repaint instead
of leaving the stale row until the next refresh.
2026-09-03 14:33:07 -05:00
bloqaudio 1c9bf10b87 httpd: read the table registers straight from the SFRs in l2_delete()
Test the busy flag and compare the entry's MAC against the SFR data
registers directly instead of going through the sfr_data copy, and
rewrite TBL_DATA_0 in place rather than reassembling all four bytes.
Smaller and faster on the deepest httpd path.

Suggested-by: vDorst
2026-09-03 14:33:07 -05:00
d00f 2e2529be27 syslog: print the server address with print_ip()
The status line built the dotted quad by hand, which is what
print_ip() from cmd_parser.c already does. Reusing it drops seven
calls to one and takes 48 bytes off BANK2.

The prototype is not __banked because cmd_parser.c and syslog.c are
both in BANK2; a plain call is what the compiler needs here. Should
either file move to another bank, this has to become __banked with it.
2026-09-03 21:27:02 +02:00
René van Dorst f6348765b4 Merge pull request #343 from bloqaudio/feat/mgmt-static-l2
port: pin the management MAC to the CPU port with a static L2 entry
2026-09-03 18:25:11 +00:00
Pietro AmeruosoandPietro Ameruoso 6411c5157c Add Hi-K0402WS V2 hardware support (#391)
Signed-off-by: Pietro Ameruoso <p.ameruso@live.it>
Co-authored-by: Pietro Ameruoso <p.ameruso@live.it>
2026-09-03 19:47:02 +02:00
eeeple d06dc3e5f8 Added Siscolink SL-G0402F support (#392)
* Added Siscolink SL-G0402F picture

* Update PCB-K0402WS-V3.0.md

* Update supported_devices.md

* Rotated picture
2026-09-03 19:44:17 +02:00
bloqaudio dd7f9f7c7b httpd: refuse to delete the switch's own L2 entry
The L2 page's Delete button removes any table entry by index, including
the entry for the switch's own MAC on the CPU port. Without that entry
unicast to the management address is no longer delivered to the CPU, so
management goes dark until the CPU happens to transmit again and the
entry is relearned. Compare the entry's MAC against uip_ethaddr before
rewriting it and answer result 0 for the switch's own address, as for a
missing entry. The compare is inline: this handler sits at the bottom of
the uIP call chain, where every extra call level costs internal RAM.
2026-09-03 10:33:53 -05:00
bloqaudio 3b8dba6e87 port: pin the management MAC to the CPU port with a static L2 entry
The CPU's own MAC exists in the L2 table only as a learned entry,
created whenever the CPU transmits, and it ages out like any other
dynamic entry. Unknown unicast is not flooded to the CPU port, so on an
idle switch (no web session, no management traffic) the entry expires
after the aging time and frames for the management IP no longer reach
the CPU: pings die until the next CPU transmission (an ARP reply)
re-teaches the entry, then die again. Real management sessions keep the
entry alive continuously, which is why this is only visible on idle
switches.

Install a static entry for the management MAC on the CPU port after the
startup config has been replayed, so it lands in the final management
VLAN, and move it when the management VLAN is changed at runtime.

Entry mechanics, established on hardware: a table write with read-method
0 hashes the key in TBL_DATA_IN and places the entry itself (a search
returns index 0 on a miss, there is no software-visible insertion slot).
The age field in the third data word must be non-zero for the entry to
be visible to lookup at all, and the static flag (bit 0 of byte 1 of
that word) stops the aging engine from decrementing it: with the flag a
full-age entry survives untouched where an identical unflagged entry
ages to invisible within the aging period. With no management VLAN
configured the entry lives in VLAN 1.
2026-09-02 21:35:51 -05:00
d00f a259587bf9 system: do not let a failed console request look like an empty answer
fetch() does not throw on 4xx, so a request that came back 401 after the
session had timed out reached the same line as a successful one: the
window showed the prompt and nothing under it, and the input was cleared
as if the command had run. The session lasts 200 seconds, so leaving the
tab open is enough to hit it.

A 401 now goes to the login page instead. The transcript also gets a
trailing newline when the answer lacks one, so the next prompt does not
run into the last line of output, and it is capped so a long session
does not keep growing the node.
2026-09-03 01:43:30 +02:00
d00f f23d840cee httpd: make the /cmd answer usable when a command fails or floods
Three ways the answer could mislead the caller, all in the paths that
matter most when something is wrong.

The capture sat in write_char(), so anything printed through
print_string_no_syslog() escaped it. That is exactly the replies of the
syslog commands, which deliberately avoid the syslog path: "syslog on"
answered with the bare address and no sentence around it, "syslog off"
answered with nothing at all. Moving the capture one level down, into
write_char_no_syslog(), catches both paths and costs nothing, since
write_char() reaches it anyway.

On a parse error send_bad_request() rewrote the buffer from the start,
throwing away the very text that explains the error and replacing it
with the 400 HTML page. Keeping the captured text and restating only the
status keeps the parser's own message; "400 NO" is as long as "200 OK",
so the header does not have to be rebuilt.

An overlong listing was cut at the buffer end with nothing to show for
it, so a clipped answer read as a short one. It now ends in a marker,
and the reserved room is accounted for in the guard.

While here: a command that configures something prints nothing, and an
empty body reads the same as "nothing ran", so a successful silent
command now answers "OK".

No internal RAM and no xdata; 174 bytes of BANK1, which has 7356 left.
2026-09-03 01:43:28 +02:00
d00f f46ef3a43c system: show what the console command printed
The console tab posts to /cmd and throws the response away, so the page stayed
silent whether the command worked, was refused or was just misspelt. Now that
/cmd answers with the command's output, the page keeps a running transcript:
each command echoed after a prompt, then whatever it printed.

The box is a pre element filled through textContent, so nothing the device
prints is taken for markup. It scrolls to the newest line and the input clears
after a send.
2026-09-03 01:43:09 +02:00
d00f 81af3a29a2 httpd: return what a command printed over /cmd
POST /cmd runs the command and answers with an empty 200, so a caller cannot
tell "done" from "nothing happened", and whatever the command prints only
reaches the serial console. Reading a register over the network needs a second
channel just to see the answer.

write_char() is the one funnel every console helper goes through, and it already
tees into the syslog buffer. A third sink, live only while the httpd runs a
command that arrived over the network, puts the same bytes into the response
behind the header. Serial and syslog keep getting their copy.

The capture stops at the end of the response buffer, so a long listing is
truncated rather than overrunning it.

BANK1 grows 39 bytes and the common area 52. One byte of xdata, BANK2 unchanged.
2026-09-03 01:43:07 +02:00
d00f 5a55524115 httpd: let the login page fetch its translations
login.html asks for i18n.js, but the file is not on the short list a
client may have before it authenticates, so the request is answered with
a redirect back to the login page and the strings stay as the English
that is baked into the markup.

The file carries only the interface text, the same text the page already
ships untranslated, so serving it before login gives nothing away.
2026-09-03 01:12:56 +02:00
René van Dorst 6b39bcc448 JS: vlan don't accept u-suffic anymore.
Als update CHANGELOG about CPU_PORT
2026-09-02 19:44:41 +02:00
René van Dorst 4c8e0ce975 Merge pull request #375 from bloqaudio/fix/post-body-split
httpd: buffer a plain POST body that arrives after the headers
2026-09-02 08:59:56 +02:00
René van Dorst 71344825c7 Merge pull request #382 from DrDoof/fix/sfp-empty-slot-json
sfp: stop the web pages printing the previous slot's bytes
2026-09-02 08:25:42 +02:00
d00f 40ce742d04 syslog: take the review notes on the state flag and the header length
The enabled flag is a flag, so say bool. The header length no longer needs
to live in xdata: it was put there when a local would not link, and there
is room for it here.

The overview names the row after the JSON key, so the syslog row said
"syslog_server_ip" while carrying only an address. It now says
"syslog_server" and carries the port with it.
2026-09-01 23:08:42 +02:00
bloqaudio f5e4fa5134 httpd: buffer a plain POST body that arrives after the headers
A POST /cmd whose body arrives in a separate TCP segment from the headers
executed nothing: handle_post() read the body from the segment that
carried the request line, found it empty, and answered 200 OK. Python's
urllib and requests both write headers and body separately, so every
scripted command from those clients became a silent no-op. POST /login had
the same hole. The multipart endpoints were fixed earlier; this covers the
plain ones.

When the first segment holds fewer body bytes than Content-Length
announces, the bytes accumulate in config_buf (idle outside multipart
uploads) and the connection waits in TSTATE_POSTBODY; once the announced
length is in, the endpoint runs against the buffer. The /cmd and /login
executions move into run_cmd_body() and run_login_body() so both paths
share them; the 200 OK builder they duplicated becomes send_ok(), and
run_login_body() checks the pwd= prefix instead of skipping four bytes
blindly.

The announced length is the contract on both paths. A body that arrives
with the headers is terminated at Content-Length before it runs, so
pipelined bytes after it are not executed as commands the way the old
code did. A request without a usable Content-Length answers 411: without
one there is no way to know when the body has arrived, and the old 200
OK for an empty body is the bug this fixes. Bodies announced at or above
the buffer size answer 400 before anything is buffered.

The wait state has a deadline. uIP is built with a single connection,
and an ESTABLISHED connection with nothing in flight never times out on
its own, so headers followed by silence (a script interrupted, a link
dropped mid-request, or someone holding the socket on purpose) would
otherwise keep the slot until reboot; POST /login reaches the wait
before authentication. The poll handler aborts the connection when the
body has made no progress for five seconds, measured on the 200 Hz
system tick rather than on poll count, which runs faster under interrupt
load.
2026-09-01 14:55:56 -05:00
bloqaudio 28020e8186 httpd: parse Content-Length in scan_header()
Record the announced body length alongside Content-Type and the session
cookie. The value goes through parse_short(), so an announcement that does
not fit sixteen bits saturates at 0xffff rather than wrapping to something
the size checks would accept, and a missing or unparseable header reads as 0.
Nothing consumes it yet; the plain POST endpoints take it up next.
2026-09-01 14:52:09 -05:00
bloqaudio cbcf67a693 httpd: keep scan_header() on the terminator of a truncated header
The scan stepped over the NUL that ends the received data before breaking,
so on a request whose headers were cut short it returned a pointer one past
the data. handle_post() then tested that byte for the end of the header,
reading whatever an earlier packet had left there instead of the NUL the
appcall wrote, and could go on to run an endpoint against the stale bytes.
Break before advancing so the returned pointer sits on the NUL and the
caller's check sees it.
2026-09-01 14:52:09 -05:00
bloqaudio d6bf46595a httpd: saturate parse_short() instead of wrapping
The digit loop accumulated into a uint16_t without a bound, so a query
such as /vlan.json?vid=65540 read as VLAN 4 and every consumer saw a small,
valid-looking number for an out-of-range one. Clamp the result at 0xffff
once another digit would overflow (6552 * 10 + 9 is the last value that
fits). The consumers already reject or mask 0xffff: vlan_get() refuses
anything from 4095 up, send_l2() masks the index to the table size, and
l2_delete() masks the high byte.
2026-09-01 14:52:09 -05:00
bloqaudio 2499d116a3 httpd: match header field names case-insensitively
scan_header() found Content-Type and Cookie with is_word(), which compares
bytes exactly and requires a separator after the pattern. Field names are
case-insensitive (RFC 7230 section 3.2), and the whitespace after the colon
is optional, so "content-type: multipart/..." and "Content-Type:multipart/..."
were both treated as absent. The value pointers were then fixed offsets that
assumed exactly one space.

Add header_value(), which matches a lower-case name anchored at the line
start, folds the request bytes to lower case as it compares, and returns
the start of the value past any blanks, so a call site no longer adds the
name length by hand. Cookie scanning reuses the returned pointer, and the
end-of-header test becomes the strstart() the file already has.
2026-09-01 14:52:09 -05:00
René van Dorst 74a1c6831a Merge pull request #335 from bloqaudio/feat/ports-connected-devices
ports: show the devices connected behind each port
2026-09-01 18:02:22 +00:00
René van Dorst a79c483f94 Merge pull request #385 from 7Ji/fg_8gt_1sx
Add FG-8GT-1SX
2026-09-01 17:35:54 +00:00
René van Dorst 75aa23468c Merge pull request #374 from bloqaudio/upload-cleanup
httpd: clean up the upload path in handle_post()
2026-09-01 17:30:56 +00:00
René van Dorst 373a21f4a1 Add CHANGELOG.md and vlan command breaking change. 2026-09-01 19:05:21 +02:00
logicog 89b4cf83ce Merge pull request #314 from DrDoof/feat/stp-v2
stp: Spanning Tree support for the RTL837x switches
2026-09-01 18:52:20 +02:00
bloqaudio 005209f97a httpd: split the upload fragment handlers out of handle_post()
The tail of handle_post() interleaved the config and firmware upload
paths behind one config_upload conditional. Give each path its own
function, handle_config_fragment() and handle_firmware_fragment(), and
reduce handle_post() to routing.

Both handlers keep their own copy of the buffer-and-bounds-check
prologue: with SDCC a shared helper costs 2 bytes of direct RAM for
the fragment pointer (a spill slot when the parameter lives in xdata,
a DSEG home when it does not), and direct RAM is fully allocated. The
memory layout is unchanged from the pre-split code.
2026-09-01 09:59:52 -05:00
bloqaudio f0f142fbe0 httpd: track buffered upload bytes in one accumulator 2026-09-01 09:59:52 -05:00