From 26a2ccb8368a9968b6ec16068ad68dd0edb40641 Mon Sep 17 00:00:00 2001 From: user Date: Fri, 17 Jul 2026 12:54:43 +0200 Subject: net: UDP sockets in the kernel + a DNS resolver (real hostname lookups) Adds UDP to the kernel socket layer on top of the ICMP core: - net_sum() (raw folded sum) split out of net_cksum() so a UDP pseudo-header (src/dst IP + proto + length) can seed the segment checksum. - udp_send: builds IP+UDP with the pseudo-header checksum; NET_BIND sets the local/source port; net_connect sets the peer. - udp_in: demuxes inbound UDP by destination port to the bound socket (net_find_udp), delivers the payload + source addr. Also fixes a real recv bug: net_pump clobbers BC/DE/HL, so the old recv timeout counted in registers and was effectively random. recv now counts in WRAM. New `nslookup HOST` (PROG_NSLOOKUP=28, bank 30): builds a DNS A query and parses the answer (with 0xC0 name-compression) entirely in userland over a UDP socket - the kernel never sees DNS, just UDP. Verified through the bridge NAT: /# nslookup example.com -> example.com -> 172.66.147.243 This gives us name resolution for the TCP/HTTP demo next. --- Makefile | 2 +- c/nslookup.c | 88 ++++++++++++++ c/sock.h | 6 + include/gbos.inc | 1 + src/programs.asm | 7 ++ src/socket.asm | 342 +++++++++++++++++++++++++++++++++++++++++++++++++++---- 6 files changed, 424 insertions(+), 22 deletions(-) create mode 100644 c/nslookup.c diff --git a/Makefile b/Makefile index fa642ad..e3bad9d 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/ping.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 c/nslookup.bin $(BUILD)/programs.o: $(CBLOBS) # C programs also depend on the shared libc sources diff --git a/c/nslookup.c b/c/nslookup.c new file mode 100644 index 0000000..020a1c1 --- /dev/null +++ b/c/nslookup.c @@ -0,0 +1,88 @@ +#include "sock.h" +/* nslookup HOST - resolve a hostname to an IPv4 address via DNS over UDP. + The kernel owns UDP; this program just builds the DNS query and parses the + answer. Queries 1.1.1.1:53 (reachable through the bridge NAT). */ + +/* buf is first and large so 'name' lands past the arg string at 0xA000 */ +static unsigned char buf[200]; +static unsigned char name[64]; +static unsigned char dnssrv[4]; +static unsigned char ip[4]; + +/* build a DNS A-record query for 'host' into buf; return total length */ +static unsigned char build_query(unsigned char *host) { + unsigned char i = 0, p = 12, lblpos, len; + buf[0] = 0x12; buf[1] = 0x34; /* id */ + buf[2] = 0x01; buf[3] = 0x00; /* flags: recursion desired */ + buf[4] = 0; buf[5] = 1; /* qdcount = 1 */ + buf[6] = 0; buf[7] = 0; buf[8] = 0; buf[9] = 0; buf[10] = 0; buf[11] = 0; + while (host[i]) { + lblpos = p++; len = 0; + while (host[i] && host[i] != '.') { buf[p++] = host[i]; i++; len++; } + buf[lblpos] = len; + if (host[i] == '.') i++; + } + buf[p++] = 0; /* root label */ + buf[p++] = 0; buf[p++] = 1; /* QTYPE = A */ + buf[p++] = 0; buf[p++] = 1; /* QCLASS = IN */ + return p; +} + +/* advance past a DNS name at offset p (handles 0xC0 compression pointers) */ +static unsigned int skip_name(unsigned int p) { + for (;;) { + if (buf[p] == 0) return p + 1; + if ((buf[p] & 0xC0) == 0xC0) return p + 2; + p += buf[p] + 1; + } +} + +/* find the first A record in the response; fill ip[]; return 1 on success */ +static unsigned char parse_answer(void) { + unsigned int p, a, an, type, rdlen; + an = ((unsigned int)buf[6] << 8) | buf[7]; /* ancount */ + if (an == 0) return 0; + p = skip_name(12) + 4; /* skip question (QNAME+QT+QC) */ + for (a = 0; a < an; a++) { + p = skip_name(p); + type = ((unsigned int)buf[p] << 8) | buf[p + 1]; + p += 8; /* TYPE(2)+CLASS(2)+TTL(4) */ + rdlen = ((unsigned int)buf[p] << 8) | buf[p + 1]; + p += 2; + if (type == 1 && rdlen == 4) { + ip[0] = buf[p]; ip[1] = buf[p + 1]; ip[2] = buf[p + 2]; ip[3] = buf[p + 3]; + return 1; + } + p += rdlen; + } + return 0; +} + +void main(void) { + char *host; + unsigned char s, i, qlen, rlen; + host = getargs(); + if (!host || !*host) { puts("usage: nslookup HOST"); nl(); sexit(1); } + for (i = 0; host[i] && i < 63; i++) name[i] = host[i]; /* arg -> safe buf */ + name[i] = 0; + + dnssrv[0] = 1; dnssrv[1] = 1; dnssrv[2] = 1; dnssrv[3] = 1; /* 1.1.1.1 */ + s = net_socket(SOCK_UDP); + if (s == 0xFF) { puts("nslookup: no socket"); nl(); sexit(1); } + net_bind(s, 40000); + net_connect(s, dnssrv, 53); + + qlen = build_query(name); + net_send(s, buf, qlen); + rlen = net_recv(s, buf, sizeof(buf)); + if (rlen == 0xFF) { puts("nslookup: timeout"); nl(); net_close(s); sexit(1); } + + if (parse_answer()) { + puts((char *)name); puts(" -> "); + for (i = 0; i < 4; i++) { putu(ip[i]); if (i < 3) putc('.'); } + nl(); + } else { + puts("nslookup: no A record"); nl(); + } + net_close(s); +} diff --git a/c/sock.h b/c/sock.h index 9af2351..1298271 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); } +/* 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; + _nr.port_hi = (unsigned char)(port >> 8); _nr.port_lo = (unsigned char)port; + return sys_net(&_nr); +} static void net_close(unsigned char s) { _nr.op = 4; _nr.sock = s; sys_net(&_nr); } diff --git a/include/gbos.inc b/include/gbos.inc index a4c9d52..74eee8e 100644 --- a/include/gbos.inc +++ b/include/gbos.inc @@ -249,5 +249,6 @@ DEF PROG_WGET EQU 24 DEF PROG_CHAT EQU 25 DEF PROG_NETD EQU 26 DEF PROG_PING EQU 27 +DEF PROG_NSLOOKUP EQU 28 ENDC diff --git a/src/programs.asm b/src/programs.asm index 854b2d2..9ae7f58 100644 --- a/src/programs.asm +++ b/src/programs.asm @@ -124,6 +124,9 @@ ProgNetd: SECTION "prog_ping", ROMX[$4000], BANK[29] ProgPing: INCBIN "c/ping.bin" +SECTION "prog_nslookup", ROMX[$4000], BANK[30] +ProgNslookup: + INCBIN "c/nslookup.bin" ; ----------------------------------------------------------------------------- ; PROG_SH (bank 3) - the shell, now written in C (c/sh.c): parses >/ program id, searched by sys_lookup. @@ -284,4 +289,6 @@ NameTable:: db PROG_NETD db "ping", 0 db PROG_PING + db "nslookup", 0 + db PROG_NSLOOKUP db 0 diff --git a/src/socket.asm b/src/socket.asm index f0d8444..ef9d4b9 100644 --- a/src/socket.asm +++ b/src/socket.asm @@ -35,6 +35,8 @@ wNetTx:: DS 256 ; tx build buffer wNetRxBuf:: DS 256 ; rx frame assembly / parse wNetRxLen:: DS 1 wNetRxEsc:: DS 1 +wNetUdpPort:: DS 2 ; scratch: UDP dst port being demuxed +wNetTO:: DS 3 ; recv timeout counter (net_pump clobbers registers) SECTION "socket", ROM0 @@ -123,9 +125,10 @@ net_slip_send: ret ; ----------------------------------------------------------------------------- -; net_cksum(HL = buf, B = len, DE = seed) -> HL = inverted 16-bit checksum. -; One's-complement sum (RFC 1071), carry-folded each add. len <= 255. -net_cksum: +; net_sum(HL = buf, B = len, DE = seed) -> DE = folded 16-bit one's-complement +; sum (RFC 1071), carry-folded each add. len <= 255. Used to chain a pseudo- +; header into a segment checksum. +net_sum: .loop ld a, b cp 2 @@ -147,15 +150,19 @@ net_cksum: .tail ld a, b or a - jr z, .fold + ret z ld a, [hl] ; last odd byte -> high half add d ld d, a - jr nc, .fold + ret nc inc e - jr nz, .fold + ret nz inc d -.fold + ret + +; net_cksum(HL, B, DE=seed) -> HL = inverted 16-bit checksum. +net_cksum: + call net_sum ld a, d cpl ld h, a @@ -201,11 +208,30 @@ sys_net:: jp z, net_op_recv cp NET_CLOSE jp z, net_op_close + cp NET_BIND + jp z, net_op_bind cp NET_POLL jp z, net_op_poll ld a, $FF ret +; ---- bind(sock, port) - set local port ----------------------------------- +net_op_bind: + ld a, [wNetReq + NR_SOCK] + call net_sock_ptr + ld a, l + add SK_LPORT + ld l, a + ld a, h + adc 0 + ld h, a + ld a, [wNetReq + NR_PORT] + ld [hl+], a + ld a, [wNetReq + NR_PORT + 1] + ld [hl], a + xor a + ret + ; ---- socket(type) -> A = sockid or $FF --------------------------------------- net_op_socket: ld c, 0 @@ -281,6 +307,8 @@ net_op_send: ld a, [hl] ; SK_TYPE cp SOCK_ICMP jp z, icmp_send + cp SOCK_UDP + jp z, udp_send ld a, $FF ret @@ -292,21 +320,27 @@ net_op_recv: ld [wNetSockPtr], a ld a, h ld [wNetSockPtr+1], a - ld b, 200 ; outer timeout -.outer - ld de, 0 -.inner + xor a ; 24-bit timeout counter in WRAM (net_pump + ld [wNetTO], a ; clobbers BC/DE/HL, so we can't count in regs) + ld [wNetTO+1], a + ld [wNetTO+2], a +.wait call net_pump - call sock_has_rx ; Z clear (nz) if HASRX set + call sock_has_rx ; nz if a datagram is buffered jr nz, .got - dec de - ld a, d - or e - jr nz, .inner - call SchedYield - dec b - jr nz, .outer - ld a, $FF ; timeout + ld hl, wNetTO + inc [hl] + jr nz, .wait ; low byte didn't wrap -> spin (no yield) + call SchedYield ; every 256 pumps, yield to the scheduler + inc hl + inc [hl] + jr nz, .wait + inc hl + inc [hl] + ld a, [hl] + cp 250 ; timeout after ~250*65536 pumps + jr c, .wait + ld a, $FF ret .got ; copy sock.RXBUF -> process NR_BUF, len = sock.RXLEN, NR_IP = sock.SRCIP @@ -492,6 +526,153 @@ icmp_send: xor a ret +; ============================================================================= +; udp_send - build + transmit a UDP datagram from the current socket. +; ============================================================================= +udp_send: + ld a, $45 + ld [wNetTx+0], a + xor a + ld [wNetTx+1], a + ld [wNetTx+2], a + ld a, [wNetReq+NR_LEN] + add 28 ; 20 IP + 8 UDP + payload + ld [wNetTx+3], a + xor a + ld [wNetTx+4], a + ld [wNetTx+5], a + ld [wNetTx+6], a + ld [wNetTx+7], a + ld a, 64 + ld [wNetTx+8], a + ld a, 17 + ld [wNetTx+9], a ; proto UDP + xor a + ld [wNetTx+10], a + ld [wNetTx+11], a + ld a, [wNetOurIP+0] + ld [wNetTx+12], a + ld a, [wNetOurIP+1] + ld [wNetTx+13], a + ld a, [wNetOurIP+2] + ld [wNetTx+14], a + ld a, [wNetOurIP+3] + ld [wNetTx+15], a + ; dst IP = sock.RIP + call sock_ptr_hl + ld a, l + add SK_RIP + ld l, a + ld a, h + adc 0 + ld h, a + ld de, wNetTx+16 + ld b, 4 +.dip + ld a, [hl+] + ld [de], a + inc de + dec b + jr nz, .dip + ; UDP header at wNetTx+20: src port = SK_LPORT + call sock_ptr_hl + ld a, l + add SK_LPORT + ld l, a + ld a, h + adc 0 + ld h, a + ld a, [hl+] + ld [wNetTx+20], a + ld a, [hl] + ld [wNetTx+21], a + ; dst port = SK_RPORT + call sock_ptr_hl + ld a, l + add SK_RPORT + ld l, a + ld a, h + adc 0 + ld h, a + ld a, [hl+] + ld [wNetTx+22], a + ld a, [hl] + ld [wNetTx+23], a + ; length = 8 + payload + xor a + ld [wNetTx+24], a + ld a, [wNetReq+NR_LEN] + add 8 + ld [wNetTx+25], a + xor a + ld [wNetTx+26], a ; cksum = 0 + ld [wNetTx+27], a + ; payload from process buf + ld a, [wNetReq+NR_BUF] + ld l, a + ld a, [wNetReq+NR_BUF+1] + ld h, a + ld de, wNetTx+28 + ld a, [wNetReq+NR_LEN] + ld b, a + or a + jr z, .nopl +.cppl + ld a, [hl+] + ld [de], a + inc de + dec b + jr nz, .cppl +.nopl + ; UDP checksum with pseudo-header: sum(src+dst IP) + proto + udplen, then seg + ld hl, wNetTx+12 + ld b, 8 + ld de, 0 + call net_sum ; DE = IP-address sum + ld a, e + add 17 ; + protocol + ld e, a + ld a, d + adc 0 + ld d, a + ld a, [wNetReq+NR_LEN] + add 8 ; + udp length + add e + ld e, a + ld a, d + adc 0 + ld d, a ; DE = pseudo-header seed + ld a, [wNetReq+NR_LEN] + add 8 + ld b, a + ld hl, wNetTx+20 + call net_cksum ; HL = inverted checksum + ld a, h + or l + jr nz, .cknz + ld hl, $FFFF ; 0 means "no checksum" -> send all-ones +.cknz + ld a, h + ld [wNetTx+26], a + ld a, l + ld [wNetTx+27], a + ; IP checksum + ld hl, wNetTx + ld b, 20 + ld de, 0 + call net_cksum + ld a, h + ld [wNetTx+10], a + ld a, l + ld [wNetTx+11], a + ld a, [wNetReq+NR_LEN] + add 28 + ld b, a + ld hl, wNetTx + call net_slip_send + xor a + ret + ; ============================================================================= ; net_pump - read available serial bytes, assemble SLIP frames, process each. ; Non-blocking: returns when the link has no more bytes ready. @@ -576,7 +757,9 @@ process_frame: ld [wNetIhl], a ; ihl in bytes ld a, [wNetRxBuf+9] ; protocol cp 1 - jr z, icmp_in + jp z, icmp_in + cp 17 + jp z, udp_in ret ; ----------------------------------------------------------------------------- @@ -736,6 +919,123 @@ icmp_reply: jr nz, .cp ret +; ----------------------------------------------------------------------------- +; udp_in - deliver an inbound UDP datagram to the socket bound to its dst port. +udp_in: + ld a, [wNetIhl] + add 2 + ld e, a + ld d, 0 + ld hl, wNetRxBuf + add hl, de ; HL = &udp.dstport + ld a, [hl+] + ld [wNetUdpPort], a + ld a, [hl] + ld [wNetUdpPort+1], a + call net_find_udp ; HL = &sock, CF if none + ret c + ; deliver: HASRX=1, SRCIP, RXLEN + RXBUF (payload after ihl+8) + push hl + ld a, l + add SK_HASRX + ld l, a + ld a, h + adc 0 + ld h, a + ld a, 1 + ld [hl], a + pop hl + push hl + ld a, l + add SK_SRCIP + ld l, a + ld a, h + adc 0 + ld h, a + ld de, wNetRxBuf+12 + ld b, 4 +.sip + ld a, [de] + ld [hl+], a + inc de + dec b + jr nz, .sip + pop hl + ; payload offset = ihl + 8, length = rxlen - ihl - 8 + ld a, [wNetIhl] + add 8 + ld e, a + ld d, 0 + ld a, [wNetRxLen] + sub e + ld c, a ; C = payload length + push hl + ld a, l + add SK_RXLEN + ld l, a + ld a, h + adc 0 + ld h, a + ld a, c + ld [hl], a + pop hl + ld a, l + add SK_RXBUF + ld l, a + ld a, h + adc 0 + ld h, a ; HL = &sock.RXBUF (dest) + push hl + ld hl, wNetRxBuf + add hl, de ; HL = &payload (src) + pop de ; DE = dest + ld a, c + or a + ret z +.cp + ld a, [hl+] + ld [de], a + inc de + dec c + jr nz, .cp + ret + +; net_find_udp -> HL = &UDP socket bound to wNetUdpPort, CF set if none +net_find_udp: + ld c, 0 +.l + ld a, c + call net_sock_ptr ; HL = &sock (clobbers B, DE) + ld a, [hl] + cp SOCK_UDP + jr nz, .nx + push hl + ld a, l + add SK_LPORT + ld l, a + ld a, h + adc 0 + ld h, a + ld a, [wNetUdpPort] + cp [hl] + jr nz, .nomatch + inc hl + ld a, [wNetUdpPort+1] + cp [hl] + jr nz, .nomatch + pop hl ; match + or a ; CF = 0 + ret +.nomatch + pop hl +.nx + inc c + ld a, c + cp MAX_SOCKS + jr c, .l + scf + ret + ; net_swap_ip - swap the src/dst IP fields of the frame in wNetRxBuf net_swap_ip: ld hl, wNetRxBuf+12 -- cgit v1.3.1-sl0p