aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authoruser <user@clank>2026-07-17 19:45:13 +0200
committeruser <user@clank>2026-07-17 19:45:13 +0200
commit4f038ecad87777bc1d74f8f93f267da8cd396446 (patch)
tree28332e362bb8c1c75e97483af21c51cc13cffdac
parentdocs: investigation notes for the idle link-socket drop (open bug) (diff)
downloadgbos-4f038ecad87777bc1d74f8f93f267da8cd396446.tar.gz
gbos-4f038ecad87777bc1d74f8f93f267da8cd396446.tar.xz
gbos-4f038ecad87777bc1d74f8f93f267da8cd396446.zip
net: unbreak ping against hosts that intermittently drop ICMP
Pinging sl0p.foo through gbhub looked hung: DNS resolved, then nothing. Packet-tracing showed the echo request leaving the hub's TUN and eth0 correctly NATed every time - but 80.78.19.56 blackholes ICMP for all ids in windows of tens of seconds (provider rate limiting). One lost reply wedged ping for minutes because net_recv's pump-counted timeout is effectively unbounded at native emulation speed. Two fixes: - NET_RECVNB (op 8): non-blocking recv - one RX pump, $FE if nothing buffered, same delivery/EOF semantics as NET_RECV otherwise. ping now waits <=~2s per seq (net_recv_nb + msleep loop), prints 'seq=N timeout' and moves on, like real ping. - ICMP echo id was hardcoded $1234 for every GB, every boot, so all sessions produced byte-identical flows - hostile to NAT conntrack (keyed on icmp id). wNetEchoId is now our host octet + rDIV timing noise sampled at DHCP lease, distinct per GB and per boot. Verified: 8 back-to-back native-speed gbhub runs, zero hangs; a run that hit a blackhole window printed seq=1 timeout then recovered to 3/4 received. GB<->GB ping and DNS/DHCP unaffected.
Diffstat (limited to '')
-rw-r--r--c/ping.c15
-rw-r--r--c/sock.h6
-rw-r--r--include/gbos.inc3
-rw-r--r--src/socket.asm57
4 files changed, 77 insertions, 4 deletions
diff --git a/c/ping.c b/c/ping.c
index f8d5bf7..8671318 100644
--- a/c/ping.c
+++ b/c/ping.c
@@ -14,6 +14,19 @@ static void put_ip(unsigned char *ip) {
for (i = 0; i < 4; i++) { putu(ip[i]); if (i < 3) putc('.'); }
}
+/* wait up to ~2s for an echo reply without blocking in the kernel: a lost
+ reply (some hosts rate-limit ICMP) must cost one "timeout" line, not
+ minutes wedged inside net_recv's pump-counted wait. */
+static unsigned char wait_reply(unsigned char s) {
+ unsigned char t, r;
+ for (t = 0; t < 40; t++) {
+ r = net_recv_nb(s, rbuf, (unsigned char)sizeof(rbuf));
+ if (r != 0xFE) return r; /* reply (or error) */
+ msleep(50);
+ }
+ return 0xFF; /* ~2s with no reply */
+}
+
void main(void) {
char *arg;
unsigned char s, seq, got = 0, i;
@@ -35,7 +48,7 @@ void main(void) {
puts("PING "); put_ip(dst); nl();
for (seq = 1; seq <= 4; seq++) {
net_send(s, payload, 4);
- if (net_recv(s, rbuf, (unsigned char)sizeof(rbuf)) != 0xFF) {
+ if (wait_reply(s) != 0xFF) {
got++;
puts(" reply from "); put_ip(dst); puts(" seq="); putu(seq); nl();
} else {
diff --git a/c/sock.h b/c/sock.h
index 9e4dd40..876e85c 100644
--- a/c/sock.h
+++ b/c/sock.h
@@ -42,6 +42,12 @@ static unsigned char net_recv(unsigned char s, void *buf, unsigned char max) {
_nr.op = 3; _nr.sock = s; _nr.buf = (unsigned char *)buf; _nr.len = max;
return sys_net(&_nr);
}
+/* non-blocking recv: one RX pump -> length, 0xFE if nothing buffered yet,
+ 0 on TCP EOF. Loop it with msleep() to own your timeout (see ping). */
+static unsigned char net_recv_nb(unsigned char s, void *buf, unsigned char max) {
+ _nr.op = 8; _nr.sock = s; _nr.buf = (unsigned char *)buf; _nr.len = max;
+ return sys_net(&_nr);
+}
/* set the local (source) port - needed so UDP replies demux back to us */
static unsigned char net_bind(unsigned char s, unsigned int port) {
_nr.op = 5; _nr.sock = s;
diff --git a/include/gbos.inc b/include/gbos.inc
index 937d083..e9f8d7d 100644
--- a/include/gbos.inc
+++ b/include/gbos.inc
@@ -153,6 +153,9 @@ DEF NET_CLOSE EQU 4 ; NR_SOCK
DEF NET_BIND EQU 5 ; NR_SOCK, NR_PORT -> bind local port (UDP)
DEF NET_POLL EQU 6 ; pump RX once (answer pings) -> A=0
DEF NET_SETIP EQU 7 ; set our IPv4 address from NR_IP (DHCP)
+DEF NET_RECVNB EQU 8 ; like NET_RECV but never blocks: pump once, then
+ ; A=len if a datagram was buffered, $FE if none yet,
+ ; 0 on TCP EOF. Lets programs own their timeouts (ping).
; socket types
DEF SOCK_ICMP EQU 1
DEF SOCK_UDP EQU 2
diff --git a/src/socket.asm b/src/socket.asm
index fa6b245..20ed982 100644
--- a/src/socket.asm
+++ b/src/socket.asm
@@ -48,6 +48,11 @@ wNetReq:: DS NR_SIZE
wNetSockPtr:: DS 2
wNetIhl:: DS 1
wNetSeq:: DS 1
+wNetEchoId:: DS 2 ; ICMP echo identifier: unique per boot + per host.
+ ; A fixed id (the old $1234) made every GB's ping
+ ; flow identical across boots/sessions, colliding in
+ ; upstream NAT conntrack (keyed on icmp id) - replies
+ ; were silently dropped until the stale entry expired.
wNetTx:: DS 320 ; tx build buffer
wNetRxBuf:: DS 320 ; rx frame assembly / parse
wNetRxLen:: DS 2 ; 16-bit: frames can exceed 255 bytes (DHCP/BOOTP)
@@ -90,6 +95,10 @@ net_init::
ld [wConHead], a
ld [wConTail], a
ld [wNetSeq], a
+ ldh a, [rDIV] ; per-boot echo id seed (refined at DHCP lease)
+ ld [wNetEchoId], a
+ cpl
+ ld [wNetEchoId+1], a
ret
; con_push(A) - queue a console-input byte (dropped if the ring is full)
@@ -147,6 +156,9 @@ net_op_setip:
ld [wNetOurIP+2], a
ld a, [wNetReq+NR_IP+3]
ld [wNetOurIP+3], a
+ ld [wNetEchoId], a ; echo id hi = our host octet (distinct per GB)
+ ldh a, [rDIV] ; lo = timer noise at lease time (distinct per
+ ld [wNetEchoId+1], a ; boot - DHCP timing varies with the network)
xor a
ret
@@ -300,6 +312,8 @@ sys_net::
jp z, net_op_poll
cp NET_SETIP
jp z, net_op_setip
+ cp NET_RECVNB
+ jp z, net_op_recvnb
ld a, $FF
ret
@@ -418,6 +432,43 @@ net_op_send:
ld a, $FF
ret
+; ---- recvnb(sock, buf, maxlen) -> A = len / $FE (nothing yet) / 0 (TCP EOF) --
+; One pump, no blocking: programs that need their own (wall-clock) timeout -
+; ping, most of all - loop recvnb+msleep instead of sitting in net_op_recv's
+; pump-counted wait, which at native speed can block for minutes if a reply
+; is lost (e.g. a host that rate-limits ICMP).
+net_op_recvnb:
+ ld a, [wNetReq + NR_SOCK]
+ call net_sock_ptr
+ ld a, l
+ ld [wNetSockPtr], a
+ ld a, h
+ ld [wNetSockPtr+1], a
+ call net_pump
+ ld a, [wNetReq+NR_SOCK] ; tcp_in may have moved wNetSockPtr; restore
+ call net_sock_ptr
+ ld a, l
+ ld [wNetSockPtr], a
+ ld a, h
+ ld [wNetSockPtr+1], a
+ call sock_has_rx
+ jp nz, net_op_recv.got ; deliver exactly like a blocking recv
+ call sock_ptr_hl ; TCP peer closed + nothing buffered -> EOF
+ ld a, l
+ add SK_STATE
+ ld l, a
+ ld a, h
+ adc 0
+ ld h, a
+ ld a, [hl]
+ cp TCP_DONE
+ jr nz, .none
+ xor a ; EOF -> 0, same as net_op_recv
+ ret
+.none
+ ld a, $FE ; nothing buffered yet
+ ret
+
; ---- recv(sock, buf, maxlen) -> A = len or $FF (timeout) ---------------------
net_op_recv:
ld a, [wNetReq + NR_SOCK]
@@ -595,9 +646,9 @@ icmp_send:
ld [wNetTx+21], a ; code
ld [wNetTx+22], a
ld [wNetTx+23], a ; cksum
- ld a, $12
- ld [wNetTx+24], a ; id
- ld a, $34
+ ld a, [wNetEchoId]
+ ld [wNetTx+24], a ; id (unique per boot + host, set by net_setip)
+ ld a, [wNetEchoId+1]
ld [wNetTx+25], a
xor a
ld [wNetTx+26], a ; seq hi