aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authoruser <user@clank>2026-07-17 12:15:09 +0200
committeruser <user@clank>2026-07-17 12:15:09 +0200
commitd102366e6808f74ef98044374d744e453efdbefa (patch)
tree6f35b79e4e0fb01c432262fbb5254f39331717ad
parentnet: real routed IP via SLIP<->TUN bridge (you can *ping* a Game Boy) (diff)
downloadgbos-d102366e6808f74ef98044374d744e453efdbefa.tar.gz
gbos-d102366e6808f74ef98044374d744e453efdbefa.tar.xz
gbos-d102366e6808f74ef98044374d744e453efdbefa.zip
net: ping - GB-originated ICMP echo (a Game Boy pings the real internet)
New `ping [A.B.C.D]` program (PROG_PING=27, bank 29): builds and sends ICMP echo requests from 10.0.0.2, then reads replies off the link port. It keeps reading SLIP frames until it finds *our* echo reply, skipping the IGMP/mDNS/ SSDP multicast noise that shares 10.0.0.0/24. Reply wait uses a generous srecv_nb spin budget since the emulator runs uncapped (no timer syscall yet). Verified over the tunbridge (with NAT): /# ping 10.0.0.1 -> 4/4 received, ttl=64 (the SLIP peer/host) /# ping 1.1.1.1 -> 4/4 received, ttl=56 (Cloudflare, real net!) ttl=56 is a real internet round trip (64 minus the hops). Combined with the host being able to ping the GB, the Game Boy is now a full two-way ICMP host.
Diffstat (limited to '')
-rw-r--r--Makefile2
-rw-r--r--c/ping.c100
-rw-r--r--include/gbos.inc1
-rw-r--r--src/programs.asm7
4 files changed, 109 insertions, 1 deletions
diff --git a/Makefile b/Makefile
index 2768899..fa642ad 100644
--- a/Makefile
+++ b/Makefile
@@ -20,7 +20,7 @@ $(BUILD)/%.o: src/%.asm | $(BUILD)
# C programs: compiled with SDCC into ROM-bank blobs and INCBIN'd by programs.asm
CBLOBS := c/chello.bin c/echo.bin c/true.bin c/false.bin c/uname.bin \
c/pid.bin c/cat.bin c/wc.bin c/head.bin c/args.bin \
- c/ls.bin c/save.bin c/rm.bin c/sh.bin c/ps.bin c/kill.bin c/spin.bin c/blk.bin c/mkdir.bin c/count.bin c/ptest.bin c/necho.bin c/wget.bin c/chat.bin c/netd.bin
+ c/ls.bin c/save.bin c/rm.bin c/sh.bin c/ps.bin c/kill.bin c/spin.bin c/blk.bin c/mkdir.bin c/count.bin c/ptest.bin c/necho.bin c/wget.bin c/chat.bin c/netd.bin c/ping.bin
$(BUILD)/programs.o: $(CBLOBS)
# C programs also depend on the shared libc sources
diff --git a/c/ping.c b/c/ping.c
new file mode 100644
index 0000000..12dabd9
--- /dev/null
+++ b/c/ping.c
@@ -0,0 +1,100 @@
+#include "netlib.h"
+/* ping - originate ICMP echo requests from the Game Boy (10.0.0.2) and print
+ the replies. Target defaults to 10.0.0.1 (the SLIP peer / host); with NAT on
+ the bridge it can reach real internet hosts too. Usage: ping [A.B.C.D] */
+
+static unsigned char pkt[128];
+static unsigned char dst[4];
+
+static unsigned int sum16(unsigned char *d, unsigned int len, unsigned int sum) {
+ unsigned int w, i = 0;
+ while (i + 1 < len) { w = ((unsigned int)d[i] << 8) | d[i + 1]; sum += w; if (sum < w) sum++; i += 2; }
+ if (len & 1) { w = (unsigned int)d[len - 1] << 8; sum += w; if (sum < w) sum++; }
+ return sum;
+}
+
+static unsigned char parse_ip(char *s, unsigned char *out) {
+ unsigned char i, v;
+ for (i = 0; i < 4; i++) {
+ if (*s < '0' || *s > '9') return 0;
+ v = 0;
+ while (*s >= '0' && *s <= '9') { v = (unsigned char)(v * 10 + (*s - '0')); s++; }
+ out[i] = v;
+ if (i < 3) { if (*s != '.') return 0; s++; }
+ }
+ return 1;
+}
+
+static void put_ip(unsigned char *ip) {
+ unsigned char i;
+ for (i = 0; i < 4; i++) { putu(ip[i]); if (i < 3) putc('.'); }
+}
+
+static void send_echo(unsigned char seq) {
+ unsigned int c;
+ pkt[0] = 0x45; pkt[1] = 0; pkt[2] = 0; pkt[3] = 28; /* 20 IP + 8 ICMP */
+ pkt[4] = 0; pkt[5] = 0; pkt[6] = 0; pkt[7] = 0;
+ pkt[8] = 64; pkt[9] = 1; pkt[10] = 0; pkt[11] = 0; /* TTL, proto ICMP */
+ pkt[12] = 10; pkt[13] = 0; pkt[14] = 0; pkt[15] = 2; /* src 10.0.0.2 */
+ pkt[16] = dst[0]; pkt[17] = dst[1]; pkt[18] = dst[2]; pkt[19] = dst[3];
+ c = ~sum16(pkt, 20, 0); pkt[10] = (unsigned char)(c >> 8); pkt[11] = (unsigned char)c;
+ pkt[20] = 8; pkt[21] = 0; pkt[22] = 0; pkt[23] = 0; /* echo request */
+ pkt[24] = 0x12; pkt[25] = 0x34; pkt[26] = 0; pkt[27] = seq;
+ c = ~sum16(pkt + 20, 8, 0); pkt[22] = (unsigned char)(c >> 8); pkt[23] = (unsigned char)c;
+ slip_send((char *)pkt, 28);
+}
+
+/* wait for the ICMP echo reply for 'seq', skipping unrelated frames (the shared
+ 10.0.0.0/24 carries IGMP/mDNS/SSDP noise). Returns 1 on match, 0 on timeout.
+ The emulator runs uncapped, so the spin budget is generous to span the
+ wall-clock round trip through the host bridge. */
+static unsigned char ping_wait(unsigned char seq) {
+ unsigned int o, i;
+ unsigned char len = 0, c, ihl;
+ int b;
+ for (o = 0; o < 20; o++) {
+ for (i = 0; i < 60000; i++) {
+ b = srecv_nb();
+ if (b < 0) continue;
+ c = (unsigned char)b;
+ if (c == SLIP_END) {
+ if (len) {
+ ihl = (unsigned char)((pkt[0] & 0x0f) << 2);
+ if (len >= (unsigned char)(ihl + 8) && pkt[9] == 1
+ && pkt[ihl] == 0 && pkt[ihl + 7] == seq) return 1;
+ len = 0; /* not ours - keep waiting */
+ }
+ continue;
+ }
+ if (c == SLIP_ESC) {
+ c = srecv();
+ if (c == SLIP_ESC_END) c = SLIP_END;
+ else if (c == SLIP_ESC_ESC) c = SLIP_ESC;
+ }
+ if (len < 128) pkt[len++] = c; else len = 0;
+ }
+ }
+ return 0;
+}
+
+void main(void) {
+ char *arg;
+ unsigned char seq, got = 0;
+ arg = getargs();
+ if (!arg || !*arg) { dst[0] = 10; dst[1] = 0; dst[2] = 0; dst[3] = 1; }
+ else if (!parse_ip(arg, dst)) { puts("ping: bad address"); nl(); sexit(1); }
+
+ puts("PING "); put_ip(dst); nl();
+ for (seq = 1; seq <= 4; seq++) {
+ send_echo(seq);
+ if (ping_wait(seq)) {
+ got++;
+ puts(" reply from "); put_ip(pkt + 12);
+ puts(" seq="); putu(seq);
+ puts(" ttl="); putu(pkt[8]); nl();
+ } else {
+ puts(" seq="); putu(seq); puts(" timeout"); nl();
+ }
+ }
+ puts("-- "); putu(got); puts("/4 received"); nl();
+}
diff --git a/include/gbos.inc b/include/gbos.inc
index 2de2a84..a80841c 100644
--- a/include/gbos.inc
+++ b/include/gbos.inc
@@ -219,5 +219,6 @@ DEF PROG_NECHO EQU 23
DEF PROG_WGET EQU 24
DEF PROG_CHAT EQU 25
DEF PROG_NETD EQU 26
+DEF PROG_PING EQU 27
ENDC
diff --git a/src/programs.asm b/src/programs.asm
index 7a01abc..854b2d2 100644
--- a/src/programs.asm
+++ b/src/programs.asm
@@ -121,6 +121,9 @@ ProgChat:
SECTION "prog_netd", ROMX[$4000], BANK[28]
ProgNetd:
INCBIN "c/netd.bin"
+SECTION "prog_ping", ROMX[$4000], BANK[29]
+ProgPing:
+ INCBIN "c/ping.bin"
; -----------------------------------------------------------------------------
; PROG_SH (bank 3) - the shell, now written in C (c/sh.c): parses >/</| and
@@ -217,6 +220,8 @@ ProgramTable::
dw ProgChat
db LOW(BANK(ProgNetd)), HIGH(BANK(ProgNetd))
dw ProgNetd
+ db LOW(BANK(ProgPing)), HIGH(BANK(ProgPing))
+ dw ProgPing
; -----------------------------------------------------------------------------
; NameTable (ROM0) - command name -> program id, searched by sys_lookup.
@@ -277,4 +282,6 @@ NameTable::
db PROG_CHAT
db "netd", 0
db PROG_NETD
+ db "ping", 0
+ db PROG_PING
db 0