aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Makefile2
-rw-r--r--c/dhcp.c72
-rw-r--r--c/sh.c6
-rw-r--r--c/sock.h7
-rw-r--r--c/tcp.c107
-rw-r--r--include/gbos.inc2
-rw-r--r--src/programs.asm7
-rw-r--r--src/socket.asm97
-rwxr-xr-xtools/netboot45
-rw-r--r--tools/tunbridge.py49
10 files changed, 360 insertions, 34 deletions
diff --git a/Makefile b/Makefile
index e3bad9d..99160dc 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/nslookup.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 c/dhcp.bin
$(BUILD)/programs.o: $(CBLOBS)
# C programs also depend on the shared libc sources
diff --git a/c/dhcp.c b/c/dhcp.c
new file mode 100644
index 0000000..e4186f4
--- /dev/null
+++ b/c/dhcp.c
@@ -0,0 +1,72 @@
+#include "sock.h"
+/* dhcp - a minimal DHCP client. DISCOVER -> OFFER -> REQUEST -> ACK over UDP
+ (kernel owns UDP; this owns BOOTP/DHCP), then hands the leased address to the
+ kernel with net_setip(). Run at boot before the shell. Times out gracefully
+ if there's no server (the shell then comes up unconfigured). */
+
+static unsigned char pkt[288]; /* BOOTP is ~240B + options (frames > 255) */
+static unsigned char ip[4];
+static unsigned char bcast[4];
+
+/* fill the fixed BOOTP request header + magic cookie; options start at 240 */
+static void bootp(void) {
+ unsigned int i;
+ for (i = 0; i < 240; i++) pkt[i] = 0;
+ pkt[0] = 1; pkt[1] = 1; pkt[2] = 6; /* op=request htype=eth hlen=6 */
+ pkt[4] = 0x12; pkt[5] = 0x34; pkt[6] = 0x56; pkt[7] = 0x78; /* xid */
+ pkt[10] = 0x80; /* flags: broadcast reply */
+ pkt[28] = 0x02; pkt[29] = 0x47; pkt[30] = 0x42; pkt[33] = 0x01; /* client MAC */
+ pkt[236] = 0x63; pkt[237] = 0x82; pkt[238] = 0x53; pkt[239] = 0x63; /* magic */
+}
+
+/* DHCP message type from the received packet (option 53), or 0 */
+static unsigned char mtype(void) {
+ unsigned int p = 240;
+ while (p < 280 && pkt[p] != 255) {
+ if (pkt[p] == 0) { p++; continue; } /* pad */
+ if (pkt[p] == 53) return pkt[p + 2];
+ p += 2 + pkt[p + 1];
+ }
+ return 0;
+}
+
+void main(void) {
+ unsigned char s, i, n;
+ unsigned int p;
+ bcast[0] = 255; bcast[1] = 255; bcast[2] = 255; bcast[3] = 255;
+
+ puts("dhcp: discovering"); nl();
+ s = net_socket(SOCK_UDP);
+ if (s == 0xFF) { puts("dhcp: no socket"); nl(); return; }
+ net_bind(s, 68);
+ net_connect(s, bcast, 67);
+
+ /* DISCOVER -> OFFER (retry a few times for startup timing) */
+ bootp();
+ p = 240;
+ pkt[p++] = 53; pkt[p++] = 1; pkt[p++] = 1; /* DHCPDISCOVER */
+ pkt[p++] = 255;
+ n = 0xFF;
+ for (i = 0; i < 3 && n == 0xFF; i++) {
+ net_send(s, pkt, (unsigned char)p);
+ n = net_recv(s, pkt, 255);
+ }
+ if (n == 0xFF || mtype() != 2) { puts("dhcp: no offer"); nl(); net_close(s); return; }
+ ip[0] = pkt[16]; ip[1] = pkt[17]; ip[2] = pkt[18]; ip[3] = pkt[19]; /* yiaddr */
+
+ /* REQUEST -> ACK */
+ bootp();
+ p = 240;
+ pkt[p++] = 53; pkt[p++] = 1; pkt[p++] = 3; /* DHCPREQUEST */
+ pkt[p++] = 50; pkt[p++] = 4; /* requested IP */
+ pkt[p++] = ip[0]; pkt[p++] = ip[1]; pkt[p++] = ip[2]; pkt[p++] = ip[3];
+ pkt[p++] = 255;
+ net_send(s, pkt, (unsigned char)p);
+ n = net_recv(s, pkt, 255);
+ net_close(s);
+ if (n == 0xFF || mtype() != 5) { puts("dhcp: request failed"); nl(); return; }
+ ip[0] = pkt[16]; ip[1] = pkt[17]; ip[2] = pkt[18]; ip[3] = pkt[19];
+
+ net_setip(ip);
+ puts("dhcp: leased "); for (i = 0; i < 4; i++) { putu(ip[i]); if (i < 3) putc('.'); } nl();
+}
diff --git a/c/sh.c b/c/sh.c
index a8dd09d..3178894 100644
--- a/c/sh.c
+++ b/c/sh.c
@@ -155,6 +155,12 @@ void main(void) {
char *tok[16];
char cwd[40];
cwd[0] = '/'; cwd[1] = 0;
+ /* bring up networking first: run the DHCP client and wait for it to finish
+ (got a lease, or gave up). Only then drop to the prompt. */
+ {
+ unsigned char d = lookup("dhcp");
+ if (d != NOFD) { if (fork() == 0) exec(d); else wait(); }
+ }
for (;;) {
unsigned char nt, i, pipepos, c, bg;
/* reap finished background jobs */
diff --git a/c/sock.h b/c/sock.h
index 1298271..9e4dd40 100644
--- a/c/sock.h
+++ b/c/sock.h
@@ -55,4 +55,11 @@ static void net_close(unsigned char s) {
static void net_poll(void) {
_nr.op = 6; sys_net(&_nr);
}
+/* set our IPv4 address (the DHCP client calls this once it has a lease) */
+static void net_setip(const unsigned char *ip) {
+ unsigned char i;
+ _nr.op = 7;
+ for (i = 0; i < 4; i++) _nr.ip[i] = ip[i];
+ sys_net(&_nr);
+}
#endif
diff --git a/c/tcp.c b/c/tcp.c
new file mode 100644
index 0000000..07eb2b1
--- /dev/null
+++ b/c/tcp.c
@@ -0,0 +1,107 @@
+#include "netlib.h"
+/* tcp - a minimal TCP client over SLIP (milestone C). Connects to 10.0.0.1:7,
+ sends a line, prints the reply, closes. No retransmission: the GB<->gateway
+ link is lossless, and the gateway's real socket handles the lossy side.
+ Us = 10.0.0.2, peer = 10.0.0.1. */
+
+static unsigned char pkt[256];
+static unsigned long snd_nxt, rcv_nxt;
+#define LPORT 40000u
+#define RPORT 7u
+
+/* SYN 0x02, ACK 0x10, FIN 0x01, PSH 0x08, RST 0x04 */
+
+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 void put32(unsigned char *p, unsigned long v) {
+ p[0] = (unsigned char)(v >> 24); p[1] = (unsigned char)(v >> 16);
+ p[2] = (unsigned char)(v >> 8); p[3] = (unsigned char)v;
+}
+static unsigned long get32(unsigned char *p) {
+ return ((unsigned long)p[0] << 24) | ((unsigned long)p[1] << 16)
+ | ((unsigned long)p[2] << 8) | p[3];
+}
+
+/* build+send IP+TCP with 'flags' and 'dlen' payload bytes already at pkt[40..] */
+static void tcp_send(unsigned char flags, unsigned char dlen) {
+ unsigned int c, psum, tcplen = 20 + dlen, total = 20 + tcplen;
+ pkt[0] = 0x45; pkt[1] = 0; pkt[2] = (unsigned char)(total >> 8); pkt[3] = (unsigned char)total;
+ pkt[4] = 0; pkt[5] = 1; pkt[6] = 0x40; pkt[7] = 0; pkt[8] = 64; pkt[9] = 6;
+ pkt[10] = 0; pkt[11] = 0;
+ pkt[12] = 10; pkt[13] = 0; pkt[14] = 0; pkt[15] = 2;
+ pkt[16] = 10; pkt[17] = 0; pkt[18] = 0; pkt[19] = 1;
+ pkt[20] = LPORT >> 8; pkt[21] = (unsigned char)LPORT;
+ pkt[22] = RPORT >> 8; pkt[23] = (unsigned char)RPORT;
+ put32(pkt + 24, snd_nxt);
+ put32(pkt + 28, rcv_nxt);
+ pkt[32] = 0x50; pkt[33] = flags; pkt[34] = 2; pkt[35] = 0; /* window 512 */
+ pkt[36] = 0; pkt[37] = 0; pkt[38] = 0; pkt[39] = 0;
+ psum = sum16(pkt + 12, 8, 0);
+ c = 6; psum += c; if (psum < c) psum++;
+ c = tcplen; psum += c; if (psum < c) psum++;
+ c = ~sum16(pkt + 20, tcplen, psum);
+ pkt[36] = (unsigned char)(c >> 8); pkt[37] = (unsigned char)c;
+ c = ~sum16(pkt, 20, 0);
+ pkt[10] = (unsigned char)(c >> 8); pkt[11] = (unsigned char)c;
+ slip_send((char *)pkt, (unsigned char)total);
+}
+
+/* receive the next TCP segment for our connection; returns TCP header offset,
+ sets *flags,*seq,*dlen,*doff. */
+static unsigned char tcp_recv(unsigned char *flags, unsigned long *seq,
+ unsigned char *dlen, unsigned char *doff) {
+ unsigned char len, ihl, hl;
+ for (;;) {
+ len = slip_recv((char *)pkt, 255);
+ if (len < 40 || (pkt[0] >> 4) != 4 || pkt[9] != 6) continue;
+ ihl = (pkt[0] & 0x0f) << 2;
+ if (((unsigned int)pkt[22] << 8 | pkt[23]) != LPORT) continue; /* not our port */
+ hl = (pkt[ihl + 12] >> 4) << 2;
+ *flags = pkt[ihl + 13];
+ *seq = get32(pkt + ihl + 4);
+ *doff = (unsigned char)(ihl + hl);
+ *dlen = (unsigned char)(len - ihl - hl);
+ return ihl;
+ }
+}
+
+void main(void) {
+ unsigned char flags, dlen, doff, i;
+ unsigned long seq;
+ puts("tcp: SYN -> 10.0.0.1:7"); nl();
+ snd_nxt = 1000; rcv_nxt = 0;
+ tcp_send(0x02, 0); /* SYN */
+ snd_nxt = 1001;
+
+ tcp_recv(&flags, &seq, &dlen, &doff); /* expect SYN|ACK */
+ if (!(flags & 0x02)) { puts("tcp: no SYN-ACK"); nl(); return; }
+ rcv_nxt = seq + 1;
+ tcp_send(0x10, 0); /* ACK -> ESTABLISHED */
+
+ pkt[40] = 'h'; pkt[41] = 'i'; pkt[42] = ' '; pkt[43] = 'g'; pkt[44] = 'b';
+ tcp_send(0x18, 5); /* PSH|ACK "hi gb" */
+ snd_nxt += 5;
+
+ for (;;) {
+ tcp_recv(&flags, &seq, &dlen, &doff);
+ if (dlen) {
+ puts("tcp: recv '");
+ for (i = 0; i < dlen; i++) putc(pkt[doff + i]);
+ puts("'"); nl();
+ rcv_nxt = seq + dlen;
+ tcp_send(0x10, 0); /* ACK the data */
+ tcp_send(0x11, 0); /* FIN|ACK - start closing */
+ snd_nxt += 1;
+ }
+ if (flags & 0x01) { /* their FIN */
+ rcv_nxt = seq + dlen + 1;
+ tcp_send(0x10, 0); /* ACK it */
+ puts("tcp: closed"); nl();
+ return;
+ }
+ }
+}
diff --git a/include/gbos.inc b/include/gbos.inc
index 049a4bc..937d083 100644
--- a/include/gbos.inc
+++ b/include/gbos.inc
@@ -152,6 +152,7 @@ DEF NET_RECV EQU 3 ; NR_SOCK, NR_BUF, NR_LEN(max) -> A=len or $FF(timeout);
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)
; socket types
DEF SOCK_ICMP EQU 1
DEF SOCK_UDP EQU 2
@@ -252,5 +253,6 @@ DEF PROG_CHAT EQU 25
DEF PROG_NETD EQU 26
DEF PROG_PING EQU 27
DEF PROG_NSLOOKUP EQU 28
+DEF PROG_DHCP EQU 29
ENDC
diff --git a/src/programs.asm b/src/programs.asm
index 9ae7f58..e89fe0c 100644
--- a/src/programs.asm
+++ b/src/programs.asm
@@ -127,6 +127,9 @@ ProgPing:
SECTION "prog_nslookup", ROMX[$4000], BANK[30]
ProgNslookup:
INCBIN "c/nslookup.bin"
+SECTION "prog_dhcp", ROMX[$4000], BANK[31]
+ProgDhcp:
+ INCBIN "c/dhcp.bin"
; -----------------------------------------------------------------------------
; PROG_SH (bank 3) - the shell, now written in C (c/sh.c): parses >/</| and
@@ -227,6 +230,8 @@ ProgramTable::
dw ProgPing
db LOW(BANK(ProgNslookup)), HIGH(BANK(ProgNslookup))
dw ProgNslookup
+ db LOW(BANK(ProgDhcp)), HIGH(BANK(ProgDhcp))
+ dw ProgDhcp
; -----------------------------------------------------------------------------
; NameTable (ROM0) - command name -> program id, searched by sys_lookup.
@@ -291,4 +296,6 @@ NameTable::
db PROG_PING
db "nslookup", 0
db PROG_NSLOOKUP
+ db "dhcp", 0
+ db PROG_DHCP
db 0
diff --git a/src/socket.asm b/src/socket.asm
index 9ce1daa..3b2b0c9 100644
--- a/src/socket.asm
+++ b/src/socket.asm
@@ -21,11 +21,11 @@ DEF SK_HASRX EQU 9 ; 1 rxbuf holds a datagram
DEF SK_RXLEN EQU 10 ; 1 payload length
DEF SK_SRCIP EQU 11 ; 4 sender IP
DEF SK_SRCPORT EQU 15 ; 2 sender port
-DEF SK_RXBUF EQU 17 ; 208 payload (>= one max segment at MSS 200)
-DEF SK_STATE EQU 225 ; 1 TCP state (see TCP_* below)
-DEF SK_SND EQU 226 ; 4 snd_nxt (our next sequence number)
-DEF SK_RCV EQU 230 ; 4 rcv_nxt (next sequence we expect from peer)
-DEF SK_SIZE EQU 234
+DEF SK_RXBUF EQU 17 ; 288 payload (holds a DHCP reply, or a max segment)
+DEF SK_STATE EQU 305 ; 1 TCP state (see TCP_* below)
+DEF SK_SND EQU 306 ; 4 snd_nxt (our next sequence number)
+DEF SK_RCV EQU 310 ; 4 rcv_nxt (next sequence we expect from peer)
+DEF SK_SIZE EQU 314
DEF TCP_MSS EQU 200
; TCP connection states
@@ -48,9 +48,9 @@ wNetReq:: DS NR_SIZE
wNetSockPtr:: DS 2
wNetIhl:: DS 1
wNetSeq:: DS 1
-wNetTx:: DS 256 ; tx build buffer
-wNetRxBuf:: DS 256 ; rx frame assembly / parse
-wNetRxLen:: DS 1
+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)
wNetRxEsc:: DS 1
wNetUdpPort:: DS 2 ; scratch: UDP dst port being demuxed
wNetTO:: DS 3 ; recv timeout counter (net_pump clobbers registers)
@@ -74,19 +74,31 @@ net_init::
ld a, b
or c
jr nz, .clr
- ld a, 10
- ld [wNetOurIP+0], a
xor a
+ ld [wNetOurIP+0], a ; 0.0.0.0 until DHCP assigns an address
ld [wNetOurIP+1], a
ld [wNetOurIP+2], a
- ld a, 2
ld [wNetOurIP+3], a
- xor a
ld [wNetRxLen], a
+ ld [wNetRxLen+1], a
ld [wNetRxEsc], a
ld [wNetSeq], a
ret
+; net_setip - store our IPv4 address (from wNetReq.NR_IP). Used by the DHCP
+; client once it has a lease.
+net_op_setip:
+ ld a, [wNetReq+NR_IP+0]
+ ld [wNetOurIP+0], a
+ ld a, [wNetReq+NR_IP+1]
+ ld [wNetOurIP+1], a
+ ld a, [wNetReq+NR_IP+2]
+ ld [wNetOurIP+2], a
+ ld a, [wNetReq+NR_IP+3]
+ ld [wNetOurIP+3], a
+ xor a
+ ret
+
; -----------------------------------------------------------------------------
; low-level link port (byte at a time). Preserve BC/DE/HL; use A only.
net_putbyte: ; A = byte to transmit
@@ -113,13 +125,13 @@ net_getbyte_nb: ; -> A = byte, CF set if none
ret
; -----------------------------------------------------------------------------
-; net_slip_send(HL = buf, B = len) - transmit a SLIP frame.
+; net_slip_send(HL = buf, DE = len) - transmit a SLIP frame (len may exceed 255).
net_slip_send:
ld a, SLIP_END
call net_putbyte
.loop
- ld a, b
- or a
+ ld a, d
+ or e
jr z, .end
ld a, [hl+]
cp SLIP_END
@@ -128,7 +140,7 @@ net_slip_send:
jr z, .esc_esc
call net_putbyte
.cont
- dec b
+ dec de
jr .loop
.esc_end
ld a, SLIP_ESC
@@ -235,6 +247,8 @@ sys_net::
jp z, net_op_bind
cp NET_POLL
jp z, net_op_poll
+ cp NET_SETIP
+ jp z, net_op_setip
ld a, $FF
ret
@@ -580,7 +594,8 @@ icmp_send:
; transmit: 28 + payload
ld a, [wNetReq+NR_LEN]
add 28
- ld b, a
+ ld e, a
+ ld d, 0
ld hl, wNetTx
call net_slip_send
xor a
@@ -594,10 +609,12 @@ udp_send:
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
+ ld [wNetTx+3], a ; IP total length low
+ ld a, 0
+ adc 0
+ ld [wNetTx+2], a ; IP total length high (carry, for DHCP)
xor a
ld [wNetTx+4], a
ld [wNetTx+5], a
@@ -727,7 +744,10 @@ udp_send:
ld [wNetTx+11], a
ld a, [wNetReq+NR_LEN]
add 28
- ld b, a
+ ld e, a
+ ld a, 0
+ adc 0
+ ld d, a
ld hl, wNetTx
call net_slip_send
xor a
@@ -774,20 +794,32 @@ net_pump:
.store
ld a, c
.store2
- ; store A at wNetRxBuf[wNetRxLen] if room
- ld c, a
+ ld c, a ; C = byte to store
+ ; index = wNetRxLen (16-bit); drop if >= 320 (buffer size)
+ ld a, [wNetRxLen+1]
+ or a
+ jr z, .instore ; high 0 -> index < 256, room
+ cp 1
+ jr nz, .next ; high >= 2 -> overflow, drop
+ ld a, [wNetRxLen]
+ cp 64 ; high 1 -> room iff low < 64 (index < 320)
+ jr nc, .next
+.instore
ld a, [wNetRxLen]
- cp 255
- jr nc, .next ; overflow: drop
ld e, a
- ld d, 0
+ ld a, [wNetRxLen+1]
+ ld d, a
ld hl, wNetRxBuf
add hl, de
ld a, c
ld [hl], a
- ld a, [wNetRxLen]
+ ld a, [wNetRxLen] ; index++
inc a
ld [wNetRxLen], a
+ jr nz, .next
+ ld a, [wNetRxLen+1]
+ inc a
+ ld [wNetRxLen+1], a
jr .next
.frame
; END: if we have a frame, process it and return (one frame per pump, so a
@@ -795,11 +827,13 @@ net_pump:
xor a
ld [wNetRxEsc], a
ld a, [wNetRxLen]
- or a
+ ld hl, wNetRxLen+1
+ or [hl]
jr z, .next ; empty frame, keep reading
call process_frame
xor a
ld [wNetRxLen], a
+ ld [wNetRxLen+1], a
ret
; -----------------------------------------------------------------------------
@@ -891,7 +925,9 @@ icmp_request:
ld [wNetRxBuf+11], a
; send it back
ld a, [wNetRxLen]
- ld b, a
+ ld e, a
+ ld a, [wNetRxLen+1]
+ ld d, a
ld hl, wNetRxBuf
call net_slip_send
ret
@@ -1406,7 +1442,10 @@ tcp_send_seg:
ld b, a
ld a, [wNetTcpDlen]
add b
- ld b, a
+ ld e, a
+ ld a, 0
+ adc 0
+ ld d, a
ld hl, wNetTx
call net_slip_send
ret
diff --git a/tools/netboot b/tools/netboot
index 3d27cf8..bca93ee 100755
--- a/tools/netboot
+++ b/tools/netboot
@@ -34,6 +34,45 @@ def slip_encode(pkt):
o.append(END)
return bytes(o)
+# ---- built-in DHCP server: lease the GB 10.0.0.2 ----------------------------
+GB_IP = b"\x0a\x00\x00\x02"; GW = b"\x0a\x00\x00\x01"
+def _cksum(d):
+ s = 0
+ for i in range(0, len(d) - 1, 2): s += (d[i] << 8) | d[i + 1]
+ if len(d) % 2: s += d[-1] << 8
+ while s >> 16: s = (s & 0xFFFF) + (s >> 16)
+ return (~s) & 0xFFFF
+def _dhcp_mtype(dhcp):
+ p = 240
+ while p < len(dhcp) and dhcp[p] != 255:
+ if dhcp[p] == 0: p += 1; continue
+ if dhcp[p] == 53: return dhcp[p + 2]
+ p += 2 + dhcp[p + 1]
+ return 0
+def _dhcp_reply(req, mt):
+ d = bytearray(240)
+ d[0] = 2; d[1] = 1; d[2] = 6
+ d[4:8] = req[4:8]; d[16:20] = GB_IP; d[20:24] = GW; d[28:44] = req[28:44]
+ d[236:240] = b"\x63\x82\x53\x63"
+ d += bytes([53, 1, mt, 54, 4]) + GW + bytes([255])
+ udp = bytearray(8); udp[1] = 67; udp[3] = 68
+ ul = 8 + len(d); udp[4] = ul >> 8; udp[5] = ul & 0xFF
+ udp += d
+ tot = 20 + len(udp)
+ ip = bytearray(20); ip[0] = 0x45; ip[2] = tot >> 8; ip[3] = tot & 0xFF
+ ip[8] = 64; ip[9] = 17; ip[12:16] = GW; ip[16:20] = b"\xff\xff\xff\xff"
+ ck = _cksum(ip); ip[10] = ck >> 8; ip[11] = ck & 0xFF
+ return bytes(ip) + bytes(udp)
+def dhcp_intercept(frame, send_raw):
+ if len(frame) < 28 or frame[9] != 17: return False
+ ihl = (frame[0] & 0x0f) * 4
+ if len(frame) < ihl + 4 or ((frame[ihl + 2] << 8) | frame[ihl + 3]) != 67: return False
+ dhcp = frame[ihl + 8:]
+ mt = _dhcp_mtype(dhcp)
+ if mt == 1: send_raw(_dhcp_reply(dhcp, 2))
+ elif mt == 3: send_raw(_dhcp_reply(dhcp, 5))
+ return True
+
def sh(cmd):
subprocess.run(cmd, shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
@@ -99,8 +138,10 @@ def main():
for c in chunk:
if c == END:
if in_frame and buf:
- try: os.write(tun, bytes(buf))
- except Exception: pass
+ fr = bytes(buf)
+ if not dhcp_intercept(fr, lambda pk: conn.sendall(slip_encode(pk))):
+ try: os.write(tun, fr)
+ except Exception: pass
buf, in_frame, esc = bytearray(), False, False
else:
buf, in_frame, esc = bytearray(), True, False
diff --git a/tools/tunbridge.py b/tools/tunbridge.py
index f5e391c..d5669e4 100644
--- a/tools/tunbridge.py
+++ b/tools/tunbridge.py
@@ -27,6 +27,49 @@ def slip_encode(pkt):
out.append(END)
return bytes(out)
+# ---- built-in DHCP server: the bridge leases the GB 10.0.0.2 ----------------
+GB_IP = b"\x0a\x00\x00\x02"; GW = b"\x0a\x00\x00\x01"
+def _cksum(d):
+ s = 0
+ for i in range(0, len(d) - 1, 2): s += (d[i] << 8) | d[i + 1]
+ if len(d) % 2: s += d[-1] << 8
+ while s >> 16: s = (s & 0xFFFF) + (s >> 16)
+ return (~s) & 0xFFFF
+def _dhcp_mtype(dhcp):
+ p = 240
+ while p < len(dhcp) and dhcp[p] != 255:
+ if dhcp[p] == 0: p += 1; continue
+ if dhcp[p] == 53: return dhcp[p + 2]
+ p += 2 + dhcp[p + 1]
+ return 0
+def _dhcp_reply(req, mt):
+ d = bytearray(240)
+ d[0] = 2; d[1] = 1; d[2] = 6
+ d[4:8] = req[4:8] # xid
+ d[16:20] = GB_IP # yiaddr
+ d[20:24] = GW # siaddr
+ d[28:44] = req[28:44] # chaddr
+ d[236:240] = b"\x63\x82\x53\x63"
+ d += bytes([53, 1, mt, 54, 4]) + GW + bytes([255])
+ udp = bytearray(8); udp[1] = 67; udp[3] = 68
+ ul = 8 + len(d); udp[4] = ul >> 8; udp[5] = ul & 0xFF
+ udp += d
+ tot = 20 + len(udp)
+ ip = bytearray(20); ip[0] = 0x45; ip[2] = tot >> 8; ip[3] = tot & 0xFF
+ ip[8] = 64; ip[9] = 17; ip[12:16] = GW; ip[16:20] = b"\xff\xff\xff\xff"
+ ck = _cksum(ip); ip[10] = ck >> 8; ip[11] = ck & 0xFF
+ return bytes(ip) + bytes(udp)
+def dhcp_intercept(frame, send_raw):
+ """If frame is a DHCP request (UDP -> :67), answer it and return True."""
+ if len(frame) < 28 or frame[9] != 17: return False
+ ihl = (frame[0] & 0x0f) * 4
+ if len(frame) < ihl + 4 or ((frame[ihl + 2] << 8) | frame[ihl + 3]) != 67: return False
+ dhcp = frame[ihl + 8:]
+ mt = _dhcp_mtype(dhcp)
+ if mt == 1: send_raw(_dhcp_reply(dhcp, 2)) # DISCOVER -> OFFER
+ elif mt == 3: send_raw(_dhcp_reply(dhcp, 5)) # REQUEST -> ACK
+ return True
+
def sh(cmd, quiet=True):
subprocess.run(cmd, shell=True, check=False,
stdout=subprocess.DEVNULL if quiet else None,
@@ -81,8 +124,10 @@ def main():
c = b[0]
if c == END:
if in_frame and buf:
- try: os.write(tun, bytes(buf))
- except Exception: pass
+ fr = bytes(buf)
+ if not dhcp_intercept(fr, lambda pk: (p.stdin.write(slip_encode(pk)), p.stdin.flush())):
+ try: os.write(tun, fr)
+ except Exception: pass
buf, in_frame, esc = bytearray(), False, False
else:
buf, in_frame, esc = bytearray(), True, False