1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
|
#!/usr/bin/env python3
"""netboot - bring up real networking (TUN + NAT) and launch sl0pboy with your
own arguments, link port bridged to the TUN over a unix socket.
Unlike the headless `tunbridge`, this keeps the LCD + on-screen keyboard, so you
get a fully interactive, networked Game Boy: the console is the OSK, the link
port is the network - no sharing, no confusion.
sudo tools/netboot --sixel --chrome gbos.gb
sudo tools/netboot gbos.gb
Then, on the Game Boy: press SELECT for the OSK and type e.g.
wget example.com
ping 1.1.1.1
nslookup sl0p.foo
The GB is 10.0.0.2; the host/gateway is 10.0.0.1. Ctrl-C (or quit the emu) to
tear everything down. Requires /dev/net/tun and root.
"""
import os, sys, fcntl, struct, subprocess, socket, threading, atexit
HOME = os.path.expanduser("~" + (os.environ.get("SUDO_USER") or ""))
EMU = os.path.join(HOME, "dev/gbc/build/sl0pboy")
SOCKPATH = "/tmp/sl0pboy-net.sock"
IFACE, GW_IP, GB_NET, MTU = "gbtun0", "10.0.0.1", "10.0.0.0/24", 256
END, ESC, ESC_END, ESC_ESC = 0xC0, 0xDB, 0xDC, 0xDD
TUNSETIFF, IFF_TUN, IFF_NO_PI = 0x400454ca, 0x0001, 0x1000
def slip_encode(pkt):
o = bytearray([END])
for c in pkt:
if c == END: o += bytes([ESC, ESC_END])
elif c == ESC: o += bytes([ESC, ESC_ESC])
else: o.append(c)
o.append(END)
return bytes(o)
def sh(cmd):
subprocess.run(cmd, shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
def wan_iface():
try:
return subprocess.check_output("ip route show default | awk '{print $5; exit}'",
shell=True).decode().strip() or "eth0"
except Exception:
return "eth0"
def main():
if os.geteuid() != 0:
sys.exit("netboot: must run as root (sudo)")
args = sys.argv[1:]
if not args:
sys.exit("usage: sudo netboot [emulator options] <rom>")
rom, opts = os.path.abspath(args[-1]), args[:-1]
if not os.path.exists(EMU):
sys.exit("netboot: emulator not found at " + EMU)
tun = os.open("/dev/net/tun", os.O_RDWR)
fcntl.ioctl(tun, TUNSETIFF, struct.pack("16sH", IFACE.encode(), IFF_TUN | IFF_NO_PI))
wan = wan_iface()
sh("ip addr add %s/24 dev %s" % (GW_IP, IFACE))
sh("ip link set %s mtu %d up" % (IFACE, MTU))
sh("sysctl -w net.ipv4.ip_forward=1")
sh("iptables -t nat -C POSTROUTING -s %s -o %s -j MASQUERADE || "
"iptables -t nat -A POSTROUTING -s %s -o %s -j MASQUERADE" % (GB_NET, wan, GB_NET, wan))
sh("iptables -C FORWARD -i %s -j ACCEPT || iptables -I FORWARD -i %s -j ACCEPT" % (IFACE, IFACE))
sh("iptables -C FORWARD -o %s -j ACCEPT || iptables -I FORWARD -o %s -j ACCEPT" % (IFACE, IFACE))
def cleanup():
sh("iptables -t nat -D POSTROUTING -s %s -o %s -j MASQUERADE" % (GB_NET, wan))
sh("iptables -D FORWARD -i %s -j ACCEPT" % IFACE)
sh("iptables -D FORWARD -o %s -j ACCEPT" % IFACE)
try: os.unlink(SOCKPATH)
except Exception: pass
atexit.register(cleanup)
try: os.unlink(SOCKPATH)
except Exception: pass
srv = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
srv.bind(SOCKPATH); srv.listen(1)
sys.stderr.write("[netboot] %s up %s/24, NAT via %s. GB=10.0.0.2. "
"OSK (SELECT) -> wget example.com / ping 1.1.1.1\n" % (IFACE, GW_IP, wan))
emu = subprocess.Popen([EMU] + opts + ["--serial-sock", SOCKPATH, rom])
srv.settimeout(10)
try:
conn, _ = srv.accept()
except socket.timeout:
sys.stderr.write("[netboot] emulator never connected the link socket\n")
emu.kill(); return
conn.setblocking(True)
def sock_to_tun(): # Game Boy -> host
buf, in_frame, esc = bytearray(), False, False
while True:
try: chunk = conn.recv(2048)
except Exception: return
if not chunk: return
for c in chunk:
if c == END:
if in_frame and buf:
try: os.write(tun, bytes(buf))
except Exception: pass
buf, in_frame, esc = bytearray(), False, False
else:
buf, in_frame, esc = bytearray(), True, False
elif in_frame:
if esc: c = {ESC_END: END, ESC_ESC: ESC}.get(c, c); esc = False; buf.append(c)
elif c == ESC: esc = True
else: buf.append(c)
# non-frame bytes are the console mirror; it's on the LCD, ignore
def tun_to_sock(): # host -> Game Boy
while True:
try: pkt = os.read(tun, 2048)
except Exception: return
if not pkt: return
try: conn.sendall(slip_encode(pkt))
except Exception: return
threading.Thread(target=sock_to_tun, daemon=True).start()
threading.Thread(target=tun_to_sock, daemon=True).start()
try:
emu.wait()
except KeyboardInterrupt:
emu.terminate()
if __name__ == "__main__":
main()
|