aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Makefile2
-rw-r--r--c/nslookup.c88
-rw-r--r--c/sock.h6
-rw-r--r--include/gbos.inc1
-rw-r--r--src/programs.asm7
-rw-r--r--src/socket.asm342
6 files changed, 424 insertions, 22 deletions
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 >/</| and
@@ -222,6 +225,8 @@ ProgramTable::
dw ProgNetd
db LOW(BANK(ProgPing)), HIGH(BANK(ProgPing))
dw ProgPing
+ db LOW(BANK(ProgNslookup)), HIGH(BANK(ProgNslookup))
+ dw ProgNslookup
; -----------------------------------------------------------------------------
; NameTable (ROM0) - command name -> 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
@@ -493,6 +527,153 @@ icmp_send:
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